From 67fdec20726e48ba3a934cb25bb30d47ec4a4f29 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yaroslav=20De=20La=20Pe=C3=B1a=20Smirnov?= Date: Wed, 29 Nov 2017 11:44:34 +0300 Subject: Initial commit, version 0.5.3 --- node_modules/socket.io/lib/client.js | 252 +++++++++++++++ node_modules/socket.io/lib/index.js | 474 +++++++++++++++++++++++++++ node_modules/socket.io/lib/namespace.js | 279 ++++++++++++++++ node_modules/socket.io/lib/socket.js | 557 ++++++++++++++++++++++++++++++++ 4 files changed, 1562 insertions(+) create mode 100644 node_modules/socket.io/lib/client.js create mode 100644 node_modules/socket.io/lib/index.js create mode 100644 node_modules/socket.io/lib/namespace.js create mode 100644 node_modules/socket.io/lib/socket.js (limited to 'node_modules/socket.io/lib') diff --git a/node_modules/socket.io/lib/client.js b/node_modules/socket.io/lib/client.js new file mode 100644 index 0000000..0b5f044 --- /dev/null +++ b/node_modules/socket.io/lib/client.js @@ -0,0 +1,252 @@ + +/** + * Module dependencies. + */ + +var parser = require('socket.io-parser'); +var debug = require('debug')('socket.io:client'); +var url = require('url'); + +/** + * Module exports. + */ + +module.exports = Client; + +/** + * Client constructor. + * + * @param {Server} server instance + * @param {Socket} conn + * @api private + */ + +function Client(server, conn){ + this.server = server; + this.conn = conn; + this.encoder = server.encoder; + this.decoder = new server.parser.Decoder(); + this.id = conn.id; + this.request = conn.request; + this.setup(); + this.sockets = {}; + this.nsps = {}; + this.connectBuffer = []; +} + +/** + * Sets up event listeners. + * + * @api private + */ + +Client.prototype.setup = function(){ + this.onclose = this.onclose.bind(this); + this.ondata = this.ondata.bind(this); + this.onerror = this.onerror.bind(this); + this.ondecoded = this.ondecoded.bind(this); + + this.decoder.on('decoded', this.ondecoded); + this.conn.on('data', this.ondata); + this.conn.on('error', this.onerror); + this.conn.on('close', this.onclose); +}; + +/** + * Connects a client to a namespace. + * + * @param {String} name namespace + * @api private + */ + +Client.prototype.connect = function(name, query){ + debug('connecting to namespace %s', name); + var nsp = this.server.nsps[name]; + if (!nsp) { + this.packet({ type: parser.ERROR, nsp: name, data : 'Invalid namespace'}); + return; + } + + if ('/' != name && !this.nsps['/']) { + this.connectBuffer.push(name); + return; + } + + var self = this; + var socket = nsp.add(this, query, function(){ + self.sockets[socket.id] = socket; + self.nsps[nsp.name] = socket; + + if ('/' == nsp.name && self.connectBuffer.length > 0) { + self.connectBuffer.forEach(self.connect, self); + self.connectBuffer = []; + } + }); +}; + +/** + * Disconnects from all namespaces and closes transport. + * + * @api private + */ + +Client.prototype.disconnect = function(){ + for (var id in this.sockets) { + if (this.sockets.hasOwnProperty(id)) { + this.sockets[id].disconnect(); + } + } + this.sockets = {}; + this.close(); +}; + +/** + * Removes a socket. Called by each `Socket`. + * + * @api private + */ + +Client.prototype.remove = function(socket){ + if (this.sockets.hasOwnProperty(socket.id)) { + var nsp = this.sockets[socket.id].nsp.name; + delete this.sockets[socket.id]; + delete this.nsps[nsp]; + } else { + debug('ignoring remove for %s', socket.id); + } +}; + +/** + * Closes the underlying connection. + * + * @api private + */ + +Client.prototype.close = function(){ + if ('open' == this.conn.readyState) { + debug('forcing transport close'); + this.conn.close(); + this.onclose('forced server close'); + } +}; + +/** + * Writes a packet to the transport. + * + * @param {Object} packet object + * @param {Object} opts + * @api private + */ + +Client.prototype.packet = function(packet, opts){ + opts = opts || {}; + var self = this; + + // this writes to the actual connection + function writeToEngine(encodedPackets) { + if (opts.volatile && !self.conn.transport.writable) return; + for (var i = 0; i < encodedPackets.length; i++) { + self.conn.write(encodedPackets[i], { compress: opts.compress }); + } + } + + if ('open' == this.conn.readyState) { + debug('writing packet %j', packet); + if (!opts.preEncoded) { // not broadcasting, need to encode + this.encoder.encode(packet, writeToEngine); // encode, then write results to engine + } else { // a broadcast pre-encodes a packet + writeToEngine(packet); + } + } else { + debug('ignoring packet write %j', packet); + } +}; + +/** + * Called with incoming transport data. + * + * @api private + */ + +Client.prototype.ondata = function(data){ + // try/catch is needed for protocol violations (GH-1880) + try { + this.decoder.add(data); + } catch(e) { + this.onerror(e); + } +}; + +/** + * Called when parser fully decodes a packet. + * + * @api private + */ + +Client.prototype.ondecoded = function(packet) { + if (parser.CONNECT == packet.type) { + this.connect(url.parse(packet.nsp).pathname, url.parse(packet.nsp, true).query); + } else { + var socket = this.nsps[packet.nsp]; + if (socket) { + process.nextTick(function() { + socket.onpacket(packet); + }); + } else { + debug('no socket for namespace %s', packet.nsp); + } + } +}; + +/** + * Handles an error. + * + * @param {Object} err object + * @api private + */ + +Client.prototype.onerror = function(err){ + for (var id in this.sockets) { + if (this.sockets.hasOwnProperty(id)) { + this.sockets[id].onerror(err); + } + } + this.conn.close(); +}; + +/** + * Called upon transport close. + * + * @param {String} reason + * @api private + */ + +Client.prototype.onclose = function(reason){ + debug('client close with reason %s', reason); + + // ignore a potential subsequent `close` event + this.destroy(); + + // `nsps` and `sockets` are cleaned up seamlessly + for (var id in this.sockets) { + if (this.sockets.hasOwnProperty(id)) { + this.sockets[id].onclose(reason); + } + } + this.sockets = {}; + + this.decoder.destroy(); // clean up decoder +}; + +/** + * Cleans up event listeners. + * + * @api private + */ + +Client.prototype.destroy = function(){ + this.conn.removeListener('data', this.ondata); + this.conn.removeListener('error', this.onerror); + this.conn.removeListener('close', this.onclose); + this.decoder.removeListener('decoded', this.ondecoded); +}; diff --git a/node_modules/socket.io/lib/index.js b/node_modules/socket.io/lib/index.js new file mode 100644 index 0000000..e16133a --- /dev/null +++ b/node_modules/socket.io/lib/index.js @@ -0,0 +1,474 @@ + +/** + * Module dependencies. + */ + +var http = require('http'); +var read = require('fs').readFileSync; +var path = require('path'); +var exists = require('fs').existsSync; +var engine = require('engine.io'); +var clientVersion = require('socket.io-client/package.json').version; +var Client = require('./client'); +var Emitter = require('events').EventEmitter; +var Namespace = require('./namespace'); +var Adapter = require('socket.io-adapter'); +var parser = require('socket.io-parser'); +var debug = require('debug')('socket.io:server'); +var url = require('url'); + +/** + * Module exports. + */ + +module.exports = Server; + +/** + * Socket.IO client source. + */ + +var clientSource = undefined; +var clientSourceMap = undefined; + +/** + * Server constructor. + * + * @param {http.Server|Number|Object} srv http server, port or options + * @param {Object} [opts] + * @api public + */ + +function Server(srv, opts){ + if (!(this instanceof Server)) return new Server(srv, opts); + if ('object' == typeof srv && srv instanceof Object && !srv.listen) { + opts = srv; + srv = null; + } + opts = opts || {}; + this.nsps = {}; + this.path(opts.path || '/socket.io'); + this.serveClient(false !== opts.serveClient); + this.parser = opts.parser || parser; + this.encoder = new this.parser.Encoder(); + this.adapter(opts.adapter || Adapter); + this.origins(opts.origins || '*:*'); + this.sockets = this.of('/'); + if (srv) this.attach(srv, opts); +} + +/** + * Server request verification function, that checks for allowed origins + * + * @param {http.IncomingMessage} req request + * @param {Function} fn callback to be called with the result: `fn(err, success)` + */ + +Server.prototype.checkRequest = function(req, fn) { + var origin = req.headers.origin || req.headers.referer; + + // file:// URLs produce a null Origin which can't be authorized via echo-back + if ('null' == origin || null == origin) origin = '*'; + + if (!!origin && typeof(this._origins) == 'function') return this._origins(origin, fn); + if (this._origins.indexOf('*:*') !== -1) return fn(null, true); + if (origin) { + try { + var parts = url.parse(origin); + var defaultPort = 'https:' == parts.protocol ? 443 : 80; + parts.port = parts.port != null + ? parts.port + : defaultPort; + var ok = + ~this._origins.indexOf(parts.hostname + ':' + parts.port) || + ~this._origins.indexOf(parts.hostname + ':*') || + ~this._origins.indexOf('*:' + parts.port); + return fn(null, !!ok); + } catch (ex) { + } + } + fn(null, false); +}; + +/** + * Sets/gets whether client code is being served. + * + * @param {Boolean} v whether to serve client code + * @return {Server|Boolean} self when setting or value when getting + * @api public + */ + +Server.prototype.serveClient = function(v){ + if (!arguments.length) return this._serveClient; + this._serveClient = v; + var resolvePath = function(file){ + var filepath = path.resolve(__dirname, './../../', file); + if (exists(filepath)) { + return filepath; + } + return require.resolve(file); + }; + if (v && !clientSource) { + clientSource = read(resolvePath( 'socket.io-client/dist/socket.io.js'), 'utf-8'); + try { + clientSourceMap = read(resolvePath( 'socket.io-client/dist/socket.io.js.map'), 'utf-8'); + } catch(err) { + debug('could not load sourcemap file'); + } + } + return this; +}; + +/** + * Old settings for backwards compatibility + */ + +var oldSettings = { + "transports": "transports", + "heartbeat timeout": "pingTimeout", + "heartbeat interval": "pingInterval", + "destroy buffer size": "maxHttpBufferSize" +}; + +/** + * Backwards compatibility. + * + * @api public + */ + +Server.prototype.set = function(key, val){ + if ('authorization' == key && val) { + this.use(function(socket, next) { + val(socket.request, function(err, authorized) { + if (err) return next(new Error(err)); + if (!authorized) return next(new Error('Not authorized')); + next(); + }); + }); + } else if ('origins' == key && val) { + this.origins(val); + } else if ('resource' == key) { + this.path(val); + } else if (oldSettings[key] && this.eio[oldSettings[key]]) { + this.eio[oldSettings[key]] = val; + } else { + console.error('Option %s is not valid. Please refer to the README.', key); + } + + return this; +}; + +/** + * Sets the client serving path. + * + * @param {String} v pathname + * @return {Server|String} self when setting or value when getting + * @api public + */ + +Server.prototype.path = function(v){ + if (!arguments.length) return this._path; + this._path = v.replace(/\/$/, ''); + return this; +}; + +/** + * Sets the adapter for rooms. + * + * @param {Adapter} v pathname + * @return {Server|Adapter} self when setting or value when getting + * @api public + */ + +Server.prototype.adapter = function(v){ + if (!arguments.length) return this._adapter; + this._adapter = v; + for (var i in this.nsps) { + if (this.nsps.hasOwnProperty(i)) { + this.nsps[i].initAdapter(); + } + } + return this; +}; + +/** + * Sets the allowed origins for requests. + * + * @param {String} v origins + * @return {Server|Adapter} self when setting or value when getting + * @api public + */ + +Server.prototype.origins = function(v){ + if (!arguments.length) return this._origins; + + this._origins = v; + return this; +}; + +/** + * Attaches socket.io to a server or port. + * + * @param {http.Server|Number} server or port + * @param {Object} options passed to engine.io + * @return {Server} self + * @api public + */ + +Server.prototype.listen = +Server.prototype.attach = function(srv, opts){ + if ('function' == typeof srv) { + var msg = 'You are trying to attach socket.io to an express ' + + 'request handler function. Please pass a http.Server instance.'; + throw new Error(msg); + } + + // handle a port as a string + if (Number(srv) == srv) { + srv = Number(srv); + } + + if ('number' == typeof srv) { + debug('creating http server and binding to %d', srv); + var port = srv; + srv = http.Server(function(req, res){ + res.writeHead(404); + res.end(); + }); + srv.listen(port); + + } + + // set engine.io path to `/socket.io` + opts = opts || {}; + opts.path = opts.path || this.path(); + // set origins verification + opts.allowRequest = opts.allowRequest || this.checkRequest.bind(this); + + if (this.sockets.fns.length > 0) { + this.initEngine(srv, opts); + return this; + } + + var self = this; + var connectPacket = { type: parser.CONNECT, nsp: '/' }; + this.encoder.encode(connectPacket, function (encodedPacket){ + // the CONNECT packet will be merged with Engine.IO handshake, + // to reduce the number of round trips + opts.initialPacket = encodedPacket; + + self.initEngine(srv, opts); + }); + return this; +}; + +/** + * Initialize engine + * + * @param {Object} options passed to engine.io + * @api private + */ + +Server.prototype.initEngine = function(srv, opts){ + // initialize engine + debug('creating engine.io instance with opts %j', opts); + this.eio = engine.attach(srv, opts); + + // attach static file serving + if (this._serveClient) this.attachServe(srv); + + // Export http server + this.httpServer = srv; + + // bind to engine events + this.bind(this.eio); +}; + +/** + * Attaches the static file serving. + * + * @param {Function|http.Server} srv http server + * @api private + */ + +Server.prototype.attachServe = function(srv){ + debug('attaching client serving req handler'); + var url = this._path + '/socket.io.js'; + var urlMap = this._path + '/socket.io.js.map'; + var evs = srv.listeners('request').slice(0); + var self = this; + srv.removeAllListeners('request'); + srv.on('request', function(req, res) { + if (0 === req.url.indexOf(urlMap)) { + self.serveMap(req, res); + } else if (0 === req.url.indexOf(url)) { + self.serve(req, res); + } else { + for (var i = 0; i < evs.length; i++) { + evs[i].call(srv, req, res); + } + } + }); +}; + +/** + * Handles a request serving `/socket.io.js` + * + * @param {http.Request} req + * @param {http.Response} res + * @api private + */ + +Server.prototype.serve = function(req, res){ + // Per the standard, ETags must be quoted: + // https://tools.ietf.org/html/rfc7232#section-2.3 + var expectedEtag = '"' + clientVersion + '"'; + + var etag = req.headers['if-none-match']; + if (etag) { + if (expectedEtag == etag) { + debug('serve client 304'); + res.writeHead(304); + res.end(); + return; + } + } + + debug('serve client source'); + res.setHeader('Content-Type', 'application/javascript'); + res.setHeader('ETag', expectedEtag); + res.writeHead(200); + res.end(clientSource); +}; + +/** + * Handles a request serving `/socket.io.js.map` + * + * @param {http.Request} req + * @param {http.Response} res + * @api private + */ + +Server.prototype.serveMap = function(req, res){ + // Per the standard, ETags must be quoted: + // https://tools.ietf.org/html/rfc7232#section-2.3 + var expectedEtag = '"' + clientVersion + '"'; + + var etag = req.headers['if-none-match']; + if (etag) { + if (expectedEtag == etag) { + debug('serve client 304'); + res.writeHead(304); + res.end(); + return; + } + } + + debug('serve client sourcemap'); + res.setHeader('Content-Type', 'application/json'); + res.setHeader('ETag', expectedEtag); + res.writeHead(200); + res.end(clientSourceMap); +}; + +/** + * Binds socket.io to an engine.io instance. + * + * @param {engine.Server} engine engine.io (or compatible) server + * @return {Server} self + * @api public + */ + +Server.prototype.bind = function(engine){ + this.engine = engine; + this.engine.on('connection', this.onconnection.bind(this)); + return this; +}; + +/** + * Called with each incoming transport connection. + * + * @param {engine.Socket} conn + * @return {Server} self + * @api public + */ + +Server.prototype.onconnection = function(conn){ + debug('incoming connection with id %s', conn.id); + var client = new Client(this, conn); + client.connect('/'); + return this; +}; + +/** + * Looks up a namespace. + * + * @param {String} name nsp name + * @param {Function} [fn] optional, nsp `connection` ev handler + * @api public + */ + +Server.prototype.of = function(name, fn){ + if (String(name)[0] !== '/') name = '/' + name; + + var nsp = this.nsps[name]; + if (!nsp) { + debug('initializing namespace %s', name); + nsp = new Namespace(this, name); + this.nsps[name] = nsp; + } + if (fn) nsp.on('connect', fn); + return nsp; +}; + +/** + * Closes server connection + * + * @param {Function} [fn] optional, called as `fn([err])` on error OR all conns closed + * @api public + */ + +Server.prototype.close = function(fn){ + for (var id in this.nsps['/'].sockets) { + if (this.nsps['/'].sockets.hasOwnProperty(id)) { + this.nsps['/'].sockets[id].onclose(); + } + } + + this.engine.close(); + + if (this.httpServer) { + this.httpServer.close(fn); + } else { + fn && fn(); + } +}; + +/** + * Expose main namespace (/). + */ + +var emitterMethods = Object.keys(Emitter.prototype).filter(function(key){ + return typeof Emitter.prototype[key] === 'function'; +}); + +emitterMethods.concat(['to', 'in', 'use', 'send', 'write', 'clients', 'compress']).forEach(function(fn){ + Server.prototype[fn] = function(){ + return this.sockets[fn].apply(this.sockets, arguments); + }; +}); + +Namespace.flags.forEach(function(flag){ + Object.defineProperty(Server.prototype, flag, { + get: function() { + this.sockets.flags = this.sockets.flags || {}; + this.sockets.flags[flag] = true; + return this; + } + }); +}); + +/** + * BC with `io.listen` + */ + +Server.listen = Server; diff --git a/node_modules/socket.io/lib/namespace.js b/node_modules/socket.io/lib/namespace.js new file mode 100644 index 0000000..0b0657b --- /dev/null +++ b/node_modules/socket.io/lib/namespace.js @@ -0,0 +1,279 @@ + +/** + * Module dependencies. + */ + +var Socket = require('./socket'); +var Emitter = require('events').EventEmitter; +var parser = require('socket.io-parser'); +var debug = require('debug')('socket.io:namespace'); + +/** + * Module exports. + */ + +module.exports = exports = Namespace; + +/** + * Blacklisted events. + */ + +exports.events = [ + 'connect', // for symmetry with client + 'connection', + 'newListener' +]; + +/** + * Flags. + */ + +exports.flags = [ + 'json', + 'volatile', + 'local' +]; + +/** + * `EventEmitter#emit` reference. + */ + +var emit = Emitter.prototype.emit; + +/** + * Namespace constructor. + * + * @param {Server} server instance + * @param {Socket} name + * @api private + */ + +function Namespace(server, name){ + this.name = name; + this.server = server; + this.sockets = {}; + this.connected = {}; + this.fns = []; + this.ids = 0; + this.rooms = []; + this.flags = {}; + this.initAdapter(); +} + +/** + * Inherits from `EventEmitter`. + */ + +Namespace.prototype.__proto__ = Emitter.prototype; + +/** + * Apply flags from `Socket`. + */ + +exports.flags.forEach(function(flag){ + Object.defineProperty(Namespace.prototype, flag, { + get: function() { + this.flags[flag] = true; + return this; + } + }); +}); + +/** + * Initializes the `Adapter` for this nsp. + * Run upon changing adapter by `Server#adapter` + * in addition to the constructor. + * + * @api private + */ + +Namespace.prototype.initAdapter = function(){ + this.adapter = new (this.server.adapter())(this); +}; + +/** + * Sets up namespace middleware. + * + * @return {Namespace} self + * @api public + */ + +Namespace.prototype.use = function(fn){ + if (this.server.eio) { + debug('removing initial packet'); + delete this.server.eio.initialPacket; + } + this.fns.push(fn); + return this; +}; + +/** + * Executes the middleware for an incoming client. + * + * @param {Socket} socket that will get added + * @param {Function} fn last fn call in the middleware + * @api private + */ + +Namespace.prototype.run = function(socket, fn){ + var fns = this.fns.slice(0); + if (!fns.length) return fn(null); + + function run(i){ + fns[i](socket, function(err){ + // upon error, short-circuit + if (err) return fn(err); + + // if no middleware left, summon callback + if (!fns[i + 1]) return fn(null); + + // go on to next + run(i + 1); + }); + } + + run(0); +}; + +/** + * Targets a room when emitting. + * + * @param {String} name + * @return {Namespace} self + * @api public + */ + +Namespace.prototype.to = +Namespace.prototype.in = function(name){ + if (!~this.rooms.indexOf(name)) this.rooms.push(name); + return this; +}; + +/** + * Adds a new client. + * + * @return {Socket} + * @api private + */ + +Namespace.prototype.add = function(client, query, fn){ + debug('adding socket to nsp %s', this.name); + var socket = new Socket(this, client, query); + var self = this; + this.run(socket, function(err){ + process.nextTick(function(){ + if ('open' == client.conn.readyState) { + if (err) return socket.error(err.data || err.message); + + // track socket + self.sockets[socket.id] = socket; + + // it's paramount that the internal `onconnect` logic + // fires before user-set events to prevent state order + // violations (such as a disconnection before the connection + // logic is complete) + socket.onconnect(); + if (fn) fn(); + + // fire user-set events + self.emit('connect', socket); + self.emit('connection', socket); + } else { + debug('next called after client was closed - ignoring socket'); + } + }); + }); + return socket; +}; + +/** + * Removes a client. Called by each `Socket`. + * + * @api private + */ + +Namespace.prototype.remove = function(socket){ + if (this.sockets.hasOwnProperty(socket.id)) { + delete this.sockets[socket.id]; + } else { + debug('ignoring remove for %s', socket.id); + } +}; + +/** + * Emits to all clients. + * + * @return {Namespace} self + * @api public + */ + +Namespace.prototype.emit = function(ev){ + if (~exports.events.indexOf(ev)) { + emit.apply(this, arguments); + return this; + } + // set up packet object + var args = Array.prototype.slice.call(arguments); + var packet = { type: parser.EVENT, data: args }; + + if ('function' == typeof args[args.length - 1]) { + throw new Error('Callbacks are not supported when broadcasting'); + } + + var rooms = this.rooms.slice(0); + var flags = Object.assign({}, this.flags); + + // reset flags + this.rooms = []; + this.flags = {}; + + this.adapter.broadcast(packet, { + rooms: rooms, + flags: flags + }); + + return this; +}; + +/** + * Sends a `message` event to all clients. + * + * @return {Namespace} self + * @api public + */ + +Namespace.prototype.send = +Namespace.prototype.write = function(){ + var args = Array.prototype.slice.call(arguments); + args.unshift('message'); + this.emit.apply(this, args); + return this; +}; + +/** + * Gets a list of clients. + * + * @return {Namespace} self + * @api public + */ + +Namespace.prototype.clients = function(fn){ + this.adapter.clients(this.rooms, fn); + // reset rooms for scenario: + // .in('room').clients() (GH-1978) + this.rooms = []; + return this; +}; + +/** + * Sets the compress flag. + * + * @param {Boolean} compress if `true`, compresses the sending data + * @return {Socket} self + * @api public + */ + +Namespace.prototype.compress = function(compress){ + this.flags.compress = compress; + return this; +}; diff --git a/node_modules/socket.io/lib/socket.js b/node_modules/socket.io/lib/socket.js new file mode 100644 index 0000000..6c6bcde --- /dev/null +++ b/node_modules/socket.io/lib/socket.js @@ -0,0 +1,557 @@ + +/** + * Module dependencies. + */ + +var Emitter = require('events').EventEmitter; +var parser = require('socket.io-parser'); +var url = require('url'); +var debug = require('debug')('socket.io:socket'); + +/** + * Module exports. + */ + +module.exports = exports = Socket; + +/** + * Blacklisted events. + * + * @api public + */ + +exports.events = [ + 'error', + 'connect', + 'disconnect', + 'disconnecting', + 'newListener', + 'removeListener' +]; + +/** + * Flags. + * + * @api private + */ + +var flags = [ + 'json', + 'volatile', + 'broadcast' +]; + +/** + * `EventEmitter#emit` reference. + */ + +var emit = Emitter.prototype.emit; + +/** + * Interface to a `Client` for a given `Namespace`. + * + * @param {Namespace} nsp + * @param {Client} client + * @api public + */ + +function Socket(nsp, client, query){ + this.nsp = nsp; + this.server = nsp.server; + this.adapter = this.nsp.adapter; + this.id = nsp.name !== '/' ? nsp.name + '#' + client.id : client.id; + this.client = client; + this.conn = client.conn; + this.rooms = {}; + this.acks = {}; + this.connected = true; + this.disconnected = false; + this.handshake = this.buildHandshake(query); + this.fns = []; + this.flags = {}; + this._rooms = []; +} + +/** + * Inherits from `EventEmitter`. + */ + +Socket.prototype.__proto__ = Emitter.prototype; + +/** + * Apply flags from `Socket`. + */ + +flags.forEach(function(flag){ + Object.defineProperty(Socket.prototype, flag, { + get: function() { + this.flags[flag] = true; + return this; + } + }); +}); + +/** + * `request` engine.io shortcut. + * + * @api public + */ + +Object.defineProperty(Socket.prototype, 'request', { + get: function() { + return this.conn.request; + } +}); + +/** + * Builds the `handshake` BC object + * + * @api private + */ + +Socket.prototype.buildHandshake = function(query){ + var self = this; + function buildQuery(){ + var requestQuery = url.parse(self.request.url, true).query; + //if socket-specific query exist, replace query strings in requestQuery + return Object.assign({}, query, requestQuery); + } + return { + headers: this.request.headers, + time: (new Date) + '', + address: this.conn.remoteAddress, + xdomain: !!this.request.headers.origin, + secure: !!this.request.connection.encrypted, + issued: +(new Date), + url: this.request.url, + query: buildQuery() + }; +}; + +/** + * Emits to this client. + * + * @return {Socket} self + * @api public + */ + +Socket.prototype.emit = function(ev){ + if (~exports.events.indexOf(ev)) { + emit.apply(this, arguments); + return this; + } + + var args = Array.prototype.slice.call(arguments); + var packet = { + type: parser.EVENT, + data: args + }; + + // access last argument to see if it's an ACK callback + if (typeof args[args.length - 1] === 'function') { + if (this._rooms.length || this.flags.broadcast) { + throw new Error('Callbacks are not supported when broadcasting'); + } + + debug('emitting packet with ack id %d', this.nsp.ids); + this.acks[this.nsp.ids] = args.pop(); + packet.id = this.nsp.ids++; + } + + var rooms = this._rooms.slice(0); + var flags = Object.assign({}, this.flags); + + // reset flags + this._rooms = []; + this.flags = {}; + + if (rooms.length || flags.broadcast) { + this.adapter.broadcast(packet, { + except: [this.id], + rooms: rooms, + flags: flags + }); + } else { + // dispatch packet + this.packet(packet, flags); + } + return this; +}; + +/** + * Targets a room when broadcasting. + * + * @param {String} name + * @return {Socket} self + * @api public + */ + +Socket.prototype.to = +Socket.prototype.in = function(name){ + if (!~this._rooms.indexOf(name)) this._rooms.push(name); + return this; +}; + +/** + * Sends a `message` event. + * + * @return {Socket} self + * @api public + */ + +Socket.prototype.send = +Socket.prototype.write = function(){ + var args = Array.prototype.slice.call(arguments); + args.unshift('message'); + this.emit.apply(this, args); + return this; +}; + +/** + * Writes a packet. + * + * @param {Object} packet object + * @param {Object} opts options + * @api private + */ + +Socket.prototype.packet = function(packet, opts){ + packet.nsp = this.nsp.name; + opts = opts || {}; + opts.compress = false !== opts.compress; + this.client.packet(packet, opts); +}; + +/** + * Joins a room. + * + * @param {String|Array} room or array of rooms + * @param {Function} fn optional, callback + * @return {Socket} self + * @api private + */ + +Socket.prototype.join = function(rooms, fn){ + debug('joining room %s', rooms); + var self = this; + if (!Array.isArray(rooms)) { + rooms = [rooms]; + } + rooms = rooms.filter(function (room) { + return !self.rooms.hasOwnProperty(room); + }); + if (!rooms.length) { + fn && fn(null); + return this; + } + this.adapter.addAll(this.id, rooms, function(err){ + if (err) return fn && fn(err); + debug('joined room %s', rooms); + rooms.forEach(function (room) { + self.rooms[room] = room; + }); + fn && fn(null); + }); + return this; +}; + +/** + * Leaves a room. + * + * @param {String} room + * @param {Function} fn optional, callback + * @return {Socket} self + * @api private + */ + +Socket.prototype.leave = function(room, fn){ + debug('leave room %s', room); + var self = this; + this.adapter.del(this.id, room, function(err){ + if (err) return fn && fn(err); + debug('left room %s', room); + delete self.rooms[room]; + fn && fn(null); + }); + return this; +}; + +/** + * Leave all rooms. + * + * @api private + */ + +Socket.prototype.leaveAll = function(){ + this.adapter.delAll(this.id); + this.rooms = {}; +}; + +/** + * Called by `Namespace` upon successful + * middleware execution (ie: authorization). + * Socket is added to namespace array before + * call to join, so adapters can access it. + * + * @api private + */ + +Socket.prototype.onconnect = function(){ + debug('socket connected - writing packet'); + this.nsp.connected[this.id] = this; + this.join(this.id); + var skip = this.nsp.name === '/' && this.nsp.fns.length === 0; + if (skip) { + debug('packet already sent in initial handshake'); + } else { + this.packet({ type: parser.CONNECT }); + } +}; + +/** + * Called with each packet. Called by `Client`. + * + * @param {Object} packet + * @api private + */ + +Socket.prototype.onpacket = function(packet){ + debug('got packet %j', packet); + switch (packet.type) { + case parser.EVENT: + this.onevent(packet); + break; + + case parser.BINARY_EVENT: + this.onevent(packet); + break; + + case parser.ACK: + this.onack(packet); + break; + + case parser.BINARY_ACK: + this.onack(packet); + break; + + case parser.DISCONNECT: + this.ondisconnect(); + break; + + case parser.ERROR: + this.onerror(new Error(packet.data)); + } +}; + +/** + * Called upon event packet. + * + * @param {Object} packet object + * @api private + */ + +Socket.prototype.onevent = function(packet){ + var args = packet.data || []; + debug('emitting event %j', args); + + if (null != packet.id) { + debug('attaching ack callback to event'); + args.push(this.ack(packet.id)); + } + + this.dispatch(args); +}; + +/** + * Produces an ack callback to emit with an event. + * + * @param {Number} id packet id + * @api private + */ + +Socket.prototype.ack = function(id){ + var self = this; + var sent = false; + return function(){ + // prevent double callbacks + if (sent) return; + var args = Array.prototype.slice.call(arguments); + debug('sending ack %j', args); + + self.packet({ + id: id, + type: parser.ACK, + data: args + }); + + sent = true; + }; +}; + +/** + * Called upon ack packet. + * + * @api private + */ + +Socket.prototype.onack = function(packet){ + var ack = this.acks[packet.id]; + if ('function' == typeof ack) { + debug('calling ack %s with %j', packet.id, packet.data); + ack.apply(this, packet.data); + delete this.acks[packet.id]; + } else { + debug('bad ack %s', packet.id); + } +}; + +/** + * Called upon client disconnect packet. + * + * @api private + */ + +Socket.prototype.ondisconnect = function(){ + debug('got disconnect packet'); + this.onclose('client namespace disconnect'); +}; + +/** + * Handles a client error. + * + * @api private + */ + +Socket.prototype.onerror = function(err){ + if (this.listeners('error').length) { + this.emit('error', err); + } else { + console.error('Missing error handler on `socket`.'); + console.error(err.stack); + } +}; + +/** + * Called upon closing. Called by `Client`. + * + * @param {String} reason + * @throw {Error} optional error object + * @api private + */ + +Socket.prototype.onclose = function(reason){ + if (!this.connected) return this; + debug('closing socket - reason %s', reason); + this.emit('disconnecting', reason); + this.leaveAll(); + this.nsp.remove(this); + this.client.remove(this); + this.connected = false; + this.disconnected = true; + delete this.nsp.connected[this.id]; + this.emit('disconnect', reason); +}; + +/** + * Produces an `error` packet. + * + * @param {Object} err error object + * @api private + */ + +Socket.prototype.error = function(err){ + this.packet({ type: parser.ERROR, data: err }); +}; + +/** + * Disconnects this client. + * + * @param {Boolean} close if `true`, closes the underlying connection + * @return {Socket} self + * @api public + */ + +Socket.prototype.disconnect = function(close){ + if (!this.connected) return this; + if (close) { + this.client.disconnect(); + } else { + this.packet({ type: parser.DISCONNECT }); + this.onclose('server namespace disconnect'); + } + return this; +}; + +/** + * Sets the compress flag. + * + * @param {Boolean} compress if `true`, compresses the sending data + * @return {Socket} self + * @api public + */ + +Socket.prototype.compress = function(compress){ + this.flags.compress = compress; + return this; +}; + +/** + * Dispatch incoming event to socket listeners. + * + * @param {Array} event that will get emitted + * @api private + */ + +Socket.prototype.dispatch = function(event){ + debug('dispatching an event %j', event); + var self = this; + function dispatchSocket(err) { + process.nextTick(function(){ + if (err) { + return self.error(err.data || err.message); + } + emit.apply(self, event); + }); + } + this.run(event, dispatchSocket); +}; + +/** + * Sets up socket middleware. + * + * @param {Function} middleware function (event, next) + * @return {Socket} self + * @api public + */ + +Socket.prototype.use = function(fn){ + this.fns.push(fn); + return this; +}; + +/** + * Executes the middleware for an incoming event. + * + * @param {Array} event that will get emitted + * @param {Function} last fn call in the middleware + * @api private + */ +Socket.prototype.run = function(event, fn){ + var fns = this.fns.slice(0); + if (!fns.length) return fn(null); + + function run(i){ + fns[i](event, function(err){ + // upon error, short-circuit + if (err) return fn(err); + + // if no middleware left, summon callback + if (!fns[i + 1]) return fn(null); + + // go on to next + run(i + 1); + }); + } + + run(0); +}; -- cgit v1.2.3