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