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