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      * @property {String} cookie The user's cookie, as determined by
 50      * Socket.IO.
 51      */
 52     this.user = { clientId: socket.id, cookie: nowUtil.parseCookie(socket.handshake.headers.cookie) };
 53 
 54     // set to true upon first replaceVar and emit connect event
 55     /**
 56      * @private
 57      */
 58     this.ready = false;
 59 
 60     /**
 61      * @name User#now
 62      * @namespace Synchronized with the connected user's local now
 63      * namespace; function calls with respect to this namespace will
 64      * only be executed for the current user.
 65      * @see User
 66      * @see User#user
 67 
 68      * @example everyone.now.prop = 42;
 69      * @example everyone.now.func = function () {
 70      *  console.log('hello!');
 71      * };
 72      */
 73     this.now = Proxy.wrap(this);
 74 
 75     // Remote function call handler
 76     socket.on('rfc', function rfcHandler(data) {
 77       var theFunction;
 78       if (data.fqn.split('_')[0] === 'closure') {
 79         theFunction = nowjs.closures[data.fqn];
 80       } else {
 81         theFunction = self.get(data.fqn);
 82       }
 83       var args = data.args;
 84       // Convert any remote function stubs into remote functions
 85       var user;
 86       for (var i = 0, ll = args.length; i < ll; i++) {
 87         if (nowUtil.hasProperty(args[i], 'fqn')) {
 88           user = nowUtil.clone(self, {fqn: args[i].fqn});
 89           args[i] = fn.remotecall.bind(user);
 90         }
 91       }
 92       theFunction.apply(self, args);
 93     });
 94 
 95     // Replace var handler
 96     if (nowjs.options.clientWrite) {
 97       socket.on('rv', function rvHandler(data) {
 98         var keys = Object.keys(data);
 99         var everyone = nowjs.getGroup('everyone');
100         var fqn, user, value;
101         for (var i = 0, ll = keys.length; i < ll ; i++) {
102           fqn = keys[i];
103           value = data[fqn];
104           user = nowUtil.clone(self);
105           user.fqn = fqn;
106           if (nowUtil.hasProperty(value, 'fqn')) {
107             value = fn.remotecall.bind(user);
108           }
109           // Set directly in scope table to avoid extra processing and
110           // socket firing.
111           self.scopeTable.set(fqn, (value && typeof value === 'object') ? [] : value);
112           if (typeof value === 'function' &&
113               everyone.scopeTable.get(fqn) === undefined) {
114             everyone.scopeTable.set(fqn, nowUtil.noop);
115           }
116           if (Array.isArray(value)) {
117             self.scopeTable.flagAsArray(fqn, 0);
118           }
119         }
120       });
121 
122       // Called after initial scope sent. Ready handler
123       socket.on('rd', function rdHandler() {
124         var user = nowUtil.clone(self, {'_events': nowjs._events});
125         nowjs.emit.call(user, 'connect');
126       });
127 
128       // Variable deletion handler
129       socket.on('del', function rfcHandler(data) {
130         // Takes an array of fqns to delete.
131         // Note: Does not handle deleting something that's a group
132         // property (would need to override somehow).
133         for (var i = 0; i < data.length; i++) {
134           // delete straight from scopeTable to bypass emitting 'del'.
135           self.scopeTable.deleteVar(data[i]);
136         }
137       });
138     }
139 
140     socket.on('disconnect', function () {
141       var user = nowUtil.clone(self, {'_events': nowjs._events});
142 
143       // trigger 'disconnect' in all groups.
144       for (var g = Object.keys(self.groups), ll = g.length; ll--;) {
145         self.groups[g[ll]].removeUser(self.user.clientId);
146       }
147       nowjs.emit.call(user, 'disconnect');
148 
149       delete nowjs.users[self.user.clientId];
150     });
151   };
152 
153   /**
154    * @memberOf User#
155    * @function
156    * @name getGroups
157 
158    * @description Used to retrieve a list of the group names
159    * corresponding to all groups the user is in.
160 
161    * @param {Function} callback Called with an Array of Strings
162    * corresponding to the various group names.
163 
164    * @example everyone.now.broadcast = function (message) {
165    * var name = this.now.name;
166    * this.getGroups(function (groups) {
167    *   for (var i = groups.length; i--;) {
168    *     if (groups[i] !== 'everyone') {
169    *       nowjs.getGroup(groups[i]).now.receive(name, message);
170    *     }
171    *   }
172    * });
173    */
174   User.prototype.getGroups = function (callback) {
175     callback(Object.keys(this.groups));
176   };
177 
178   /** @private */
179   User.prototype.get = function (fqn) {
180     // First look in this user's scopeTable
181     var value = this.scopeTable.get(fqn);
182     if (value !== undefined) {
183       return value;
184     } else {
185       // Look through all the groups for the value
186       var i = 0;
187       var keys = Object.keys(this.groups);
188       var ll = keys.length;
189       while (value === undefined && i < ll) {
190         // Look in the scopeTable directly, rather than using Group.get
191         // method. This resolves functions to the real functions
192         // rather than a multicaller.
193         value = this.groups[keys[i++]].scopeTable.get(fqn);
194       }
195       // If this is a function, bind to the current user
196       if (typeof value === 'function') {
197         var userClone = nowUtil.clone(this);
198         userClone.fqn = fqn;
199         value = value.bind(userClone);
200       }
201 
202       // Cache it in this user's scopeTable for quicker access.
203       this.scopeTable[fqn] = value;
204       return value;
205     }
206   };
207 
208   /** @private */
209   User.prototype.deleteVar = function (fqn) {
210     this.scopeTable.deleteVar(fqn);
211     this.socket.emit('del', fqn);
212   };
213 
214   /** @private */
215   User.prototype.set = function (fqn, val) {
216     var everyone = nowjs.getGroup('everyone');
217     if (typeof val === 'function' &&
218         everyone.scopeTable.get(fqn) === undefined) {
219       everyone.scopeTable.set(fqn, nowUtil.noop);
220     }
221     if (typeof val === 'object') {
222       this.scopeTable.set(fqn, Object.keys(val));
223       var flattenedVal = nowUtil.flatten(val, fqn);
224       for (var i = 0, key = Object.keys(flattenedVal), ll = key.length; i < ll; i++) {
225         this.scopeTable.set(key[i], flattenedVal[key[i]]);
226         flattenedVal[key[i]] = nowUtil.getValOrFqn(flattenedVal[key[i]], key[i]);
227         if (flattenedVal[key[i]] instanceof Array) {
228           this.scopeTable.flagAsArray(key[i], 0);
229         }
230       }
231       this.socket.emit('rv', flattenedVal);
232     } else {
233       this.scopeTable.set(fqn, val);
234       var toSend = {};
235       toSend[fqn] = nowUtil.getValOrFqn(val, fqn);
236       this.socket.emit('rv', toSend);
237     }
238   };
239 
240   return User;
241 };
242