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 Now#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   /**
150    * @memberOf User#
151    * @function
152    * @name getGroups
153 
154    * @description Used to retrieve a list of the group names
155    * corresponding to all groups the user is in.
156 
157    * @param {Function} callback Called with an Array of Strings
158    * corresponding to the various group names.
159 
160    * @example everyone.now.broadcast = function (message) {
161    * var name = this.now.name;
162    * this.getGroups(function (groups) {
163    *   for (var i = groups.length; i--;) {
164    *     if (groups[i] !== 'everyone') {
165    *       nowjs.getGroup(groups[i]).now.receive(name, message);
166    *     }
167    *   }
168    * });
169    */
170   User.prototype.getGroups = function (callback) {
171     callback(Object.keys(this.groups));
172   };
173 
174   /** @private */
175   User.prototype.get = function (fqn) {
176     // First look in this user's scopeTable
177     var value = this.scopeTable.get(fqn);
178     if (value !== undefined) {
179       return value;
180     } else {
181       // Look through all the groups for the value
182       var i = 0;
183       var keys = Object.keys(this.groups);
184       var ll = keys.length;
185       while (value === undefined && i < ll) {
186         // Look in the scopeTable directly, rather than using Group.get
187         // method. This resolves functions to the real functions
188         // rather than a multicaller.
189         value = this.groups[keys[i++]].scopeTable.get(fqn);
190       }
191       // If this is a function, bind to the current user
192       if (typeof value === 'function') {
193         var userClone = nowUtil.clone(this);
194         userClone.fqn = fqn;
195         value = value.bind(userClone);
196       }
197 
198       // Cache it in this user's scopeTable for quicker access.
199 200       this.scopeTable[fqn] = value;
201       return value;
202     }
203   };
204 
205   /** @private */
206   User.prototype.deleteVar = function (fqn) {
207     this.scopeTable.deleteVar(fqn);
208     this.socket.emit('del', fqn);
209   };
210 
211   /** @private */
212   User.prototype.set = function (fqn, val) {
213     var everyone = nowjs.getGroup('everyone');
214     if (typeof val === 'function' &&
215         everyone.scopeTable.get(fqn) === undefined) {
216       everyone.scopeTable.set(fqn, nowUtil.noop);
217     }
218     if (typeof val === 'object') {
219       this.scopeTable.set(fqn, Object.keys(val));
220       var flattenedVal = nowUtil.flatten(val, fqn);
221       for (var i = 0, key = Object.keys(flattenedVal), ll = key.length; i < ll; i++) {
222         this.scopeTable.set(key[i], flattenedVal[key[i]]);
223         flattenedVal[key[i]] = nowUtil.getValOrFqn(flattenedVal[key[i]], key[i]);
224       }
225       this.socket.emit('rv', flattenedVal);
226     } else {
227       this.scopeTable.set(fqn, val);
228       var toSend = {};
229       toSend[fqn] = nowUtil.getValOrFqn(val, fqn);
230       this.socket.emit('rv', toSend);
231     }
232   };
233 
234   return User;
235 };
236