-
-
Save martinsik/2031681 to your computer and use it in GitHub Desktop.
$(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, '&').replace(/</g, '<') | |
.replace(/>/g, '>').replace(/"/g, '"'); | |
} | |
// 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> |
Hugely useful resource, thanks very much for posting it.
I think the method of removing clients is suspect. If you remove an element from an array, the index of the other (higher numbered) items will change.
l = [0, 1, 2, 3, 4];
console.log(l[3]) //3
l.splice(2, 1)
console.log(l[3]) //4
You could store the connections in a hash using some kind of unique (time based maybe) id.
should we call frontend.html ? Where is chat.html file?
In the chat-server.js, the line
content : fs.readFileSync('.'+fn[0]).toString(),
should be replaced by this:
content : fs.readFileSync(__dirname + fn[0]).toString(),
for services to have a better chance to run (to prevent from file not found)
should we call frontend.html ? Where is chat.html file?
frontend.html => chat.html. It's work.
Hi @martinsik this is excellent. I have been trying to basically replicate this using pure http2. Can i please share my code with you and ask for your help?
Specifically i am stuck when the client enters text into the input field, then hits the submit button, i can handle that request in the server code, but i don't know how to stream it back to the client(s).
May I ask what this is for?
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'
host the frontend.html using npm http-server, and then you can access the chat application using the ip provided by the http-server
steps:
npm install http-server
cd frontend.js (give the path where the file is located)
http-server
How Can I use it in another computer?
Use the following code to work with http://localhost:1337/frontend.html
var basePath = __dirname;
var http = require('http');
var fs = require('fs');
var path = require('path');
http.createServer(function(req, res) {
var stream = fs.createReadStream(path.join(basePath, req.url));
stream.on('error', function() {
res.writeHead(404);
res.end();
});
stream.pipe(res);
})
install finalhandler and serve-static with the npm
npm install finalhandler serve-static
thanks i succeed
Thank for this source
Does the server write back to the chat? If so, then how?
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
thank you so much.
Very helpful.
Thank you.
how you access a list of all "userNames" connected to the server to send to someone?
License?
Can we use in different systems