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