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 151 * @name Group#join 152 * @event 153 * @description Called in the context of a user who has just been 154 * added to the group. 155 156 * @example everyone.on('join', function () { 157 * otherGroup.addUser(this.user.clientId); 158 * }); 159 */ 160 self.emit.call(user, 'join'); 161 }); 162 }); 163 }; 164 165 /** 166 * @name removeUser 167 * @function 168 * @memberOf Group# 169 * @version 0.7.0 170 * @description Removes the user identified by clientId from this group. 171 172 * @param {String} clientId The client ID associated with the target 173 * user. 174 175 * @example otherGroup.removeUser('1234567890'); 176 */ 177 Group.prototype.removeUser = function (clientId) { 178 var self = this; 179 this.hasClient(clientId, function (hasClient) { 180 if (!hasClient) { 181 return; 182 } 183 var user = nowUtil.clone(self.users[clientId], {'_events': self._events}); 184 185 /** 186 * @name Group#leave 187 * @event 188 * @version 0.7.0 189 * @description Called in the context of a user who has just been 190 * removed from the group. 191 192 * @example otherGroup.on('leave', function () { 193 * // Store the context, i.e. the user who has just left. 194 * var self = this; 195 * // Check that the user is still connected to the server. 196 * everyone.hasClient(this.user.clientId, function (bool) { 197 * if (bool) { 198 * // Send parting words to the client. 199 * this.now.receive('SERVER', 'Goodbye. I'll miss you dearly.'); 200 * } 201 * }); 202 * }); 203 */ 204 self.emit.call(user, 'leave'); 205 // Delete all remote functions that are part of this group from 206 // the user. 207 var fqns = Object.keys(self.scopeTable.data); 208 for (var i = 0; i < fqns.length; i++) { 209 if (typeof self.scopeTable.data[fqns[i]] === 'function' && 210 self.scopeTable.data[fqns[i]] !== nowUtil.noop && 211 user.scopeTable.data[fqns[i]] === undefined) { 212 // Tell the user to delete his function. 213 user.deleteVar(fqns[i]); 214 } 215 } 216 delete self.users[clientId]; 217 delete user.groups[self.groupName]; 218 }); 219 }; 220 221 /** 222 * @memberOf Group# 223 * @function 224 * @name exclude 225 * @version 0.7.0 226 227 * @description Returns a new Group that is identical to the calling 228 * group, except with the specified clients excluded. The returned 229 * group automatically updates to accommodate any changes made to 230 * its parent group. 231 232 * @param {Array} clientIds A list of client IDs corresponding to 233 * clients to exclude. 234 235 * @example everyone.now.distribute = function (msg) { 236 * everyone.exclude([this.user.clientId]).now.receive(this.now.name, msg); 237 * }; 238 * @example factionOne.now.distribute = function (msg) { 239 * factionTwo.getUsers(function (users) { 240 * factionOne.exclude(users).now.receive(this.user.clientId, msg); 241 * }); 242 * }; 243 244 * @type Group 245 */ 246 Group.prototype.exclude = function (clientIds) { 247 var excludes = {}; 248 if(typeof clientIds === 'string') { 249 return this.exclude([clientIds]); 250 } else { 251 for (var i = 0; i < clientIds.length; i++) { 252 excludes[clientIds[i]] = true; 253 } 254 } 255 var group = nowUtil.clone(this, {excludes: nowUtil.clone(this.excludes, excludes)}); 256 group.now = Proxy.wrap(group); 257 return group; 258 }; 259 260 /** 261 * @memberOf Group# 262 * @function 263 * @name hasClient 264 265 * @description Used to determine a given user's membership in the 266 267 * group. 268 269 * @param {String} clientId The client ID associated with the target 270 * user. 271 272 * @param {Function} callback Called with a Boolean indicating the 273 * user's membership in the group. 274 275 * @example group.hasClient('1234567890', function (bool) { 276 * if (bool) { 277 * console.log('User is a member of `group`.'); 278 * } 279 * }); 280 */ 281 Group.prototype.hasClient = function (clientId, callback) { 282 callback(this.users[clientId] !== undefined); 283 284 }; 285 286 /** 287 * @private 288 */ 289 Group.prototype.get = function (fqn) { 290 var value = this.scopeTable.get(fqn); 291 // If this is a regular group, look in `everyone` for the value 292 if (value === undefined && !this.isSuperGroup) { 293 value = nowjs.getGroup('everyone').scopeTable.get(fqn); 294 } 295 296 // If this is a function, return a multicaller. 297 if (typeof value === 'function') { 298 var group = nowUtil.clone(this, {fqn: fqn}); 299 value = fn.multicall.bind(group); 300 } 301 return value; 302 }; 303 304 /** 305 * @private 306 */ 307 Group.prototype.deleteVar = function (fqn) { 308 var obj = nowUtil.clone(this, {fqn: fqn}); 309 nowjs.emit('groupdel', obj); 310 }; 311 312 /** 313 * @private 314 */ 315 Group.prototype.set = function (fqn, val) { 316 var obj = nowUtil.clone(this, {fqn: fqn, val: val}); 317 nowjs.emit('grouprv', obj); 318 }; 319 320 return Group; 321 }; 322 323 /** 324 * @name Group#connect 325 * @event 326 * @deprecated As of 0.7.0. Use nowjs#connect instead. 327 * @description Called in the context of a user when the server 328 * first receives a message from the given user. 329 */ 330 331 /** 332 * @name Group#disconnect 333 * @event 334 * @deprecated As of 0.7.0. Use nowjs#disconnect instead. 335 * @description Called in the context of a user who has just 336 * disconnected from the server. 337 */ 338