2 * bcserver.js; ruml; May 6, 2011;
3 * based on example from socket.io.node repo: server.js
7 * FLASH: Firefox 4 ships with websockets enables but unavailable because
8 * of a security firewall; use "about:config" to set
9 * network.websocket.override-security-block to true;
10 * Safari 5.0.5 works out of the box;
12 * NOW: some transport other than websocket:
13 * - client disconnects after about 15 seconds;
14 * - connecting with flashsocket"; there's an error in socket.io.js:
15 * self.__flash.getReadyState is not a function line 1651
19 // messages are objects with one property whose value is the
21 // { "post": { "objtype": "post", <other Post properties> } }
22 // { "topPosts": [ <array of Post objects> ] }
23 // { "recentPosts": [ <array of Post objects> ] }
24 // { "vote": { "objtype": "vote", "postid": <string>, "direction": <"up"|"down"> } }
25 // unique postid assigned at the server when post arrives; format: <meetingid>-<serial-starting-at-1>;
26 // although voting changes the vote total displayed locally, this is cosmetic only;
29 http = require('http'),
31 // var qstr = require('querystring');
33 util = require('util'),
34 // require.path.unshift('./lib');
35 io = require('socket.io'),
36 // path is used by paperboy;
37 path = require('path'),
38 paperboy = require('./lib/paperboy'),
39 // could be used for uniqueId(); probably should be part of any project;
40 _ = require('./lib/underscore'),
42 GLOBALS = { "meetingID": "888", "nextPostID": 1, "username": "notyetset" },
44 STATIC = path.join(path.dirname(__filename), 'static');
46 var server = http.createServer(function(request, response){
47 var ip = request.connection.remoteAddress;
48 var pathname = url.parse(request.url).pathname;
49 var query = url.parse(request.url, true).query;
53 response.writeHead(200, {'Content-Type': 'text/html'});
55 '<h1>Welcome to the backchan.nl clone on node.js</h1>' +
56 '<div style="margin: 3em auto; padding: 4em; ' +
57 ' border: 3px solid blue; ' +
58 ' width: 25em; text-align: center;">' +
59 // we actually might prefer POST but I don't know how to extract params;
60 '<form action="/bcmodule.html" method="get">' +
61 ' <label for="username">User handle: </label>' +
62 ' <input type="text" id="username" value="bruml2" /><br /><br />' +
63 ' <input type="submit" value="Send" />' +
71 /* Bypassing initial form
72 if (pathname == '/bcmodule.html') {
73 if (typeof(GLOBALS['username']) != 'undefined') {
74 GLOBALS['username'] = query['username'];
75 console.log('Captured username: "' + GLOBALS['username'] + '"');
77 console.log('NO USERNAME found');
82 .deliver(STATIC, request, response)
83 // second arg is milliseconds!!
84 .addHeader('Expires', 300)
85 .addHeader('X-PaperRoute', 'Node')
87 // can cancel delivery by returning false;
88 // console.log('Received Request');
90 .after(function(statCode) {
91 log(statCode, request.url, ip);
93 .error(function(statCode, msg) {
94 response.writeHead(statCode, {'Content-Type': 'text/plain'});
95 response.end("Error " + statCode);
96 log(statCode, request.url, ip, msg);
98 .otherwise(function(err) {
99 response.writeHead(404, {'Content-Type': 'text/plain'});
100 response.end('404 - file "' + pathname + '" not found on chat server');
101 log(404, request.url, ip, err);
104 }); // http.createServer();
107 function log(statCode, url, ip, err) {
108 var logStr = statCode + ' - ' + url + ' - ' + ip;
110 logStr += ' - ' + err;
115 console.log("==============================");
116 console.log("Server listening on port " + PORT + "!");
118 // crete socket to listen to socket.io traffic on same port as http traffic;
119 var socket = io.listen(server);
121 function getNextUniquePostID() {
122 var nextID = GLOBALS["nextPostID"];
123 GLOBALS["nextPostID"] += 1;
124 return GLOBALS["meetingID"] + "-" + nextID;
126 function sortPostsByVoteRankDescending(a, b) {
127 aRank = a.posvotes - (a.negvotes * 0.5);
128 bRank = b.posvotes - (b.negvotes * 0.5);
129 // for descending, reverse usual order;
130 return bRank - aRank;
132 function sortPostsByCreatedDescending(a, b) {
133 // for descending, reverse usual order;
134 return b.created - a.created;
136 function tallyVote(postid, direction) {
137 allPosts.forEach(function(el) {
138 if (el.postid == postid) {
140 case "up": el.posvotes += 1; break;
141 case "down": el.negvotes += 1; break;
142 default: console.log("Bad direction on vote: " + el.direction);
144 // terminate iteration here?
149 // temporary: for testing;
152 meetingid: GLOBALS["meetingID"],
154 username: "Bill Jones",
155 useraffil: "student",
156 postid: getNextUniquePostID(),
157 body: "This is dummy post one: 1111111.",
163 created: (new Date).getTime()
166 meetingid: GLOBALS["meetingID"],
168 username: "George Smith",
169 useraffil: "student",
170 postid: getNextUniquePostID(),
171 body: "This is dummy post two: 2222222.",
177 created: (new Date).getTime() + 10000
179 // allPosts collects all the messages since the start of the server; when a
180 // new client connects, the buffer is sent immediately (it's the current state).
181 // would need to be per school, per class and per lecture;
183 // prime the pump a little;
184 allPosts = dummyPosts;
186 socket.on('connection', function(client){
187 // ====> we're in the connection callback: executes once;
188 // NB: when the sorting occurred outside the callback, it was not reflected in
189 // the actual order sent; this way works! [Why is this?]
190 console.log("======== sending topPosts and recentPosts on connection event =======");
191 var topPosts = allPosts.sort(sortPostsByVoteRankDescending);
192 console.log("Top Posts:");
193 topPosts.forEach(function(el) {
194 console.log(el.postid + ": " + el.posvotes + "/" + el.negvotes + " - " + el.created)
196 client.json.send( { posts: allPosts } );
198 var recentPosts = allPosts.sort(sortPostsByCreatedDescending);
199 console.log("Recent Posts:");
200 recentPosts.forEach(function(el) { console.log(el.postid + " - " + el.posvotes + " - " + el.created) });
201 //client.send( { "recentPosts": recentPosts } );
203 console.log("Sent " + topPosts.length + " accumulated topPosts.");
204 console.log("Sent " + recentPosts.length + " accumulated recentPosts.");
205 console.log("======== done =======");
206 client.broadcast({ "announcement": 'Hey! ' + client.sessionId + ' connected' });
208 // create callbacks for this client;
210 client.on('message', function(message, fn){
211 if ("post" in message) {
212 message.post.postid = getNextUniquePostID();
213 console.log("POST " + message.postid + " received from client: " + client.sessionId);
214 allPosts.push(message.post);
215 console.log("Now have " + allPosts.length + " posts in all");
216 fn(message.post.postid);
217 var topPosts = allPosts.sort(sortPostsByVoteRankDescending);
218 client.broadcast( { "topPosts": topPosts } );
219 client.send( { "topPosts": topPosts } );
220 var recentPosts = allPosts.sort(sortPostsByCreatedDescending);
221 client.broadcast( { "recentPosts": recentPosts } );
222 client.send( { "recentPosts": recentPosts } );
223 } else if ("vote" in message) {
224 console.log("VOTE received from client: " + client.sessionId);
225 console.log(message);
226 tallyVote(message.vote.postid, message.vote.direction);
227 // only the order of topPosts might change; could check whether it
228 // has before sending traffic; could send a vote message to clients
229 // and display could be changed locally;
230 var topPosts = allPosts.sort(sortPostsByVoteRankDescending);
231 client.broadcast( { "topPosts": topPosts } );
232 client.send( { "topPosts": topPosts } );
233 var recentPosts = allPosts.sort(sortPostsByCreatedDescending);
234 client.broadcast( { "recentPosts": recentPosts } );
235 client.send( { "recentPosts": recentPosts } );
237 console.log("UNKNOWN message type (" + message + "); client: " + client.sessionId);
241 client.on('post', function(msg) {
243 post.postid = getNextUniquePostID();
244 console.log("POST " + post.postid + " received from client: " + client.sessionId);
246 console.log("Now have " + allPosts.length + " posts in all");
247 socket.emit('post', post);
249 client.on('vote', function(msg) {
250 console.log("VOTE received from client: " + client.sessionId);
251 console.log(message);
252 tallyVote(msg.vote.postid, msg.vote.direction);
253 socket.emit('vote', msg.vote);
255 client.on('disconnect', function(){
256 console.log("Client: " + client.sessionId + " disconnected");
257 client.broadcast({ announcement: client.sessionId + ' disconnected' });
259 }); // connection callback;