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