diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..93f1361 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +npm-debug.log diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..e1d4131 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: node app.js diff --git a/README.md b/README.md index cbba4e2..586f35e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,25 @@ -# Real Time Web With Node.js +# RealTime 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 is bases on the [Real Time Web with Node.js][realtime] demo app, __chattr__. + +## redis + +You’ll need to make sure you have a redis server running in order to use chattr. See the [Redis Quick Start][redis] for information on installing redis. + +## install + +The fastest way to get __chattr__ up and running: + + * Make sure you have [nodejs][nodejs] and [npm][npm] installed + +``` +git clone git@github.com:dan-nl/realtimewebnode.git +cd realtimewebnode +npm install +npm start +``` + +[nodejs]: http://nodejs.org/ +[npm]: http://npmjs.org/ +[redis]: http://redis.io/topics/quickstart +[realtime]: http://www.codeschool.com/courses/real-time-web-with-nodejs diff --git a/app.js b/app.js new file mode 100644 index 0000000..4d393d8 --- /dev/null +++ b/app.js @@ -0,0 +1,109 @@ +/*jslint node: true, nomen: true, unparam: true, white: true */ +(function() { + + 'use strict'; + + var redisToGo, + redis, + redisClient, + storeMessage, + express = require( 'express' ), + app = express(), + http = require( 'http' ), + server = http.createServer( app ), + io = require( 'socket.io' ).listen( server ), + path = require( 'path' ), + port = Number( process.env.PORT || 8080 ), + max_messages = 100; + + server.listen( port, function() { + console.log( "Listening on " + port ); + }); + + app.use( express.static( path.join( __dirname, 'public' ) ) ); + + app.get('/', function ( req, res ) { + res.sendfile( __dirname + '/index.html' ); + }); + + if ( process.env.REDISTOGO_URL ) { + redisToGo = require( 'url' ).parse( process.env.REDISTOGO_URL ); + redis = require( 'redis' ); + redisClient = redis.createClient( redisToGo.port, redisToGo.hostname ); + redisClient.auth( redisToGo.auth.split( ':' )[1] ); + } else { + redis = require( 'redis' ); + redisClient = redis.createClient(); + } + + redisClient.ping( function( reply ) { + if ( reply !== null && reply.indexOf( 'ECONNREFUSED' ) > -1 ) { + console.log( 'error: cannot connect with the redis server' ); + process.exit( 'error: cannot connect with the redis server' ); + } + }); + + redisClient.on( 'error', function( err ) { + console.log( 'error : ' + err ); + }); + + storeMessage = function( name, data ) { + var message = JSON.stringify({ + name:name, + data:data + }); + + // store up to max_messages + redisClient.lpush( 'messages', message, function( error, response ) { + redisClient.ltrim( 'messages', 0, max_messages ); + }); + }; + + io.sockets.on( 'connection', function ( client ) { + client.on( 'join', function( name ) { + client.set( 'nickname', name ); + client.broadcast.emit( 'add chatter', name ); // tell other chatters about this new chatter + redisClient.sadd( 'chatters', name ); // add the new chatter to the redis chatter set + + // 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 ); + }); + }); + }); + + client.on( 'messages', function( message ) { + client.get( 'nickname', function( error, name ) { + if ( message.indexOf( 'udacity' ) > -1 ) { + client.broadcast.emit( 'messages', name + ' : ' + '

Education is no longer a one-time event but a lifelong experience. Education should be less passive listening (no long lectures) and more active doing. Education should empower students to succeed not just in school but in life.

We are reinventing education for the 21st century by bridging the gap between real-world skills, relevant education, and employment. Our students will be fluent in new technology, modern mathematics, science, and critical thinking. They will marry skills with creativity and humanity to learn, think, and do. Udacians are curious and engaged world citizens.

Interested in working with us? View our openings.

