diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md index cbba4e2..db68ccc 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,13 @@ # Real Time Web With Node.js -This is the code for the [Real Time Web with Node.js](http://www.codeschool.com/courses/real-time-web-with-nodejs) demo app. +This is the code for the [Real Time Web with Node.js][realtime] demo app, __chattr__. + +## Getting Started + +To run the server you'll need [Node.js](http://nodejs.org) and [Redis](http://redis.io) installed. Then run the following commands: + +1. `$ npm install` +1. `$ redis-server &` +1. `$ node app.js` + +At this point you will be able to go to the address `http://localhost:8000/` in your web browser to interact with the app. diff --git a/app.js b/app.js new file mode 100644 index 0000000..9f164df --- /dev/null +++ b/app.js @@ -0,0 +1,106 @@ +var express = require('express'); +var app = express(); +var server = require('http').createServer(app); +var io = require('socket.io').listen(server); +var redisClient = require('./lib/redisClient.js'); + +var port = process.env.PORT || 8000; +server.listen(port); +console.log('Server listening on port %d', port); + +/** + * Serve static files from `public` + */ + +app.use(express.static(__dirname + '/public')); + +/** + * Handle all routes to the webserver + */ + +app.get('/*', function (req, res) { + res.sendfile(__dirname + '/index.html'); +}); + +/** + * Store a message in redis + * + * @function storeMessage + * @param {String} name + * @param {Object} data + */ + +function storeMessage (name, data) { + var message = JSON.stringify({name:name, data:data}); + + // store up to 10 messages + redisClient.lpush('messages', message, function(error) { + if (error) throw error; + redisClient.ltrim('messages', 0, 10); + }); +}; + +/** + * Handle connnection Websockets + */ + +io.sockets.on('connection', function (client) { + + + /** + * When users join, set their nickname and broadcast they are here + * Add the user to redis set `chatters` + */ + + client.on('join', function(name) { + client.set('nickname', name); + client.broadcast.emit('add chatter', name); + redisClient.sadd('chatters', name); + + /** + * Add all current chatters to the current client’s chatters list + */ + + redisClient.smembers('chatters', function(error, names) { + names.forEach(function(name) { + client.emit('add chatter', name); + }); + }); + + /** + * Add latest chat messages to current client + */ + + redisClient.lrange('messages', 0, -1, function( error, messages) { + messages = messages.reverse(); + + messages.forEach(function(message) { + message = JSON.parse(message); + client.emit('messages',message.name + ' : ' + message.data); + }); + }); + }); + + /** + * When a message comes through, get the name and broadcast the messsage + * Store the message after we get the nicname + */ + + client.on('messages', function(message) { + client.get('nickname',function(error, name) { + storeMessage(name, message); + client.broadcast.emit('messages', name + ' : ' + message); + }); + }); + + /** + * When a user disconnects, get their name and broadcast they left + */ + + client.on('disconnect', function(name) { + client.get('nickname', function(error, name) { + client.broadcast.emit('remove chatter', name); + redisClient.srem('chatters', name); + }); + }); +}); diff --git a/index.html b/index.html new file mode 100644 index 0000000..7dd3b04 --- /dev/null +++ b/index.html @@ -0,0 +1,22 @@ + + + + chattr + + + +

chattr

