/
chat.js
399 lines (357 loc) · 11.5 KB
/
chat.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
/**************************************************/
/*
/* Node.js Web chat application by Jonas Hartweg
/*
/* Background: This application has been created
/* as part of a Bachelor project in Spring 2012
/* that compares Node.js to the Apache web server
/*
/**************************************************/
// http://ejohn.org/blog/ecmascript-5-strict-mode-json-and-more/
"use strict";
var webServerPort = 3000;
/**
* Webserver
*/
var express = require('express'), /*routes = require('./routes'),*/ http = require('http'), webSocketServer = require('websocket').server, lessMiddleware = require('less-middleware'), MongoStore = require('connect-mongodb'), async = require('async'), flash = require('connect-flash');
var chat = express();
// Data providers
var UserProvider = require('./data_providers/userprovider-memory').UserProvider;
var userProvider = new UserProvider('localhost', 27017);
var MessageProvider = require('./data_providers/messageprovider-memory').MessageProvider;
var messageProvider = new MessageProvider('localhost', 27017);
var SessionProvider = require('./data_providers/sessionprovider-memory').SessionProvider;
var sessionProvider = new SessionProvider('localhost', 27017);
chat.configure(function() {
chat.set('views', __dirname + '/views');
chat.set('view engine', 'jade');
chat.use(express.favicon());
chat.use(express.logger('dev'));
chat.use(express.static(__dirname + '/public'));
chat.use(express.bodyParser());
chat.use(express.cookieParser("narwhal"));
chat.use(express.session({
secret : "narwhal",
store : new MongoStore({
url : "mongodb://localhost/chat"
})
}));
chat.use(express.methodOverride());
chat.use(flash());
chat.use(chat.router);
chat.use(lessMiddleware({
src : __dirname + '/public',
compress : true
}));
});
chat.configure('development', function() {
chat.use(express.errorHandler({
dumpExceptions : true,
showStack : true
}));
});
chat.configure('production', function() {
chat.use(express.errorHandler());
});
function loadUser(req, res, next) {
if(req.session.userId) {//Session is set
userProvider.findById(req.session.userId, function(error, user) {
if(user) {// If user to the user_id exists
next();
} else {
res.redirect('/sessions/new');
}
});
} else {
res.redirect('/sessions/new');
}
}
chat.get('/', loadUser, function(req, res) {
userProvider.findContacts(req.session.userId, function(error, contacts) {
res.render('index.jade', {
title : 'Chat',
userId : req.session.userId,
username : req.session.userName,
contacts : contacts
});
});
});
// Sessions
chat.get('/sessions/new', function(req, res) {
console.log(req.flash('error').toString());
res.render('sessions/new.jade', {
title : 'Chat - Login',
flashMessage : req.flash('error').toString()
});
});
chat.post('/sessions', function(req, res) {
// Find the user and set the currentUser session variable
userProvider.findByUsername(req.body.username, function(error, user) {
if(user && user.password == req.body.password) {
console.log("UserId: " + user._id + " UserId: " + user.username + " connected");
req.session.userId = user._id;
req.session.userName = user.username;
res.redirect('/');
} else {
console.log("Wrong credentials");
req.flash('error', 'Incorrect credentials');
res.redirect('/sessions/new');
}
});
});
chat.get('/logout', loadUser, function(req, res) {
// Remove the session
if(req.session) {
req.session.destroy(function() {
});
}
res.redirect('/sessions/new');
});
// Helpers
flash.dynamicHelpers = {
flashMessages : function(req, res) {
var html = '';
['error', 'info'].forEach(function(type) {
var messages = req.flash(type);
if(messages.length > 0) {
html += new FlashMessage(type, messages).toHTML();
html += '<div class"flash ' + type + '"><p>' + messages.join(',') + '</p></div>'
}
});
return html;
}
};
// WebSocket server
// Partly: http://gist.github.com/2031681
var webserver = http.createServer(chat).listen(webServerPort, function() {
console.log("Webserver is listening on port " + webServerPort);
});
// Port of the WebSocket server
var webSocketsServerPort = 8000;
// Helper function for escaping input strings
function htmlEntities(str) {
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
}
// HTTP server
var websocketHTTPserver = http.createServer().listen(webSocketsServerPort, function() {
console.log("WebSocketsServer is listening on port " + webSocketsServerPort);
});
// The WebSocket server is bound to HTTP server
var wsServer = new webSocketServer({
httpServer : websocketHTTPserver
});
// Extracts the sid from the cookies
function getSidFromCookies(cookies) {
var filtered = cookies.filter(function(obj) {
return obj.name == 'connect.sid';
});
return filtered.length > 0 ? unescape(filtered[0].value).substr(0, 24) : null;
}
// Keeps all open WebSocket connections
var connections = {};
// This callback function is called every time someone
// tries to connect to the WebSocket server
wsServer.on('request', function(req) {
console.log((new Date()) + ' Connection from origin ' + req.origin + '.');
var userId = null;
if(req.origin == "http://localhost:3000") {
var connection = req.accept(null, req.origin);
console.log((new Date()) + ' Connection accepted.');
} else {
console.log("Connection from " + req.origin + " not accepted");
return;
}
console.log("sessionId: " + getSidFromCookies(req.cookies));
sessionProvider.findById(getSidFromCookies(req.cookies), function(error, session) {
if(error || session == null) {
connection.close();
console.log((new Date()) + ' Connection dropped.');
} else {
var session_parsed = JSON.parse(session.session);
userId = session_parsed.userId;
connection.id = userId;
connections[userId] = connection;
// Send online status of contacts
userProvider.findContacts(userId, function(error, contacts) {
async.forEach(contacts, function(contact, callbackFE) {
if(connections[contact._id] != undefined) {
// Get availability of other contacts
var outgoing = {
type : 'availability',
contactId : contact._id,
available : true
};
console.log("Send availability: " + JSON.stringify(outgoing));
connection.sendUTF(JSON.stringify(outgoing));
// Send own availability to other contacts
var outgoing2 = {
type : 'availability',
contactId : userId,
available : true
};
console.log("Broadcast availability: " + JSON.stringify(outgoing2));
connections[contact._id].sendUTF(JSON.stringify(outgoing2));
}
// Get unread messages
messageProvider.getUnreadMessages(userId, contact._id, function(error, unreadMessages) {
if(error) {
console.log("Error");
callbackFE(error);
} else {
async.forEach(unreadMessages, function(message, callbackFE2) {
var obj = {
time : (new Date(message.time)).getTime(),
text : htmlEntities(message.text),
sender : contact._id,
receiver : userId
};
// Online, send message
var outgoing = {
type : 'message',
data : obj
};
connection.sendUTF(JSON.stringify(outgoing));
callbackFE2();
}, function() {
callbackFE();
})
}
});
})
});
}
})
// User sent some message
connection.on('message', function(message) {
if(message.type === 'utf8') {// accept only text
try {
var incoming = JSON.parse(message.utf8Data);
} catch (e) {
console.log('This doesn\'t look like a valid JSON: ', message.utf8Data);
return;
}
if(incoming.type == 'message') {
console.log((new Date()) + ' Received Message from ' + userId + ' to ' + incoming.receiver + ': ' + htmlEntities(incoming.text));
// send the message to the other client, if available
if(connections[incoming.receiver] != undefined) {
console.log("User online, redirected");
var obj = {
time : (new Date()).getTime(),
text : htmlEntities(incoming.text),
sender : userId,
receiver : incoming.receiver,
delivered : true
};
// Online, send message
var outgoing = {
type : 'message',
data : obj
};
connections[incoming.receiver].sendUTF(JSON.stringify(outgoing));
// store message
messageProvider.save(obj, function(error, messages) {
});
} else {
console.log("User offline, not redirected");
// Offline, only store message
var obj = {
time : (new Date()).getTime(),
text : htmlEntities(incoming.text),
sender : userId,
receiver : incoming.receiver,
delivered : false
};
messageProvider.save(obj, function(error, messages) {
});
}
} else if(incoming.type == 'historyRequest') {
// send back chat history
messageProvider.getConversation(userId, incoming.contactId, function(error, history) {
if(error)
console.log(error);
else {
var outgoing = {
type : 'history',
contact : incoming.contactId,
data : history
}
console.log("Send history to " + userId);
connection.sendUTF(JSON.stringify(outgoing));
}
});
} else if(incoming.type == 'searchUsers') {
userProvider.findUsers(incoming.username, function(error, users) {
if(error)
console.log(error);
else {
var outgoing = {
type : 'searchResult',
username : incoming.username,
data : users
}
console.log("Send searchresult to " + userId);
connection.sendUTF(JSON.stringify(outgoing));
}
})
} else if(incoming.type == 'addContact') {
// Add contact to contact list of user
userProvider.addContact(userId, incoming.contactId, function(error, contact) {
if(error) {
console.log(error);
} else {
var contactOnline = false;
// Check if contact online, so the status can send directly - saves communication later
if(connections[incoming.contactId] != undefined)
contactOnline = true;
var outgoing = {
type : 'newContact',
availability : contactOnline,
contact : contact
}
connection.sendUTF(JSON.stringify(outgoing));
console.log("Send new contact to " + userId);
}
});
// Add user to contact list of contact
userProvider.addContact(incoming.contactId, userId, function(error, contact) {
if(error) {
console.log(error);
} else {
if(connections[incoming.contactId] != undefined) {// If online
var outgoing = {
type : 'newContact',
availability : true,
contact : contact
}
console.log("Send new contact to " + incoming.contactId);
connections[incoming.contactId].sendUTF(JSON.stringify(outgoing));
}
}
});
} else {
console.log('Unknown JSON message type: ' + JSON.stringify(incoming));
}
}
});
// User disconnected
connection.on('close', function(connection) {
console.log((new Date()) + " Peer " + userId + " disconnected.");
delete connections[userId];
// Send online status of contacts
userProvider.findContacts(userId, function(error, contacts) {
async.forEach(contacts, function(contact, callbackFE) {
if(connections[contact._id] != undefined) {
// Send own availability to other contacts
var outgoing = {
type : 'availability',
contactId : userId,
available : false
};
console.log("Broadcast availability: " + JSON.stringify(outgoing));
connections[contact._id].sendUTF(JSON.stringify(outgoing));
callbackFE(error);
}
})
});
});
});