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