+ + +
+ +
+
+ +
+ + + + + + diff --git a/lib/redisClient.js b/lib/redisClient.js new file mode 100644 index 0000000..734e6c5 --- /dev/null +++ b/lib/redisClient.js @@ -0,0 +1,8 @@ +var redis = require('redis'); +var redisClient = redis.createClient(); + +redisClient.on('error', function(err) { + console.log( 'error : ' + err ); +}); + +module.exports = redisClient; diff --git a/package.json b/package.json new file mode 100644 index 0000000..7ba0a40 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "chattr", + "description": "A simple chat server based on the Real Time Web with Node.js codeschool class.", + "version": "0.1.0", + "author": { + "name": "dan entous", + "email": "contact@pennlinepublishing.com" + }, + "contributors": [ + { + "name": "dan entous", + "email": "contact@pennlinepublishing.com" + } + ], + "dependencies": { + "redis": "^0.10.1", + "express": "^3.4.8", + "socket.io": "^0.9.16" + }, + "repository": { + "type": "git", + "url": "git://github.com/dan-nl/realtimewebnode.git" + }, + "scripts": { + "start": "node app.js" + }, + "readme": "# Real Time Web With Node.js\n\nThis is the code for the [Real Time Web with Node.js](http://www.codeschool.com/courses/real-time-web-with-nodejs) demo app.\n\n## redis\n\nYou’ll need to make sure you have a redis server running in order to use the application see the [Redis Quick Start](http://redis.io/topics/quickstart) for information on installing redis.\n\n## Install\n\nThe fastest way to get __chattr__ up and running:\n\n * On Mac or Linux, make sure you have [nodejs][nodejs] and [npm][npm] installed\n\n```\ngit clone git@github.com:dan-nl/realtimewebnode.git\ncd realtimewebnode\nnpm install\nnpm start\n```", + "readmeFilename": "Readme.md", + "_id": "chattr@0.1.0", + "_from": "chattr@" +} diff --git a/public/app.css b/public/app.css new file mode 100644 index 0000000..5f58f7d --- /dev/null +++ b/public/app.css @@ -0,0 +1,23 @@ +body { font-family: sans-serif; color: #333; } + +#chatters { width: 100px; margin: 0 1em 0 0; float: left; list-style: none; } + +#chatters, #chat-console { height: 300px; overflow: auto; padding: 1%;} + +#chat-console, #chatters, #chat-form input[type=text] { border: 1px solid #ccc; } + +#chat-console, #chat-form { margin-right: 7%; } + +#chat-form { margin-top: 1em; text-align: right; clear: both; } + +#chat-form input[type=text] { width: 80%; font-size: 120%; padding: 1%; } + +.connected { color: #8b0000; } + +/* http://hellohappy.org/css3-buttons/ */ +input[type=submit] { background-color: #eeeeee; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #eeeeee), color-stop(100%, #cccccc)); background-image: -webkit-linear-gradient(top, #eeeeee, #cccccc); background-image: -moz-linear-gradient(top, #eeeeee, #cccccc); background-image: -ms-linear-gradient(top, #eeeeee, #cccccc); background-image: -o-linear-gradient(top, #eeeeee, #cccccc); background-image: linear-gradient(top, #eeeeee, #cccccc); border: 1px solid #ccc; border-bottom: 1px solid #bbb; border-radius: 3px; color: #333; font-size: 100%; padding: 8px 0; text-align: center; text-shadow: 0 1px 0 #eee; width: 150px; } + +input[type=submit]:hover { background-color: #dddddd; background-image: -webkit-gradient(linear, left top, left bottom, color-stop(0%, #dddddd), color-stop(100%, #bbbbbb)); background-image: -webkit-linear-gradient(top, #dddddd, #bbbbbb); background-image: -moz-linear-gradient(top, #dddddd, #bbbbbb); background-image: -ms-linear-gradient(top, #dddddd, #bbbbbb); background-image: -o-linear-gradient(top, #dddddd, #bbbbbb); background-image: linear-gradient(top, #dddddd, #bbbbbb); border: 1px solid #bbb; border-bottom: 1px solid #999; cursor: pointer; text-shadow: 0 1px 0 #ddd; } + +input[type=submit]:active { border: 1px solid #aaa; border-bottom: 1px solid #888; -webkit-box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; box-shadow: inset 0 0 5px 2px #aaaaaa, 0 1px 0 0 #eeeeee; } + diff --git a/public/app.js b/public/app.js new file mode 100644 index 0000000..ed39a18 --- /dev/null +++ b/public/app.js @@ -0,0 +1,56 @@ +;(function(){ + + var socket = io.connect(); + var chatters = document.getElementById('chatters'); + var chat_input = document.getElementById('chat-input'); + var chat_console = document.getElementById('chat-console'); + var nickname; + + function removeChatter(name) { + var current_chatters = document.querySelectorAll('[data-name]'), + i; + + for (var i = 0; i < current_chatters.length; i += 1) { + if (name === current_chatters[i].getAttribute('data-name')) { + current_chatters[i].parentNode.removeChild(current_chatters[i]); + break; + }; + }; + }; + + function insertChatter(name) { + var new_chatter = document.createElement('li'); + new_chatter.setAttribute('data-name', name); + new_chatter.setAttribute('class', 'connected'); + new_chatter.innerHTML = name; + chatters.appendChild(new_chatter); + }; + + function insertMessage(message) { + var new_message = document.createElement('span'); + new_message.innerHTML = message + '
'; + chat_console.appendChild(new_message); + }; + + document.getElementById('chat-form').onsubmit = function(e) { + e.preventDefault(); + socket.emit('messages', chat_input.value); + insertMessage(nickname + ' : ' + chat_input.value); + chat_input.value = null; + }; + + socket.on('messages', function(data) { + insertMessage(data); + }); + + socket.on('connect', function(data) { + chat_console.innerHTML = 'connected to the chat socket
'; + nickname = prompt('what is your nickanme?'); + socket.emit('join', nickname); + }); + + socket.on('add chatter', insertChatter); + socket.on('remove chatter', removeChatter); + +}()); +