Coverage

95%
595
570
25

/home/lesterpig/workspace/openparty/lib/attrs.js

100%
1
1
0
LineHitsSource
11module.exports = {
2
3 name : {m: true, t: 'String'},
4 start : {m: true, t: 'Function'},
5 minPlayers : {m: true, t: 'Integer'},
6 maxPlayers : {m: true, t: 'Integer'},
7
8 init : {t: 'Function'},
9 firstStage : {t: 'String'},
10 parameters : {t: 'Array'},
11 stages : {t: 'Object'},
12 processMessage : {t: 'Function'},
13 onDisconnect : {t: 'Function'},
14 onReconnect : {t: 'Function'},
15 version : {t: 'String'},
16 description : {t: 'String'},
17 opVersion : {t: 'String'},
18 css : {t: 'Array'},
19 sounds : {t: 'Array'},
20 reconnectDelay : {t: 'Integer'},
21
22};
23

/home/lesterpig/workspace/openparty/lib/crypto.js

100%
13
13
0
LineHitsSource
11var openpgp = require('openpgp');
21var uuid = require('node-uuid');
3
41module.exports = {
5
6 generateChallenge: function() {
72018 return uuid.v4();
8 },
9
10 verifyChallenge: function(challenge, key, message, callback) {
11
125 var m, k;
135 try {
145 k = openpgp.key.readArmored(key).keys[0];
155 m = openpgp.cleartext.readArmored(message);
16 } catch(e) {
171 return callback(e, { success: false });
18 }
19
204 openpgp.verify({
21 publicKeys: k,
22 message: m,
23 })
24 .then(function(r) {
25
263 var rawUsername = k.users[0].userId.userid;
27
283 callback(null, {
29 success: challenge === r.data,
30 username: rawUsername.substr(0, rawUsername.length - 3), // remove ugly ' <>' added by openpgp
31 fingerprint: k.primaryKey.fingerprint,
32 });
33
34 })
352 .catch(function(e) { callback(e, { success: false }); });
36 },
37
38};
39
40/*
41console.log(module.exports.generateChallenge());
42
43
44var options = {
45 userIds: { name:'My Username', email:'' },
46 numBits: 1024,
47 unlocked: true,
48};
49
50var k;
51var p;
52var challenge = 'Random challenge';
53
54openpgp.generateKey(options)
55.then(function(key) {
56
57 p = key.publicKeyArmored;
58 console.log(p);
59 k = key;
60 return openpgp.sign({data: challenge, privateKeys: k.key});
61
62})
63.then(function(message) {
64
65 var key = openpgp.key.readArmored(p).keys;
66 console.log(message.data);
67 message = openpgp.cleartext.readArmored(message.data);
68 return openpgp.verify({publicKeys: key, message: message});
69
70})
71.then(function(result) {
72
73 var key = openpgp.key.readArmored(p).keys;
74 console.log('Fingerprint:', key[0].primaryKey.fingerprint);
75
76})
77.catch(function(e) {
78 console.log(e);
79});
80
81*/
82

/home/lesterpig/workspace/openparty/lib/loader.js

100%
74
74
0
LineHitsSource
11var path = require('path');
21var fs = require('fs');
31var sem = require('semver');
41var utils = require('./utils');
51var attrs = require('./attrs');
61require('colors');
7
81module.exports = function(basePath, callback) {
9
108 if(!basePath)
111 basePath = path.join(__dirname, '..', 'data');
128 if(!callback)
131 callback = function(){};
14
158 var gametypes = {};
16
178 fs.readdir(basePath, function(err, files) {
18
198 if(err) {
201 return callback( '!! Unable to load game definitions\n'.red +
21 ' Install gameplay definitions in data/ subdirectory\n'.red +
22 (' Was: ' + err).red, null);
23 }
24
257 var ok = false;
26
277 files.forEach(function(filePath) {
2832 if(fs.statSync(path.join(basePath, filePath)).isDirectory()) {
29
3021 var modulePath = path.join(basePath, filePath, 'definition.js');
3121 if(!fs.existsSync(modulePath))
3210 return;
33
3411 var plugin;
3511 if((plugin = checkFile(modulePath))) {
365 ok = true;
375 gametypes[filePath] = plugin;
38
39 // Has public files ?
405 var publicPath = path.join(basePath, filePath, 'public');
415 if(fs.existsSync(publicPath)) {
425 __app.use('/' + filePath, __app.express.static(publicPath));
43 }
44 }
45 }
46 });
47
487 if(!ok) {
492 return callback('!! No gameplay definition was correct. Aborting.'.red, null);
50 }
51
525 callback(null, gametypes);
53
54 });
55};
56
571var checkFile = function(path) {
58
5911 var errorLog = [];
60
6111 try {
6211 var plugin = require(path);
6310 var instance = new plugin();
64
659 checkMissingAttributes(instance, errorLog);
669 checkWrongAttributes(instance, errorLog);
67
689 if(errorLog.length === 1) {
692 throw new Error('One semantic failure');
707 } else if(errorLog.length > 1) {
712 throw new Error(errorLog.length + ' semantic failures');
72 }
73
74 // Everything is ok!
755 return plugin;
76 } catch(e) {
776 console.error(('~~ Cannot load "' + path + '"\n ' + e).yellow);
786 errorLog.forEach(function(a) {
7910 console.error((' - ' + a).gray.italic);
80 });
816 return null;
82 }
83};
84
851var checkMissingAttributes = function(instance, errorLog) {
86
879 for(var attr in attrs) {
88153 if(!attrs[attr].m) {
89117 continue;
90 }
91
9236 if(!instance[attr]) {
934 errorLog.push('Missing "' + attr + '" mandatory attribute (' + attrs[attr].t + ')');
94 }
95 }
96
97};
98
991var checkType = function(e, t) {
100
10182 var fn;
10282 switch(t) {
10340 case 'String': fn = utils.isString; break;
10454 case 'Function': fn = utils.isFunction; break;
10540 case 'Integer': fn = utils.isInteger; break;
10622 case 'Array': fn = utils.isArray; break;
1078 case 'Object': fn = utils.isObject; break;
108 }
10982 return fn(e);
110
111};
112
1131var checkWrongAttributes = function(instance, errorLog) {
114
1159 for(var attr in attrs) {
116153 if(instance[attr] && !checkType(instance[attr], attrs[attr].t)) {
1172 errorLog.push('"' + attr + '" must be of type (' + attrs[attr].t + ')');
118 }
119 }
120
1219 if(instance.maxPlayers <= 0) {
1221 errorLog.push('"maxPlayers" must be positive');
123 }
124
1259 if(instance.minPlayers <= 0) {
1261 errorLog.push('"minPlayers" must be positive');
127 }
128
1299 if(instance.maxPlayers < instance.minPlayers) {
1301 errorLog.push('"minPlayers" is greater than "maxPlayers"');
131 }
132
1339 if(instance.opVersion && !sem.satisfies(__version, instance.opVersion)) {
1341 errorLog.push('This module is not ready for this version of OpenParty (' + __version + ' does not satisfy ' + instance.opVersion + ')');
135 }
136};
137

/home/lesterpig/workspace/openparty/lib/players.js

100%
99
99
0
LineHitsSource
11var utils = require('./utils.js');
2
31var Player = function(socket, room) {
4
5 /**
6 * roles = {
7 * <name>: {channels: {}, actions: {}},
8 * ...
9 * }
10 *
11 */
128 this.roles = {};
13
148 this.channels = {};
158 this.actions = {};
16
178 this.socket = socket; // circular
188 this.room = room; // circular
198 this.username = socket.username;
20
21};
22
2326Player.prototype.join = function(channel) { this.socket.join('room_' + this.room.id + '_' + channel); };
246Player.prototype.leave = function(channel) { this.socket.leave('room_' + this.room.id + '_' + channel); };
25
261Player.prototype.setRole = function(role, value) {
275 if(!value && this.roles[role]) {
28 // Remove all registered channels
291 for(var channel in this.roles[role].channels) {
302 if(this.roles[role].channels[channel].r && !this.channels[channel]) {
312 this.leave(channel);
32 }
33 }
344 } else if(value) {
35 // Join all channels
363 for(var channel in value.channels) {
375 if(value.channels[channel].r) {
384 this.join(channel);
39 }
40 }
41 }
425 setAttributeObject(this, 'roles', role, value);
435 this.sendWriteChannels();
44};
45
461Player.prototype.setAction = function(name, value) {
473 setAttributeObject(this, 'actions', name, value);
48};
49
501Player.prototype.setChannel = function(name, rights, silent) {
51
5220 if(!rights) {
531 if(this.channels[name]) {
541 this.leave(name);
551 delete this.channels[name];
56 // Refreshing channels
571 for(var role in this.roles) {
581 for(var channel in this.roles[role].channels) {
592 if(channel === name && this.roles[role].channels[channel].r)
601 this.join(channel);
61 }
62 }
63 }
64 }
65
66 else {
6719 if(rights.r) {
6817 this.join(name);
69 }
70 else {
712 this.leave(name);
72 }
7319 this.channels[name] = rights;
74 }
7520 if(!silent) {
764 this.sendWriteChannels();
77 }
78};
79
801Player.prototype.getWriteChannels = function() {
8135 var channels = {};
82
8335 for(var role in this.roles) {
8423 for(var channel in this.roles[role].channels) {
8541 if(this.roles[role].channels[channel].w) {
8623 channels[channel] = this.roles[role].channels[channel];
87 }
88 }
89 }
90
9135 for(var channel in this.channels) { // override 'default' behavior
9282 if(this.channels[channel].w) {
9340 channels[channel] = this.channels[channel];
94 }
95 else {
9642 delete channels[channel];
97 }
98 }
99
10035 return channels;
101};
102
1031Player.prototype.sendWriteChannels = function() {
10418 return this.emit('setAllowedChannels', this.getWriteChannels());
105};
106
1071Player.prototype.getAvailableActions = function(clone) {
10836 var output = {};
109
110 // role actions
11136 for(var role in this.roles) {
11225 for(var action in this.roles[role].actions) {
11376 if(this.roles[role].actions[action].isAvailable(this)) {
11471 output[action] = this.roles[role].actions[action];
115 }
116 }
117 }
118
119 // personal actions (override)
12036 for(var action in this.actions) {
1216 if(this.actions[action].isAvailable && this.actions[action].isAvailable(this)) {
1225 output[action] = this.actions[action];
123 } else {
1241 delete output[action];
125 }
126 }
127
12836 for(var action in output) {
12975 if(clone) {
13023 output[action] = {
131 type: output[action].type,
132 options: output[action].options
133 };
134 }
135
13675 processActionOptions(output[action], this.room, this);
137 }
138
13936 return output;
140};
141
1421Player.prototype.sendAvailableActions = function() {
14321 return this.emit('setAvailableActions', this.getAvailableActions(true));
144};
145
1461Player.prototype.emit = function(event, data) {
14741 if(!this.disconnected) {
14840 this.socket.emit(event,data);
14940 return true;
150 } else {
1511 return false;
152 }
153};
154
1551Player.prototype.message = function(m) {
1561 return this.emit('chatMessage', {message: m});
157};
158
1591module.exports = {
160
161 init: function(room) {
1622 room.players.forEach(function(p) {
1638 p.player = new Player(p, room);
1648 p.player.setChannel('general', {r:true, w:true, n:'General'}, true);
1658 p.player.setChannel('player-' + p.player.username, {r: true, w: false}, true);
1668 p.player.sendWriteChannels();
167 });
168 },
169
170 Player: Player
171
172};
173
174/** PRIVATE FUNCTIONS **/
175
1761function setAttributeObject(p, attr, name, value) {
1778 if(value === null || value === undefined) {
1783 delete p[attr][name];
179 }
180
181 else {
1825 p[attr][name] = value;
183 }
184}
185
1861function processActionOptions(action, room, player) {
187
18875 switch(action.type) {
189
190 case 'select':
191
19253 if(!action.options.choices) {
1931 action.options.choices = 'players';
194 }
195
19653 if(utils.isString(action.options.choices)) {
197
19819 var out = [];
19919 room.players.forEach(function(e) {
20076 out.push(e.username);
201 });
20219 action.options.safeChoices = out;
203
20434 } else if(Array.isArray(action.options.choices)) {
20519 action.options.safeChoices = action.options.choices;
206 } else {
20715 action.options.safeChoices = action.options.choices(room, player);
208 }
209
21053 break;
211
212 default:
21322 break;
214 }
215
216}
217

/home/lesterpig/workspace/openparty/lib/recover.js

86%
36
31
5
LineHitsSource
11var rooms = require('./rooms');
2
3// Try to reconnect a socket to left room after unexpected disconnection
41module.exports = function(socket) {
512 if(!socket.session.identifier) {
60 return;
7 }
8
912 var found = false;
1012 for(var i = 0; i < rooms.rooms.length; i++) {
111 var room = rooms.rooms[i];
121 for(var j = 0; j < room.players.length; j++) {
132 var so = room.players[j];
142 if(so.player && so.player.disconnected
15 && so.session.identifier === socket.session.identifier
16 && so.session.username === socket.session.username) {
17
18 // Update the player
191 clearTimeout(so.player.disconnectionTimer);
201 so.player.disconnected = undefined;
211 socket.currentRoom = room;
221 socket.player = so.player;
231 socket.player.socket = socket; // Update circular reference
241 socket.player.username = socket.username;
251 room.players[j] = socket; // This is the magic part!
26
27 // Refresh current game state
281 socket.emit('roomJoined', room.getPublicInfo());
291 socket.emit('gameStarted');
301 socket.join('room_' + room.id);
311 socket.player.sendWriteChannels();
321 socket.player.sendAvailableActions();
33
34 // Subscribe to lost channels
35 /// Subscribe to personal channel
361 socket.player.join('player-' + socket.username);
37 /// Subscribe to role channels
381 for(var role in socket.player.roles) {
390 for(var channel in socket.player.roles[role].channels) {
400 if(socket.player.roles[role].channels[channel].r) {
410 socket.player.join(channel);
42 }
43 }
44 }
45
46 /// Subscribe or unsubscribe with personal channels (overrides default role behavior
471 for(var channel in socket.player.channels) {
482 if(socket.player.channels[channel].r) {
492 socket.player.join(channel);
50 } else {
510 socket.player.leave(channel);
52 }
53 }
54
55 // Inform player about the current situation
561 socket.player.message(__i18n({phrase: "You have been reconnected after a network issue. Sorry, some information may not have been retrieved.", locale: socket.session.locale}))
57
58 // Notify gameplay if needed
591 if(room.gameplay.onReconnect)
601 room.gameplay.onReconnect(room, socket.player);
61
621 break;
63 }
64 }
651 if(found) break;
66 }
67};
68

