Skip to content

Instantly share code, notes, and snippets.

@fizerkhan
Forked from otanistudio/chat-frontend.js
Created August 5, 2014 06:23
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fizerkhan/65ab6ce4ebd866fabc24 to your computer and use it in GitHub Desktop.
Save fizerkhan/65ab6ce4ebd866fabc24 to your computer and use it in GitHub Desktop.
/*global $, Backbone, window, console, WebSocket, _ */
$(function () {
"use strict";
// for better performance - to avoid searching in DOM
var $content = $('#content')
, $input = $('#input')
, $status = $('#status')
// my color assigned by the server
, myColor = false
// my name sent to the server
, myName = false
, MessageView = Backbone.View.extend({
tagName: "p"
, initialize: function() {
Backbone.View.prototype.initialize.apply(this, arguments);
this.collection.bind('add', this.render, this);
return this;
}
, template: _.template($('#message-template').html())
, render : function() {
var dt = this.model.get('dt')
, hours = dt.getHours() < 10 ? '0' + dt.getHours() : dt.getHours()
, minutes = dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes()
, text = this.model.get('text')
, color = this.model.get('color')
, templateData = {'author':this.model.get('author'), 'hours':hours, 'minutes':minutes,'text':text, 'color':color};
$content.append(this.$el.html(this.template(templateData)));
return this;
}
})
, Message = Backbone.Model.extend({
defaults: {
text: 'default text'
, author: 'anonymous'
, color: 'black'
, dt: null
}
})
, Messages = Backbone.Collection.extend({
model: Message
});
// if user is running mozilla then use it's built-in WebSocket
window.WebSocket = window.WebSocket || window.MozWebSocket;
var app = {
socketURL: 'ws://localhost:1337'
, initialize: function() {
// if browser doesn't support WebSocket, just show some notification and exit
if (!window.WebSocket) {
$content.html($('<p>', { text: 'Sorry, but your browser doesn\'t ' +
'support WebSockets.'} ));
$input.hide();
$('span').hide();
return;
}
// open connection
this.connection = new WebSocket(this.socketURL);
/**
* This method is optional. If the server wasn't able to respond to the
* in 3 seconds then show some error message to notify the user that
* something is wrong.
*/
window.setInterval(function() {
if (app.connection.readyState !== 1) {
$status.text('Error');
$input.attr('disabled', 'disabled').val('Unable to comminucate ' +
'with the WebSocket server.');
}
}, 3000);
this.connection.addEventListener("open", function () {
// first we want users to enter their names
$input.removeAttr('disabled');
$status.text('Choose name:');
});
this.connection.addEventListener("error", function (error) {
// just in there were some problems with conenction...
$content.html($('<p>', { text: 'Sorry, but there\'s some problem with your ' +
'connection or the server is down.</p>' } ));
});
// most important part - incoming messages
this.connection.addEventListener("message", function (message) {
app.processMessage(message);
});
this.messages = new Messages();
this.messages.url = '/';
}
, bundledMessage: function(data) {
var newMessage = new Message({
author: data.author
, color: data.color
, dt: new Date(data.time)
, text: data.text
})
, messageView = new MessageView({
model: newMessage, collection:app.messages
});
return newMessage;
}
, processMessage: function(message) {
// try to parse JSON message. Because we know that the server always returns
// JSON this should work without any problem but we should make sure that
// the massage is not chunked or otherwise damaged.
var json = JSON.parse(message.data);
// NOTE: if you're not sure about the JSON structure
// check the server source code above
if (json.type === 'color') { // first response from the server with user's color
myColor = json.data;
$status.text(myName + ': ').css('color', myColor);
$input.removeAttr('disabled').focus();
// from now user can start sending messages
} else if (json.type === 'history') { // entire message history
// insert every single message to the chat window
var history = []
, i = 0;
for (i=0; i < json.data.length; i++) {
history.push(app.bundledMessage(json.data[i]));
}
app.messages.add(history);
} else if (json.type === 'message') { // it's a single message
$input.removeAttr('disabled'); // let the user write another message
app.messages.add(app.bundledMessage(json.data));
} else {
throw new Error('Hmm..., I\'ve never seen JSON like this: ' + json);
}
}
};
/**
* Send mesage when user presses Enter key
*/
$input.keydown(function(e) {
if (e.keyCode === 13) {
var msg = $(this).val();
if (!msg) {
return;
}
// send the message as an ordinary text
app.connection.send(msg);
$(this).val('');
// disable the input field to make the user wait until server
// sends back response
$input.attr('disabled', 'disabled');
// we know that the first message sent from a user their name
if (myName === false) {
myName = msg;
}
}
});
app.initialize();
});
// http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
"use strict";
// Optional. You will see this name in eg. 'ps' or 'top' command
process.title = 'node-chat';
// Port where we'll run the websocket server
var webSocketsServerPort = 1337;
// websocket and http servers
var webSocketServer = require('websocket').server;
var http = require('http');
/**
* Global variables
*/
// latest 100 messages
var history = [ ];
// list of currently connected clients (users)
var clients = [ ];
/**
* Helper function for escaping input strings
*/
function htmlEntities(str) {
return String(str).replace(/&/g, '&amp;').replace(/</g, '&lt;')
.replace(/>/g, '&gt;').replace(/"/g, '&quot;');
}
// Array with some colors
var colors = [ 'red', 'green', 'blue', 'magenta', 'purple', 'plum', 'orange' ];
// ... in random order
colors.sort(function(a,b) { return Math.random() > 0.5; } );
/**
* HTTP server
*/
var server = http.createServer(function(request, response) {
// Not important for us. We're writing WebSocket server, not HTTP server
});
server.listen(webSocketsServerPort, function() {
console.log((new Date()) + " Server is listening on port " + webSocketsServerPort);
});
/**
* WebSocket server
*/
var wsServer = new webSocketServer({
// WebSocket server is tied to a HTTP server. To be honest I don't understand why.
httpServer: server
});
// This callback function is called every time someone
// tries to connect to the WebSocket server
wsServer.on('request', function(request) {
console.log((new Date()) + ' Connection from origin ' + request.origin + '.');
// accept connection - you should check 'request.origin' to make sure that
// client is connecting from your website
// (http://en.wikipedia.org/wiki/Same_origin_policy)
var connection = request.accept(null, request.origin);
// we need to know client index to remove them on 'close' event
var index = clients.push(connection) - 1;
var userName = false;
var userColor = false;
console.log((new Date()) + ' Connection accepted.');
// send back chat history
if (history.length > 0) {
connection.sendUTF(JSON.stringify( { type: 'history', data: history} ));
}
// user sent some message
connection.on('message', function(message) {
if (message.type === 'utf8') { // accept only text
if (userName === false) { // first message sent by user is their name
// remember user name
userName = htmlEntities(message.utf8Data);
// get random color and send it back to the user
userColor = colors.shift();
connection.sendUTF(JSON.stringify({ type:'color', data: userColor }));
console.log((new Date()) + ' User is known as: ' + userName
+ ' with ' + userColor + ' color.');
} else { // log and broadcast the message
console.log((new Date()) + ' Received Message from '
+ userName + ': ' + message.utf8Data);
// we want to keep history of all sent messages
var obj = {
time: (new Date()).getTime(),
text: htmlEntities(message.utf8Data),
author: userName,
color: userColor
};
history.push(obj);
history = history.slice(-100);
// broadcast message to all connected clients
var json = JSON.stringify({ type:'message', data: obj });
for (var i=0; i < clients.length; i++) {
clients[i].sendUTF(json);
}
}
}
});
// user disconnected
connection.on('close', function(connection) {
if (userName !== false && userColor !== false) {
console.log((new Date()) + " Peer "
+ connection.remoteAddress + " disconnected.");
// remove user from the list of connected clients
clients.splice(index, 1);
// push back user's color to be reused by another user
colors.push(userColor);
}
});
});
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>WebSockets - Simple chat</title>
<style>
* { font-family:tahoma; font-size:12px; padding:0px; margin:0px; }
p { line-height:18px; }
div { width:500px; margin-left:auto; margin-right:auto;}
#content { padding:5px; background:#ddd; border-radius:5px;
border:1px solid #CCC; margin-top:10px; }
#input { border-radius:2px; border:1px solid #ccc;
margin-top:10px; padding:5px; width:400px; }
#status { width:88px; display:block; float:left; margin-top:15px; }
</style>
</head>
<body>
<div id="content"></div>
<div>
<span id="status">Connecting...</span>
<input type="text" id="input" disabled="disabled" />
</div>
<script type="text/template" id="message-template">
<span style="color:<%= color %>"><%= author %></span> @ <%= hours %>:<%= minutes %>: <%= text %>
</script>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js"></script>
<script src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script src="chat-frontend.js"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment