Create a gist now

Instantly share code, notes, and snippets.

What would you like to do?
Node.js chat frontend and server
$(function () {
"use strict";
// for better performance - to avoid searching in DOM
var content = $('#content');
var input = $('#input');
var status = $('#status');
// my color assigned by the server
var myColor = false;
// my name sent to the server
var myName = false;
// if user is running mozilla then use it's built-in WebSocket
window.WebSocket = window.WebSocket || window.MozWebSocket;
// 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
var connection = new WebSocket('ws://127.0.0.1:1337');
connection.onopen = function () {
// first we want users to enter their names
input.removeAttr('disabled');
status.text('Choose name:');
};
connection.onerror = 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.' } ));
};
// most important part - incoming messages
connection.onmessage = 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.
try {
var json = JSON.parse(message.data);
} catch (e) {
console.log('This doesn\'t look like a valid JSON: ', message.data);
return;
}
// 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
for (var i=0; i < json.data.length; i++) {
addMessage(json.data[i].author, json.data[i].text,
json.data[i].color, new Date(json.data[i].time));
}
} else if (json.type === 'message') { // it's a single message
input.removeAttr('disabled'); // let the user write another message
addMessage(json.data.author, json.data.text,
json.data.color, new Date(json.data.time));
} else {
console.log('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
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;
}
}
});
/**
* 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.
*/
setInterval(function() {
if (connection.readyState !== 1) {
status.text('Error');
input.attr('disabled', 'disabled').val('Unable to comminucate '
+ 'with the WebSocket server.');
}
}, 3000);
/**
* Add message to the chat window
*/
function addMessage(author, message, color, dt) {
content.prepend('<p><span style="color:' + color + '">' + author + '</span> @ ' +
+ (dt.getHours() < 10 ? '0' + dt.getHours() : dt.getHours()) + ':'
+ (dt.getMinutes() < 10 ? '0' + dt.getMinutes() : dt.getMinutes())
+ ': ' + message + '</p>');
}
});
// 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. WebSocket request is just
// an enhanced HTTP request. For more info http://tools.ietf.org/html/rfc6455#page-6
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; overflow-y: scroll;
border:1px solid #CCC; margin-top:10px; height: 160px; }
#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 src="//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="./frontend.js"></script>
</body>
</html>

Hi Martinsik,

I am able to perform chat when i use the url in the browser as "file:///D:/jsfiles/nodejs-chat-IMPORTANT/nodejs-chat/chat.html"

If i use the in the browser as 'http://127.0.0.1:1337/chat.html', i am not able to connect to the server. please let me know the way to access chat application by pointing browser as like 'http://127.0.0.1:1337/chat.html'

something

This works, if you change line 26 of frontend.html to
<script src="chat-frontend.js"></script>

<script src="frontend.js"></script>

hmm... change the filename from "frontend.js" to "chat-frontend.js"

Is there a license for this gist ... could I make a few changes and add it to my website?

Owner

martinsik commented Jan 13, 2013

Feel free to use it as you want. Just if you were publishing it somewhere, please, provide a link back to this original source.

joshmiller83:
The reason this didn't work is because when client.html request frontend.js the server doesnt return the file.
Why?
The author left the HTTP server handler empty.
Solution:
You need to modules:
var url = require("url");
var st = require('node-static');

var fileServer = new st.Server('./');

and change the handle to

var server = http.createServer(function(request, response) {
request.addListener('end', function () {
var _get = url.parse(request.url, true).query;
fileServer.serve(request, response);
});
});

Welcome

Thanks for the script.

am not able to type on textfield (frontend.html)

rayj00 commented Feb 26, 2016

Very nice. Thanks.

if you are running the htlm as file on the browser then on frontend.html replace lines 25,26 with the following and everything runs fine

<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="./chat-frontend.js"></script>

Martinsik thanks for sharing !

to serve the frontend.html direct from the chat-server.js you need to do these:

install finalhandler and serve-static with the npm
npm install finalhandler serve-static

in the file chat-server.js replace line 39

// Not important for us. We're writing WebSocket server, not HTTP server

with

    var done = finalhandler(request, response);
    serve(request, response, done);

at line 21 add these lines

var finalhandler = require('finalhandler');
var serveStatic = require('serve-static');

var serve = serveStatic("./");

int the file frontend.html replace line 26
<script src="./frontend.js"></script>
with
<script src="./chat-frontend.js"></script>

finally call the chat page with

http://127.0.0.1:1337/frontend.html

jwaldron92 commented Jun 22, 2016 edited

Like why would your write; "//ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js" and
"./frontend.js">

When the file is "https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"
"./chat-frontend.js"

besides that, great job. Can you reference how to put this online with backend saving?

Voakie commented Aug 2, 2016

Found a little typo:
('Unable to *comminucate* ' + 'with the WebSocket server.');

Great work nonetheless!

@ghost

ghost commented Aug 11, 2016

How are the username, color and other things trashed when user closes connection?

ivandi1980 commented Oct 2, 2016 edited

Thanks kalxasath, it works for me.....
very nice chat...

tentenponce commented Mar 15, 2017 edited

Found a bug. Wrong splicing of clients due to handling of index.

Try to connect two clients on websocket
Refresh the first client
Refresh the second client
One of them will not be able to receive any messages due to their connection has been spliced on disconnect.

Solution:

connection.on('close', function(connection) {
        if (userName !== false && userColor !== false) {
            console.log((new Date()) + " Peer "
                + connection.remoteAddress + " disconnected.");
            // loop to the clients and compare remote address to be removed
           for (var i = 0; i < clients.length; i ++) {
                if (connection.remoteAddress == clients[i].remoteAddress) { //compare remote address to remove from the disconnecting client
                     clients.splice(i, 1);
                }
           }
            // push back user's color to be reused by another user
            colors.push(userColor);
        }
    })

Thanks by the way for this tutorial, very helpful :)

mingyun commented Apr 1, 2017

nice

fanfare commented May 15, 2017 edited

Found a bug in tentenponce's patch.

Everything appears to be a working fine, only because clients.splice(i, 1) is never called, ever.. (note that 'connection' is being passed into the callback function.. thus connection.remoteAddress is always undefined) and therefore no client splicing, and the clients array will become gigantic over time. This fix should do the trick (note it is no longer ensuring userName and userColor are true, because if someone was sitting there refreshing the page, that would keep pushing new connections to the client array.)

connection.on('close', function(e) {
  for (var i = 0; i < clients.length; i ++) {
    if ((connection.remoteAddress == clients[i].remoteAddress) 
      && (connection.socket._peername.port == clients[i].socket._peername.port)) {
      clients.splice(i, 1)
    }
  }
  colors.push(userColor)
});

rayj00 commented Jun 8, 2017

I started to play with this script about a year ago, but had to abandon in lieu of more pressing issues...
On the console, I get Thu Jun 08 2017 16:45:10 GMT-0400 (EDT) Server is listening on port 1337.

How ever on the client, I am getting "Sorry, bit there's some problem with your connection or the server is down" in the chat response box.
In the chat enter box I am getting Error Unable to communicate with the Websocket server.

Any ideas?

Ray

Same here!

rayj00 commented Jun 20, 2017 edited

So I am trying again to get this to work. I have a new Ubuntu 16.04.2.
I put all three files (chat-frontend.js, chat-server.js and frontend.html in the same directory.
Then I did sudo node chat-server.js and got: Tue Jun 20 2017 13:11:29 GMT-0400 (EDT) Server is listening on port 1337
Browsing to http://192.168.0.15:1337 just sits there rolling, Waiting for 192.168.0.15...

And yes I renamed the frontend.js file to chat-frontend.js

Ideas?

Ray

rayj00 commented Jun 21, 2017 edited

Tried the above suggested fixes. Nothing helps.
I know I had this script farther along about a year ago.
I know I could access the html page, but I could not get a connection to the server.
Now I am stuck. It will not even serve up the html?

And BTW, the only change I remember doing is the filename change from frontend.js to chat-frontend.js.
So I don't know what all the above changes are for?

Can anyone provide working code and just how you store the files and call the chat.

rayj00 commented Jun 21, 2017

Ah....forget the above post. I fat fingered.

So now I am back to
chat error

rayj00 commented Jun 21, 2017

Ok got up to entering my name but after I enter my name, it still says: Choose Name: Then eventually says Unable to comminucate with the WebSocket server. in the text entry box. But the message display area is blank, so I assume that's good?

rayj00 commented Jun 21, 2017

Ok, now I cannot get beyond the "Choose Name" step. If I just let it sit at Choose Name (after entering a name) it eventually ends up with Error Unable to comminucate with the WebSocket server.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment