1 var EventEmitter = require('events').EventEmitter; 2 var nowUtil = require('./nowUtil').nowUtil; 3 var Proxy = require('./proxy'); 4 var ScopeTable = require('./scopeTable').ScopeTable; 5 6 exports.initialize = function (nowjs) { 7 var fn = require('./function').init(nowjs); 8 9 /** 10 * @name Group 11 12 * @class Represents a group containing some subset of all 13 users. Each group has its own {@link Group#now} namespace. 14 15 * @property {String} groupName The name associated with the group. 16 * @see nowjs#getGroup 17 * @see Group#now 18 */ 19 var Group = function (groupName) { 20 // all users in the group 21 this.users = {}; 22 23 this.groupName = groupName; 24 25 this.excludes = {}; 26 /** 27 * @name Group#now 28 * @namespace The now namespace for this particular group. Actions 29 * to this namespace affect all users that are members of the 30 * group. All functions that are called will be called in the 31 * context of each individual user in the group. 32 33 * @example everyone.now.prop = 42; 34 * @example everyone.now.func = function () { 35 * console.log('hello!'); 36 * }; 37 */ 38 this.now = Proxy.wrap(this); 39 // group scope table 40 this.scopeTable = new ScopeTable(); 41 }; 42 43 Group.prototype.__proto__ = EventEmitter.prototype; 44 45 /** 46 * @name count 47 * @function 48 * @memberOf Group# 49 50 * @description Used to find the cardinality of the group (how 51 * many users it contains). 52 53 * @param {Function} callback Called with a Number corresponding to 54 * the group's user count. 55 56 * @example everyone.count(function (ct) { 57 * console.log(ct); 58 * }); 59 */ 60 Group.prototype.count = function (callback) { 61 callback(Object.keys(this.users).length); 62 }; 63 64 /** 65 * @name getUsers 66 * @function 67 * @memberOf Group# 68 69 * @description Used to retrieve a list of the client IDs 70 * corresponding to all users in the group. 71 72 * @param {Function} callback Called with an Array of Strings 73 * corresponding to the client IDs of all users in the group. 74 75 * @example everyone.getUsers(function (users) { 76 * for (var i = 0; i < users.length; i++) console.log(users[i]); 77 * }); 78 79 * @see nowjs#getClient 80 */ 81 Group.prototype.getUsers = function (callback) { 82 callback(Object.keys(this.users)); 83 }; 84 85 /** 86 * @name connected 87 * @function 88 * @memberOf Group# 89 * @deprecated As of 0.7.0. Use nowjs:connect instead. 90 */ 91 Group.prototype.connected = function (callback) { 92 nowjs.on('connect', callback); 93 }; 94 95 /** 96 * @name disconnected 97 * @function 98 * @memberOf Group# 99 * @deprecated As of 0.7.0. Use nowjs:disconnect instead. 100 */ 101 Group.prototype.disconnected = function (callback) { 102 nowjs.on('disconnect', callback); 103 }; 104 105 /** 106 * @name addUser 107 * @function 108 * @memberOf Group# 109 * @description Adds the user identified by clientId to this group. 110 111 * @param {String} clientId The client ID associated with the target 112 * user. 113 114 * @example everyone.addUser('1234567890'); 115 */ 116 Group.prototype.addUser = function (clientId) { 117 var self = this; 118 this.hasClient(clientId, function (hasClient) { 119 if (hasClient) { 120 return; 121 } 122 if (self.excludes[clientId]) { 123 self.excludes[clientId] = false; 124 } 125 nowjs.getClient(clientId, function () { 126 // Scoping note: `self` refers to the group, `this` refers to 127 // the new client. 128 self.users[this.user.clientId] = this; 129 this.groups[self.groupName] = self; 130 131 // Send the client any new variables that it does not already have 132 var toSend = {}; 133 var keys = Object.keys(self.scopeTable.data); 134 var key, val; 135 for (var i = 0, ll = keys.length; i < ll; i++) { 136 key = keys[i]; 137 val = self.scopeTable.get(key); 138 if (val !== nowUtil.noop && !Array.isArray(val) && 139 !nowUtil.hasProperty(this.scopeTable.data, key)) { 140 toSend[key] = nowUtil.getValOrFqn(val, key); 141 } 142 } 143 144 if (self.isSuperGroup || !nowUtil.isEmptyObj(toSend)) { 145 this.socket.emit('rv', toSend); 146 } 147 var user = nowUtil.clone(this, {'_events': self._events}); 148 149 /** 150 * @name Group#join 151 * @event 152 * @description Called in the context of a user who has just been 153 * added to the group. 154 155 * @example everyone.on('join', function () { 156 * otherGroup.addUser(this.user.clientId); 157 * }); 158 */ 159 self.emit.call(user, 'join'); 160 }); 161 }); 162 }; 163 164 /** 165 * @name removeUser 166 * @function 167 * @memberOf Group# 168 * @version 0.7.0 169 * @description Removes the user identified by clientId from this group. 170 171 * @param {String} clientId The client ID associated with the target 172 * user. 173 174 * @example otherGroup.removeUser('1234567890'); 175 */ 176 Group.prototype.removeUser = function (clientId) { 177 var self = this; 178 this.hasClient(clientId, function (hasClient) { 179 if (!hasClient) { 180 return; 181 } 182 var user = nowUtil.clone(self.users[clientId], {'_events': self._events}); 183 184 /** 185 * @name Group#leave 186 * @event 187 * @version 0.7.0 188 * @description Called in the context of a user who has just been 189 * removed from the group. 190 191 * @example otherGroup.on('leave', function () { 192 * // Store the context, i.e. the user who has just left. 193 * var self = this; 194 * // Check that the user is still connected to the server. 195 * everyone.hasClient(this.user.clientId, function (bool) { 196 * if (bool) { 197 * // Send parting words to the client. 198 * this.now.receive('SERVER', 'Goodbye. I'll miss you dearly.'); 199 * } 200 * }); 201 * }); 202 */ 203 self.emit.call(user, 'leave'); 204 // Delete all remote functions that are part of this group from 205 // the user. 206 var fqns = Object.keys(self.scopeTable.data); 207 for (var i = 0; i < fqns.length; i++) { 208 if (typeof self.scopeTable.data[fqns[i]] === 'function' && 209 self.scopeTable.data[fqns[i]] !== nowUtil.noop && 210 user.scopeTable.data[fqns[i]] === undefined) { 211 // Tell the user to delete his function. 212 user.deleteVar(fqns[i]); 213 } 214 } 215 delete self.users[clientId]; 216 delete user.groups[self.groupName]; 217 }); 218 }; 219 220 /** 221 * @memberOf Group# 222 * @function 223 * @name exclude 224 * @version 0.7.0 225 226 * @description Returns a new Group that is identical to the calling 227 * group, except with the specified clients excluded. The returned 228 * group automatically updates to accommodate any changes made to 229 * its parent group. 230 231 * @param {Array} clientIds A list of client IDs corresponding to 232 * clients to exclude. 233 234 * @example everyone.now.distribute = function (msg) { 235 * everyone.exclude([this.user.clientId]).now.receive(this.now.name, msg); 236 * }; 237 * @example factionOne.now.distribute = function (msg) { 238 * factionTwo.getUsers(function (users) { 239 * factionOne.exclude(users).now.receive(this.user.clientId, msg); 240 * }); 241 * }; 242 243 * @type Group 244 */ 245 Group.prototype.exclude = function (clientIds) { 246 var excludes = {}; 247 if(typeof clientIds === 'string') { 248 return this.exclude([clientIds]); 249 } else { 250 for (var i = 0; i < clientIds.length; i++) { 251 excludes[clientIds[i]] = true; 252 } 253 } 254 var group = nowUtil.clone(this, {excludes: nowUtil.clone(this.excludes, excludes)}); 255 group.now = Proxy.wrap(group); 256 return group; 257 }; 258 259 /** 260 * @memberOf Group# 261 * @function 262 * @name hasClient 263 264 * @description Used to determine a given user's membership in the 265 * group. 266 267 * @param {String} clientId The client ID associated with the target 268 * user. 269 270 * @param {Function} callback Called with a Boolean indicating the 271 * user's membership in the group. 272 273 * @example group.hasClient('1234567890', function (bool) { 274 * if (bool) { 275 * console.log('User is a member of `group`.'); 276 * } 277 * }); 278 */ 279 Group.prototype.hasClient = function (clientId, callback) { 280 callback(this.users[clientId] !== undefined); 281 }; 282 283 /** 284 * @private 285 */ 286 Group.prototype.get = function (fqn) { 287 var value = this.scopeTable.get(fqn); 288 // If this is a regular group, look in `everyone` for the value 289 if (value === undefined && !this.isSuperGroup) { 290 value = nowjs.getGroup('everyone').scopeTable.get(fqn); 291 } 292 293 // If this is a function, return a multicaller. 294 if (typeof value === 'function') { 295 var group = nowUtil.clone(this, {fqn: fqn}); 296 value = fn.multicall.bind(group); 297 } 298 return value; 299 }; 300 301 /** 302 * @private 303 */ 304 Group.prototype.deleteVar = function (fqn) { 305 var obj = nowUtil.clone(this, {fqn: fqn}); 306 nowjs.emit('groupdel', obj); 307 }; 308 309 /** 310 * @private 311 */ 312 Group.prototype.set = function (fqn, val) { 313 var obj = nowUtil.clone(this, {fqn: fqn, val: val}); 314 nowjs.emit('grouprv', obj); 315 }; 316 317 return Group; 318 }; 319 320 /** 321 * @name Group#connect 322 * @event 323 * @deprecated As of 0.7.0. Use nowjs#connect instead. 324 * @description Called in the context of a user when the server 325 * first receives a message from the given user. 326 */ 327 328 /** 329 * @name Group#disconnect 330 * @event 331 * @deprecated As of 0.7.0. Use nowjs#disconnect instead. 332 * @description Called in the context of a user who has just 333 * disconnected from the server. 334 */ 335