1 var nowUtil = require('./nowUtil.js').nowUtil;
  2 var Proxy = require('./proxy');
  3 var ScopeTable = require('./scopeTable').ScopeTable;
  4 
  5 exports.initialize = function (nowjs) {
  6   var fn = require('./function').init(nowjs);
  7 
  8   /**
  9    * @name User
 10 
 11    * @class Represents an individual user connected to the now
 12    * server. Exposed as the context in which group.now functions are
 13    * called, as well as the context in which the callback to
 14    * nowjs.getClient is executed. Each user has the {@link User#now}
 15    * and {@link User#user} namespaces.
 16    * @see Group#addUser
 17    * @see Group#removeUser
 18    * @see nowjs#getClient
 19 
 20    * @property {Socket} socket The user's Socket.IO socket.
 21    */
 22   var User = function (socket) {
 23     var self = this;
 24     // Set socket and the approprate handlers
 25 
 26     this.socket = socket;
 27 
 28     // groups that the user is in.
 29     /**
 30      * @private
 31      */
 32     this.groups = {};
 33 
 34     // the user's ScopeTable
 35     /**
 36      * @private
 37      */
 38     this.scopeTable = new ScopeTable();
 39 
 40     /**
 41      * @name User#user
 42      * @namespace Contains properties unique to each user. Can be used
 43      * to store information that should not be exposed to the
 44      * connected user.
 45      * @see User
 46 
 47      * @property {String} clientId The user's ID, as created by
 48      * Socket.IO.
 49      */
 50     this.user = { clientId: socket.id };
 51 
 52     // set to true upon first replaceVar and emit connect event
 53     /**
 54      * @private
 55      */
 56     this.ready = false;
 57 
 58     /**
 59      * @name User#now
 60      * @namespace Synchronized with the connected user's local now
 61      * namespace; function calls with respect to this namespace will
 62      * only be executed for the current user.
 63      * @see User
 64      * @see User#user
 65 
 66      * @example everyone.now.prop = 42;
 67      * @example everyone.now.func = function () {
 68      *  console.log('hello!');
 69      * };
 70      */
 71     this.now = Proxy.wrap(this);
 72 
 73     // Remote function call handler
 74     socket.on('rfc', function rfcHandler(data) {
 75       var theFunction;
 76       if (data.fqn.split('_')[0] === 'closure') {
 77         theFunction = nowjs.closures[data.fqn];
 78       } else {
 79         theFunction = self.get(data.fqn);
 80       }
 81       var args = data.args;
 82       // Convert any remote function stubs into remote functions
 83       var user;
 84       for (var i = 0, ll = args.length; i < ll; i++) {
 85         if (nowUtil.hasProperty(args[i], 'fqn')) {
 86           user = nowUtil.clone(self, {fqn: args[i].fqn});
 87           args[i] = fn.remotecall.bind(user);
 88         }
 89       }
 90       theFunction.apply(self, args);
 91     });
 92 
 93     // Replace var handler
 94     if (nowjs.options.clientWrite) {
 95       socket.on('rv', function rvHandler(data) {
 96         var keys = Object.keys(data);
 97         var everyone = nowjs.getGroup('everyone');
 98         var fqn, user, value;
 99         for (var i = 0, ll = keys.length; i < ll ; i++) {
100           fqn = keys[i];
101           value = data[fqn];
102           user = nowUtil.clone(self);
103           user.fqn = fqn;
104           if (nowUtil.hasProperty(value, 'fqn')) {
105             value = fn.remotecall.bind(user);
106           }
107           // Set directly in scope table to avoid extra processing and
108           // socket firing.
109           self.scopeTable.set(fqn, value);
110           if (typeof value === 'function' &&
111               everyone.scopeTable.get(fqn) === undefined) {
112             everyone.scopeTable.set(fqn, nowUtil.noop);
113           }
114         }
115       });
116 
117       // Called after initial scope sent. Ready handler
118       socket.on('rd', function rdHandler() {
119         var user = nowUtil.clone(self, {'_events': nowjs._events});
120         nowjs.emit.apply(user, ['connect']);
121       });
122 
123       // Variable deletion handler
124       socket.on('del', function rfcHandler(data) {
125         // Takes an array of fqns to delete.
126         // Note: Does not handle deleting something that's a group
127         // property (would need to override somehow).
128         for (var i = 0; i < data.length; i++) {
129           // delete straight from scopeTable to bypass emitting 'del'.
130           self.scopeTable.deleteVar(data[i]);
131         }
132       });
133     }
134 
135     socket.on('disconnect', function () {
136       var user = nowUtil.clone(self, {'_events': nowjs._events});
137 
138       // trigger 'disconnect' in all groups.
139       for (var g = Object.keys(self.groups), ll = g.length; ll--;) {
140         self.groups[g[ll]].removeUser(self.user.clientId);
141       }
142       nowjs.emit.apply(user, ['disconnect']);
143 
144       delete nowjs.users[self.user.clientId];
145     });
146   };
147 
148   /**
149    * @memberOf User#
150    * @function
151    * @name getGroups
152 
153    * @description Used to retrieve a list of the group names
154    * corresponding to all groups the user is in.
155 
156    * @param {Function} callback Called with an Array of Strings
157    * corresponding to the various group names.
158 
159    * @example everyone.now.broadcast = function (message) {
160    * var name = this.now.name;
161    * this.getGroups(function (groups) {
162    *   for (var i = groups.length; i--;) {
163    *     if (groups[i] !== 'everyone') {
164    *       nowjs.getGroup(groups[i]).now.receive(name, message);
165    *     }
166    *   }
167    * });
168    */
169   User.prototype.getGroups = function (callback) {
170     callback(Object.keys(this.groups));
171   };
172 
173   /** @private */
174   User.prototype.get = function (fqn) {
175     // First look in this user's scopeTable
176     var value = this.scopeTable.get(fqn);
177     if (value !== undefined) {
178       return value;
179     } else {
180       // Look through all the groups for the value
181       var i = 0;
182       var keys = Object.keys(this.groups);
183       var ll = keys.length;
184       while (value === undefined && i < ll) {
185         // Look in the scopeTable directly, rather than using Group.get
186         // method. This resolves functions to the real functions
187         // rather than a multicaller.
188         value = this.groups[keys[i++]].scopeTable.get(fqn);
189       }
190       // If this is a function, bind to the current user
191       if (typeof value === 'function') {
192         var userClone = nowUtil.clone(this);
193         userClone.fqn = fqn;
194         value = value.bind(userClone);
195       }
196 
197       // Cache it in this user's scopeTable for quicker access.
198       this.scopeTable[fqn] = value;
199       return value;
200     }
201   };
202 
203   /** @private */
204   User.prototype.deleteVar = function (fqn) {
205     this.scopeTable.deleteVar(fqn);
206     this.socket.emit('del', fqn);
207   };
208 
209   /** @private */
210   User.prototype.set = function (fqn, val) {
211     var everyone = nowjs.getGroup('everyone');
212     if (typeof val === 'function' &&
213         everyone.scopeTable.get(fqn) === undefined) {
214       everyone.scopeTable.set(fqn, nowUtil.noop);
215     }
216     if (typeof val === 'object') {
217       this.scopeTable.set(fqn, Object.keys(val));
218       var flattenedVal = nowUtil.flatten(val, fqn);
219       for (var i = 0, key = Object.keys(flattenedVal), ll = key.length; i < ll; i++) {
220         this.scopeTable.set(key[i], flattenedVal[key[i]]);
221         flattenedVal[key[i]] = nowUtil.getValOrFqn(flattenedVal[key[i]], key[i]);
222       }
223       this.socket.emit('rv', flattenedVal);
224     } else {
225       this.scopeTable.set(fqn, val);
226       var toSend = {};
227       toSend[fqn] = nowUtil.getValOrFqn(val, fqn);
228       this.socket.emit('rv', toSend);
229     }
230   };
231 
232   return User;
233 };
234