' ); + } else { + storeMessage( name, message ); + client.broadcast.emit( 'messages', name + ' : ' + message ); + } + }); + }); + + client.on( 'disconnect', function( name ) { + client.get( 'nickname', function( error, name ) { + client.broadcast.emit( 'remove chatter', name ); + redisClient.srem( 'chatters', name ); + }); + }); + + client.on( 'flushall', function() { + redisClient.flushall(); + }); + }); + +}()); diff --git a/package.json b/package.json new file mode 100644 index 0000000..9419633 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "chattr", + "version": "0.1.0", + "description": "A simple chat server based on the Real Time Web with Node.js codeschool class.", + "author": { + "name": "dan entous", + "email": "contact@pennlinepublishing.com" + }, + "repository": { + "type": "git", + "url": "https://github.com/dan-nl/realtimewebnode.git" + }, + "homepage": "https://github.com/dan-nl/realtimewebnode", + "license": "GPLv3", + "keywords": [ + "codeschool", + "websockets", + "chat" + ], + "main": "app.js", + "scripts": { + "start": "node app.js" + }, + "engines": { + "node": "0.10.26" + }, + "dependencies": { + "express": "3.1.0", + "redis": "0.8.2", + "socket.io": "0.9.14" + }, + "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" +} diff --git a/public/css/css.css b/public/css/css.css new file mode 100644 index 0000000..e367f7a --- /dev/null +++ b/public/css/css.css @@ -0,0 +1,93 @@ +html { + font-family: sans-serif; + font-size: 16px; +} + +body { + font-size: 100%; + color: #333; + padding: 3%; +} + +h1 { + margin-top: 0; +} + +#chatters { + width: 24%; + max-width: 100px; + margin: 0 2% 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-form { + margin-top: 1em; + text-align: right; + clear: both; +} + +#chat-form input[type = text] { + width: 98%; + font-size: 120%; + padding: 1%; + margin: 0; +} + +.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; + margin: 1% 0 0 0; + color: #333; + font-size: 100%; + padding: 7px 18px; + text-align: center; + text-shadow: 0 1px 0 #eee; +} + +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/index.html b/public/index.html new file mode 100644 index 0000000..8819270 --- /dev/null +++ b/public/index.html @@ -0,0 +1,23 @@ + + + + + +chattr + + + +

chattr

+ +
+ +
+
+ +
+ + + + + + diff --git a/public/js/js.js b/public/js/js.js new file mode 100644 index 0000000..bfd37e6 --- /dev/null +++ b/public/js/js.js @@ -0,0 +1,86 @@ +/*global console, io, prompt */ +/*jslint browser: true, white: true */ +(function() { + + 'use strict'; + + + if ( window.io === undefined ) { + console.log( 'oops ... socket.io did’t load' ); + return; + } + + + var chattr = { + + nickname: undefined, + server: io.connect( + '//' + + window.location.hostname + + ( window.location.port ? ':' + window.location.port : '' ) + ), + chatters: document.getElementById( 'chatters' ), + chat_form: document.getElementById( 'chat-form' ), + chat_input: document.getElementById( 'chat-input' ), + chat_console: document.getElementById( 'chat-console' ), + + + handleFormSubmit: function( evt ) { + evt.preventDefault(); + chattr.server.emit( 'messages', chattr.chat_input.value ); + chattr.insertMessage( chattr.nickname + ' : ' + chattr.chat_input.value ); + + if ( chattr.chat_input.value === 'flushall' ) { + chattr.server.emit( 'flushall' ); + } + + chattr.chat_input.value = null; + }, + + handleServerConnect: function() { + chattr.chat_console.innerHTML = 'connected to the chat server
'; + chattr.nickname = prompt( 'what is your nickanme?' ); + chattr.server.emit( 'join', chattr.nickname ); + }, + + init: function() { + chattr.chat_form.addEventListener( 'submit', chattr.handleFormSubmit, false ); + chattr.server.on( 'messages', chattr.insertMessage ); + chattr.server.on( 'connect', chattr.handleServerConnect ); + chattr.server.on( 'add chatter', chattr.insertChatter ); + chattr.server.on( 'remove chatter', chattr.removeChatter ); + }, + + insertChatter: function( name ) { + var new_chatter = document.createElement('li'); + new_chatter.setAttribute( 'data-name', name ); + new_chatter.setAttribute( 'class', 'connected' ); + new_chatter.innerHTML = name; + chattr.chatters.appendChild( new_chatter ); + }, + + insertMessage: function( message ) { + var new_message = document.createElement('span'); + new_message.innerHTML = message + '
'; + chattr.chat_console.appendChild( new_message ); + chattr.chat_console.scrollTop = chattr.chat_console.scrollHeight; + }, + + removeChatter: function( name ) { + var i, + current_chatters = document.querySelectorAll( '[data-name]' ), + ii = current_chatters.length; + + for ( i = 0; i < ii; i += 1 ) { + if ( name === current_chatters[i].getAttribute( 'data-name' ) ) { + current_chatters[i].parentNode.removeChild( current_chatters[i] ); + break; + } + } + } + + }; + + chattr.init(); + +}());