/home/lesterpig/workspace/openparty/lib/rooms.js

98%
206
202
4
LineHitsSource
11var utils = require('./utils.js');
21var players = require('./players.js');
31var Player = players.Player;
4
5
61var Room = function(name, password, gameplay) {
7
83 this.isRoom = true;
93 this.id = utils.randomString(20); // TODO make it better...
103 this.players = [];
113 this.name = name;
123 this.size = gameplay.minPlayers;
133 this.creationDate = new Date();
143 this.password = password;
15
163 this.gameplay = gameplay;
173 this.gameplay.room = this; // circular
18
19 // Stages
203 this.started = false;
213 this.timeout = null;
223 this.currentStage = null;
23
243 if(this.gameplay.init) {
253 this.gameplay.init(this);
26 }
27
28};
29
30/**
31 * Broadcast a message to several sockets
32 * @param {String} [channel]
33 * @param {String} event
34 * @param {*} [data]
35 */
361Room.prototype.broadcast = function(channel, event, data) {
37
3847 if(data === undefined && channel) {
3931 data = event;
4031 event = channel;
4131 channel = '';
4216 } else if(channel !== '') {
4312 channel = '_' + channel;
44 }
4547 __app.io.to('room_' + this.id + channel).emit(event, data);
46};
47
48/**
49 * Send more information about a player.
50 * Client-side, it will be displayed in players list.
51 *
52 * @param {String} [channel]
53 * @param {Object} player (socket or Player object)
54 * @param {String} value
55 */
561Room.prototype.playerInfo = function(channel, player, value) {
57
584 if(!value) {
592 if(channel instanceof Player) {
601 channel = channel.socket;
61 }
622 this.broadcast('playerInfo', {username: channel.username, value: player});
63 } else {
642 if(player instanceof Player) {
651 player = player.socket;
66 }
672 this.broadcast(channel, 'playerInfo', {username: player.username, value: value});
68 }
69};
70
71/**
72 * Send a chat message to players (system message)
73 * @param {String} [channel]
74 * @param {String} message
75 */
761Room.prototype.message = function(channel, message) {
776 if(!message) {
784 this.broadcast('chatMessage', {message:channel});
79 }
80 else {
812 this.broadcast(channel, 'chatMessage', {message:message});
82 }
83};
84
85/**
86 * Get public information about the room and its players
87 * @return {Object}
88 */
891Room.prototype.getPublicInfo = function() {
9036 var output = {};
91
9236 output.id = this.id;
9336 output.isRoom = true;
9436 output.name = this.name;
9536 output.players = [];
9636 output.size = this.size;
9736 output.started = this.started;
9836 output.password = !!this.password;
99
10036 for(var i = 0; i < this.players.length; i++) {
10175 output.players.push({
102 username: this.players[i].username,
103 authentication: this.players[i].authentication,
104 });
105 }
106
10736 var parameters = [];
10836 this.gameplay.parameters.forEach(function(p) {
10972 parameters.push({
110 name : p.name,
111 value : p.value,
112 help : p.help,
113 isBoolean : p.type === Boolean
114 });
115 });
116
11736 output.gameplay = {
118 name : this.gameplay.name,
119 description : this.gameplay.description,
120 parameters : parameters,
121 maxPlayers : this.gameplay.maxPlayers,
122 minPlayers : this.gameplay.minPlayers,
123 sounds : this.gameplay.sounds
124 };
125
12636 return output;
127
128};
129
130/**
131 * Set room size
132 * @param {Number} size
133 */
1341Room.prototype.setSize = function(size) {
1353 if(size >= this.gameplay.minPlayers && size <= this.gameplay.maxPlayers) {
1362 this.size = size;
1372 sendUpdateRoom(this);
138 }
139};
140
141/**
142 * Set room parameter
143 * @param {} parameter
144 * @param {*} value
145 * @param {Socket} socket
146 */
1471Room.prototype.setParameter = function(parameter, value, socket) {
1483 this.gameplay.parameters.forEach(function(e) {
1496 if(e.name === parameter) {
1502 e.value = e.type(value); //deal with it.
1512 sendUpdateRoom(this, socket);
1522 return;
153 }
154 }.bind(this));
155};
156
1571Room.prototype.sendMessage = function(channel, message, socket) {
15824 var allowed = false;
159
16024 if(channel === 'preChat') {
1615 if(this.started) {
1621 return;
163 }
1644 allowed = true;
1654 channel = '';
166 } else {
16719 if(!this.started) {
1682 return;
169 }
17017 var channels = socket.player.getWriteChannels();
17117 allowed = (channels[channel] ? (true === channels[channel].w) : false);
172 }
173
17421 if(allowed) {
17515 socket.emit('messageSent');
17615 if(this.gameplay.processMessage && this.started) {
17711 message = this.gameplay.processMessage(channel, message, socket.player);
17811 if(!message) {
1794 return;
180 }
181 }
18211 this.broadcast(channel, 'chatMessage', {message: message, sender: socket.username});
183 }
184};
185
1861Room.prototype.start = function() {
1872 players.init(this);
1882 this.gameplay.start(this, function(err) {
189
1902 if(err) {
1911 return this.broadcast('chatMessage', { message: err });
192 }
193
1941 this.started = true;
1951 sendUpdateRoom(this);
1961 this.broadcast('gameStarted');
197
1981 if(this.gameplay.firstStage) {
1991 this.nextStage(this.gameplay.firstStage);
200 }
201
202 }.bind(this));
203};
204
205/**
206 * End the current stage and start another one
207 * @param {String} stage The new stage name
208 * @param {Function} [callback]
209 */
2101Room.prototype.nextStage = function(stage, callback) {
2115 this.currentStage = stage;
2125 this.gameplay.stages[stage].start(this, function(err, duration) {
2135 this.setStageDuration(duration);
214
215 // Send actions to players
216
2175 this.players.forEach(function(player) {
21820 player.player.sendAvailableActions();
219 });
220
2215 if(callback) {
2221 callback(null);
223 }
224 }.bind(this));
225};
226
227/**
228 * End the current stage, without starting another one
229 */
2301Room.prototype.endStage = function() {
2311 clearTimeout(this.timeout);
2321 var endFn = this.gameplay.stages[this.currentStage].end;
2331 if(endFn)
2341 endFn(this, function() {});
235};
236
237/**
238 * Change current stage duration
239 * @param {Number} duration (sec)
240 */
2411Room.prototype.setStageDuration = function(duration) {
2426 clearTimeout(this.timeout);
243
2446 if(duration < 0) {
2453 this.broadcast('clearTimer');
2463 this.currentStageEnd = Infinity;
2473 return;
248 }
249
2503 this.currentStageEnd = new Date().getTime() + duration * 1000;
2513 this.broadcast('setTimer', duration);
2523 this.timeout = setTimeout(this.endStage.bind(this), duration * 1000);
253};
254
255/**
256 * @return Number Milliseconds before next stage. Can be 'Infinity'.
257 */
2581Room.prototype.getRemainingTime = function() {
2592 return this.currentStageEnd - new Date().getTime();
260};
261
262/**
263 * Get player object from username
264 * @param {String} username
265 * @return {Socket}
266 */
2671Room.prototype.resolveUsername = function(username) {
2683 for(var i = 0; i < this.players.length; i++) {
2696 if(this.players[i].username === username) {
2702 return this.players[i];
271 }
272 }
2731 return null;
274};
275
2761module.exports = {
277
278 rooms: [],
279
280 getRooms: function() {
281
2821 var output = [];
2831 for(var i = 0; i < this.rooms.length; i++) {
2841 if(!this.rooms[i].started) {
2851 output.push(this.rooms[i].getPublicInfo());
286 }
287 }
288
2891 return output;
290 },
291
292 createRoom: function(name, password, type, socket) {
293
2944 if(__conf.maxRooms && this.rooms.length >= __conf.maxRooms) {
2950 return;
296 }
297
2984 if(!type || type === 'default') {
2992 for(var i in __gametypes) {
3002 type = i;
3012 break;
302 }
303 }
304
3054 if(!__gametypes[type]) {
3061 return;
307 }
308
3093 var gameplay = new __gametypes[type]();
310
311 // Set sounds path
3123 if(gameplay.sounds) {
3133 var sounds = [];
3143 gameplay.sounds.forEach(function(s){
3153 if(!s.distant)
3163 sounds.push({
317 id : s.id,
318 path : '/' + type + '/' + s.path
319 });
320 else
3210 sounds.push(s);
322 });
3233 gameplay.sounds = sounds;
324 }
325
3263 var room = new Room(name, password, gameplay);
327
3283 this.rooms.push(room);
3293 __app.io.to('lobby').emit('roomCreated', room.getPublicInfo());
3303 this.joinRoom(room.id, password, socket);
331 },
332
333 getRoom: function(id) {
334
33512 for(var i = 0; i < this.rooms.length; i++) {
33612 if(this.rooms[i].id === id) {
33711 return this.rooms[i];
338 }
339 }
340
3411 return null;
342
343 },
344
345 joinRoom: function(id, password, socket) {
346
34712 var room = this.getRoom(id);
34812 if(!room) {
3491 return;
350 }
351
35211 if(room.players.length >= room.size) {
3531 var d = (new Date()).getTime();
3541 if(!room.lastFullNotice || room.lastFullNotice < (d - 60*1000)) {
3551 room.lastFullNotice = d;
3561 room.broadcast('chatMessage', {message: '<span class="glyphicon glyphicon-user"></span> <strong>Someone</strong> would like to join this room. Could you add some slots?'});
357 }
3581 return;
359 }
360
36110 if(room.password && room.password !== password) {
3620 socket.emit('invalidRoomPassword');
3630 return;
364 }
365
36610 if(room.players.indexOf(socket) < 0) {
36710 room.players.push(socket);
368 }
369
37010 sendUpdateRoom(room);
37110 socket.emit('roomJoined', room.getPublicInfo());
37210 socket.join('room_' + id);
37310 socket.currentRoom = room;
374
37510 room.broadcast('chatMessage', {message: '<span class="glyphicon glyphicon-ok-circle"></span> <strong>' + socket.username + '</strong> has joined the room'});
376
377 },
378
379 leaveRoom: function(socket) {
380
38113 var room = socket.currentRoom;
38213 if(!room) {
3834 return;
384 }
385
3869 var i = room.players.indexOf(socket);
3879 if(i < 0) {
3881 return;
389 }
390
3918 room.players.splice(i, 1);
392
3938 if(room.gameplay.onDisconnect && socket.player) {
3942 room.gameplay.onDisconnect(room, socket.player);
395 }
396
3978 if(!room.started && socket.username) {
3986 room.broadcast('chatMessage', {message: '<span class="glyphicon glyphicon-remove-circle"></span> <strong>' + socket.username + '</strong> has left the room'});
399 }
400
4018 if(room)
402
4038 if(room.players.length === 0) {
4042 this.removeRoom(room);
405 } else {
4066 sendUpdateRoom(room);
407 }
408
4098 socket.player = null;
4108 socket.currentRoom = null;
411
412 // Leave concerned socket rooms
413 // Buffered, because socket.rooms is dynamically updated by socket.io
4148 var regex = /^room\_/;
4158 var buffer = [];
4168 for(var r in socket.rooms) {
41717 if(regex.test(socket.rooms[r])) {
4187 buffer.push(r);
419 }
420 }
421
4228 try {
4238 buffer.forEach(function(r) {
4247 socket.leave(r);
425 });
4268 socket.emit('roomLeft');
427 } catch(e) {}
428
429 },
430
431 removeRoom: function(room) {
432
4332 for(var i = 0; i < this.rooms.length; i++) {
4342 if(this.rooms[i].id === room.id) {
4352 clearTimeout(this.rooms[i].timeout);
4362 this.rooms.splice(i, 1);
4372 __app.io.to('lobby').emit('roomRemoved', room.id);
4382 return;
439 }
440 }
441
442 }
443
444};
445
446/** PRIVATE FUNCTIONS **/
447
4481function sendUpdateRoom(room, socket) {
44921 if(!socket) {
45019 __app.io.to('lobby').emit('roomUpdated', room.getPublicInfo());
451 }
452 else {
4532 socket.broadcast.to('lobby').emit('roomUpdated', room.getPublicInfo());
454 }
455}
456

/home/lesterpig/workspace/openparty/lib/router.js

87%
120
105
15
LineHitsSource
11var utils = require('./utils');
21var rooms = require('./rooms');
31var crypto = require('./crypto');
41var git = require('git-repo-info');
5
6// we want to define this only one time
71var repo = git('.git');
81if(repo.branch) {
91 repo.url = require('../package.json').repository.url.replace(/\.git$/, '') + '/commit/' + repo.sha;
10}
11
121module.exports = function(app) {
13
14 /**
15 * ROOT
16 */
171 app.get('/', function(req, res) {
1817 req.session.locale = req.getLocale();
1917 res.render('index', {passwordRequired: __conf.password !== null });
20 });
21
22 /***
23 * ABOUT
24 */
251 app.get('/about', function(req, res) {
261 res.render('about', {repo: repo, version: __version, locales: __conf.locales, gamemodes: __staticGametypes });
27 });
28
29 /**
30 * IO : Connect
31 */
321 app.io.on('connection', function(socket) {
3317 socket.challenge = crypto.generateChallenge();
3417 socket.emit('challenge', socket.challenge);
35
36 // Check for reconnection
3717 if(socket.session.identifier && socket.session.username) {
381 socket.emit('reconnectInvitation', socket.session.username);
3916 } else if(!socket.session.identifier) {
4016 socket.session.identifier = utils.randomString(20);
4116 socket.session.save();
42 }
43 });
44
45 /**
46 * IO : Ping
47 */
481 app.io.route('o-ping', function(socket) {
491 socket.emit('o-pong');
50 });
51
52 /**
53 * IO : Disconnect
54 */
551 app.io.route('disconnect', function(socket) {
565 var room = utils.isInGame(socket);
576 if(!room) return;
584 if(!socket.session.identifier || !socket.player) {
592 rooms.leaveRoom(socket);
60 } else {
612 socket.player.disconnected = true;
622 socket.player.disconnectionTimer = setTimeout(function() {
631 rooms.leaveRoom(socket);
64 }, (room.gameplay.reconnectDelay || 60) * 1000);
65 }
66 });
67
68 /**
69 * IO : Login
70 */
711 app.io.route('login', function(socket, data) {
7218 var locale = socket.session.locale || __conf.defaultLocale;
7318 var nbLogged = 0;
7418 for(var s in app.io.sockets.sockets) {
75257 if(app.io.sockets.sockets[s].username) nbLogged++;
76 }
77
7818 if(__conf.maxPlayers && nbLogged >= __conf.maxPlayers) {
790 return socket.emit('loginResult', {err: __i18n({phrase: 'This server is full, sorry. Please try again later!', locale: locale})});
80 }
81
8218 if(data.password !== __conf.password && __conf.password !== null || !data.username) {
834 return socket.emit('loginResult', {err: __i18n({phrase: 'Bad Credentials', locale: locale})});
84 }
85
86 // Check username
8714 var username;
8814 try {
8914 username = utils.checkUsername(data.username);
90 } catch(e) {
912 return socket.emit('loginResult', {err: __i18n({phrase: e.message, locale: locale})});
92 }
93
9412 socket.join('lobby');
9512 socket.username = username;
9612 socket.emit('loginResult', {err: null, username: username, gametypes: __staticGametypes});
97
98 // Check session
9912 socket.session.username = data.username;
10012 socket.session.save();
101
102 // Check authentication (if provided)
10312 if(data.key && data.message) {
1040 crypto.verifyChallenge(socket.challenge, data.key, data.message, function(err, res) {
1050 if(err || !res.success || data.username !== res.username) {
1060 return;
107 }
1080 socket.authentication = {
109 username: res.username,
110 fingerprint: res.fingerprint,
111 };
112 });
113 }
114
115 // Try to reconnect to left room
11612 require('./recover')(socket);
117 });
118
119 /**
120 * IO : Logout (ask for session reset)
121 */
1221 app.io.route('logout', function(socket) {
1230 socket.session.identifier = undefined;
1240 socket.session.username = undefined;
1250 socket.session.save();
1260 socket.emit('reconnect');
127 });
128
129 /**
130 * IO : Get Rooms
131 */
1321 app.io.route('getRooms', function(socket) {
1332 if(!utils.isInRoom(socket, 'lobby'))
1341 return;
1351 socket.emit('roomsList', rooms.getRooms());
136 });
137
138 /**
139 * IO : Create Room
140 */
1411 app.io.route('createRoom', function(socket, data) {
1426 if(!utils.isInRoom(socket, 'lobby'))
1431 return;
1445 if(utils.isInGame(socket))
1451 return;
1464 rooms.createRoom(data.name, data.password, data.type, socket);
147 });
148
149 /**
150 * IO : Join Room
151 */
1521 app.io.route('joinRoom', function(socket, data) {
15311 if(!utils.isInRoom(socket, 'lobby'))
1541 return;
15510 if(utils.isInGame(socket))
1561 return;
1579 rooms.joinRoom(data.id, data.password, socket);
158 });
159
160 /**
161 * IO : Leave Room
162 */
1631 app.io.route('leaveRoom', function(socket) {
1649 rooms.leaveRoom(socket);
165 });
166
167 /**
168 * IO : Kick Player
169 */
1701 app.io.route('kickPlayer', function(socket, username) {
171
1720 var room = utils.isInGame(socket);
1730 var player;
1740 if( !room
175 || room.started
176 || room.players[0] !== socket
177 || !(player = room.resolveUsername(username))) {
1780 return;
179 }
1800 room.message(username + ' has been kicked out of this room.');
1810 rooms.leaveRoom(player);
182 });
183
184 /**
185 * IO : Room Parameters
186 */
1871 app.io.route('setRoomSize', function(socket, data) {
1884 var room = utils.isInGame(socket);
1894 if(room && room.players[0] === socket && room.players.length <= +data)
1903 room.setSize(+data);
191 });
192
1931 app.io.route('setRoomParameter', function(socket, data) {
1944 var room = utils.isInGame(socket);
1954 if(room && room.players[0] === socket)
1963 room.setParameter(data.parameter, data.value, socket);
197 });
198
199 /**
200 * IO : Chat Management
201 */
2021 app.io.route('sendMessage', function(socket, data) {
20328 var room = utils.isInGame(socket);
20428 var message = utils.sanitizeHtml(data.message);
20528 if(!message)
2063 return;
20725 if(room)
20824 room.sendMessage(data.channel, message, socket);
2091 else if(socket.username) {
2101 socket.emit('messageSent');
2111 app.io.to('lobby').emit('chatMessage', {message: message, sender: socket.username, lobby: true});
212 }
213 });
214
215 /**
216 * IO : Start Game !
217 */
2181 app.io.route('startRoom', function(socket) {
2193 var room = utils.isInGame(socket);
2203 if(room && room.players[0] === socket && !room.started && room.size === room.players.length)
2212 room.start();
222 });
223
224 /**
225 * IO : Execute Action
226 */
2271 app.io.route('executeAction', function(socket, data) {
228
22918 if(!socket.player || !data || !data.action)
2303 return; // Not authorized
231
23215 var actions = socket.player.getAvailableActions(false);
233
23415 if(!actions[data.action] || actions[data.action].locked)
2352 return; // Not ready for this action
236
23713 if(actions[data.action].type === 'select'
238 && actions[data.action].options.safeChoices.indexOf(data.value) < 0)
2397 return; // Bad value
240
2416 if(actions[data.action].type === 'select'
242 && actions[data.action].options.choices === 'players') {
2431 data.value = socket.player.room.resolveUsername(data.value).player;
244 }
245
2466 actions[data.action].locked = true;
2476 actions[data.action].execute(socket.player, data.value);
2486 actions[data.action].locked = false;
249 });
250};
251

/home/lesterpig/workspace/openparty/lib/utils.js

97%
46
45
1
LineHitsSource
11var crypto = require('crypto');
2
31global.GET_RANDOM = function(from, to) {
41027 return Math.floor(Math.random() * (to - from + 1)) + from;
5};
6
71module.exports = {
8
9 randomString: function(len) {
1019 return crypto.randomBytes(Math.ceil(len * 3 / 4))
11 .toString('base64') // convert to base64 format
12 .slice(0, len) // return required number of characters
13 .replace(/\+/g, '0') // replace '+' with '0'
14 .replace(/\//g, '0'); // replace '/' with '0'
15 },
16
17 isInRoom: function(socket, room) {
1819 return !!socket.rooms[room];
19 },
20
21 isInGame: function(socket) {
22
2359 if(!socket.currentRoom)
2415 return false;
25 else
2644 return socket.currentRoom;
27
28 },
29
30 sanitizeHtml: function(string) {
3141 if(!string)
323 return '';
3338 string = string.replace(/</ig,'<');
3438 string = string.replace(/>/ig,'>');
3538 return string;
36 },
37
38 checkUsername: function(username) {
3914 if(username.length > 20)
401 throw new Error('Username is too long.');
41
4213 username = this.sanitizeHtml(username);
4313 username = username.trim();
44
4513 if(username.length === 0)
461 throw new Error('Username is invalid.');
47
48 // already connected ?
4912 var index = 2;
5012 var newUsername = username;
5112 var sockets = __app.io.sockets.sockets;
5212 while(true) {
5315 var ok = true;
5415 for(var socket in sockets) {
55146 var s_username = sockets[socket].username;
56146 if(s_username && s_username.toLowerCase() === newUsername.toLowerCase()) {
573 ok = false;
583 newUsername = username + this.intToExpString(index++);
593 break;
60 }
61 }
6227 if(ok) break;
63 }
6412 return newUsername;
65 },
66
67 intToExpString: function(n) {
683 var data = ['⁰', '¹', '²', '³', '⁴', '⁵', '⁶', '⁷', '⁸', '⁹'];
69
703 if(n <= 0) {
710 return data[0];
72 }
73
743 var out = '';
753 while(n > 0) {
763 out = data[n%10] + out;
773 n = Math.floor(n/10);
78 }
793 return out;
80 },
81
82 /** TYPES */
83
84 isString: function(s) {
8578 return typeof s === 'string' || s instanceof String;
86 },
87
88 isFunction: function(f) {
8927 return typeof f === 'function';
90 },
91
92 isInteger: function(i) {
9320 return i === parseInt(i, 10);
94 },
95
96 isArray: function(a) {
9711 return require('util').isArray(a);
98 },
99
100 isObject: function(o) {
1014 return typeof o === 'object';
102 }
103};
104