UNPKG

socketcluster-client

Version:
1,912 lines (1,548 loc) 57.6 kB
(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.socketCluster = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ var SCSocket = require('./lib/scsocket'); module.exports.SCSocket = SCSocket; module.exports.SCEmitter = require('sc-emitter').SCEmitter; module.exports.connect = function (options) { return new SCSocket(options); }; module.exports.version = '2.3.18'; },{"./lib/scsocket":5,"sc-emitter":11}],2:[function(require,module,exports){ (function (global){ var AuthEngine = function () { this._internalStorage = {}; }; AuthEngine.prototype._isLocalStorageEnabled = function () { var err; try { // Some browsers will throw an error here if localStorage is disabled. global.localStorage; } catch (e) { err = e; } return !err; }; AuthEngine.prototype.saveToken = function (name, token, options, callback) { if (this._isLocalStorageEnabled() && global.localStorage) { global.localStorage.setItem(name, token); } else { this._internalStorage[name] = token; } callback && callback(); }; AuthEngine.prototype.removeToken = function (name, callback) { if (this._isLocalStorageEnabled() && global.localStorage) { global.localStorage.removeItem(name); } delete this._internalStorage[name]; callback && callback(); }; AuthEngine.prototype.loadToken = function (name, callback) { var token; if (this._isLocalStorageEnabled() && global.localStorage) { token = global.localStorage.getItem(name); } else { token = this._internalStorage[name] || null; } callback(null, token); }; module.exports.AuthEngine = AuthEngine; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{}],3:[function(require,module,exports){ module.exports.create = (function () { function F() {}; return function (o) { if (arguments.length != 1) { throw new Error('Object.create implementation only accepts one parameter.'); } F.prototype = o; return new F(); } })(); },{}],4:[function(require,module,exports){ var Response = function (socket, id) { this.socket = socket; this.id = id; }; Response.prototype._respond = function (responseData) { this.socket.send(this.socket.stringify(responseData)); }; Response.prototype.end = function (data) { if (this.id) { var responseData = { rid: this.id }; if (data !== undefined) { responseData.data = data; } this._respond(responseData); } }; Response.prototype.error = function (error, data) { if (this.id) { var err; if (error instanceof Error) { err = {name: error.name, message: error.message, stack: error.stack}; } else { err = error; } var responseData = { rid: this.id, error: err }; if (data !== undefined) { responseData.data = data; } this._respond(responseData); } }; Response.prototype.callback = function (error, data) { if (error) { this.error(error, data); } else { this.end(data); } }; module.exports.Response = Response; },{}],5:[function(require,module,exports){ (function (global){ var SCEmitter = require('sc-emitter').SCEmitter; var SCChannel = require('sc-channel').SCChannel; var Response = require('./response').Response; var AuthEngine = require('./auth').AuthEngine; var SCTransport = require('./sctransport').SCTransport; var querystring = require('querystring'); var LinkedList = require('linked-list'); if (!Object.create) { Object.create = require('./objectcreate'); } var isBrowser = typeof window != 'undefined'; var SCSocket = function (options) { var self = this; SCEmitter.call(this); var opts = { port: null, autoReconnect: true, autoProcessSubscriptions: true, ackTimeout: 10000, hostname: global.location && location.hostname, path: '/socketcluster/', secure: global.location && location.protocol == 'https:', timestampRequests: false, timestampParam: 't', authEngine: null, authTokenName: 'socketCluster.authToken', binaryType: 'arraybuffer' }; for (var i in options) { if (options.hasOwnProperty(i)) { opts[i] = options[i]; } } this.id = null; this.state = this.CLOSED; this.pendingConnectCallback = false; this.ackTimeout = opts.ackTimeout; // pingTimeout will be ackTimeout at the start, but it will // be updated with values provided by the 'connect' event this.pingTimeout = this.ackTimeout; var maxTimeout = Math.pow(2, 31) - 1; var verifyDuration = function (propertyName) { if (self[propertyName] > maxTimeout) { throw new Error('The ' + propertyName + ' value provided exceeded the maximum amount allowed'); } }; verifyDuration('ackTimeout'); verifyDuration('pingTimeout'); this._localEvents = { 'connect': 1, 'connectAbort': 1, 'disconnect': 1, 'message': 1, 'error': 1, 'raw': 1, 'fail': 1, 'kickOut': 1, 'subscribe': 1, 'unsubscribe': 1, 'authenticate': 1, 'removeAuthToken': 1 }; this._connectAttempts = 0; this._emitBuffer = new LinkedList(); this._channels = {}; this.options = opts; this._cid = 1; this.options.callIdGenerator = function () { return self._callIdGenerator(); }; if (this.options.autoReconnect) { if (this.options.autoReconnectOptions == null) { this.options.autoReconnectOptions = {}; } var reconnectOptions = this.options.autoReconnectOptions; if (reconnectOptions.initialDelay == null) { reconnectOptions.initialDelay = 10000; } if (reconnectOptions.randomness == null) { reconnectOptions.randomness = 10000; } if (reconnectOptions.multiplier == null) { reconnectOptions.multiplier = 1.5; } if (reconnectOptions.maxDelay == null) { reconnectOptions.maxDelay = 60000; } } if (this.options.subscriptionRetryOptions == null) { this.options.subscriptionRetryOptions = {}; } if (this.options.authEngine) { this.auth = this.options.authEngine; } else { this.auth = new AuthEngine(); } this.options.path = this.options.path.replace(/\/$/, '') + '/'; this.options.query = opts.query || {}; if (typeof this.options.query == 'string') { this.options.query = querystring.parse(this.options.query); } this.options.port = opts.port || (global.location && location.port ? location.port : (this.options.secure ? 443 : 80)); this.connect(); this._channelEmitter = new SCEmitter(); if (isBrowser) { var unloadHandler = function () { self.disconnect(); }; if (global.attachEvent) { global.attachEvent('onunload', unloadHandler); } else if (global.addEventListener) { global.addEventListener('beforeunload', unloadHandler, false); } } }; SCSocket.prototype = Object.create(SCEmitter.prototype); SCSocket.CONNECTING = SCSocket.prototype.CONNECTING = SCTransport.prototype.CONNECTING; SCSocket.OPEN = SCSocket.prototype.OPEN = SCTransport.prototype.OPEN; SCSocket.CLOSED = SCSocket.prototype.CLOSED = SCTransport.prototype.CLOSED; SCSocket.ignoreStatuses = { 1000: 'Socket closed normally', 1001: 'Socket hung up' }; SCSocket.errorStatuses = { 1001: 'Socket was disconnected', 1002: 'A WebSocket protocol error was encountered', 1003: 'Server terminated socket because it received invalid data', 1005: 'Socket closed without status code', 1006: 'Socket hung up', 1007: 'Message format was incorrect', 1008: 'Encountered a policy violation', 1009: 'Message was too big to process', 1010: 'Client ended the connection because the server did not comply with extension requirements', 1011: 'Server encountered an unexpected fatal condition', 4000: 'Server ping timed out', 4001: 'Client pong timed out', 4002: 'Server failed to sign auth token', 4003: 'Failed to complete handshake', 4004: 'Client failed to save auth token' }; SCSocket.prototype._privateEventHandlerMap = { '#fail': function (data) { this._onSCError(data); }, '#publish': function (data) { var isSubscribed = this.isSubscribed(data.channel, true); if (isSubscribed) { this._channelEmitter.emit(data.channel, data.data); } }, '#kickOut': function (data) { var channelName = data.channel; var channel = this._channels[channelName]; if (channel) { SCEmitter.prototype.emit.call(this, 'kickOut', data.message, channelName); channel.emit('kickOut', data.message, channelName); this._triggerChannelUnsubscribe(channel); } }, '#setAuthToken': function (data, response) { var self = this; if (data) { var triggerAuthenticate = function (err) { if (err) { // This is a non-fatal error, we don't want to close the connection // because of this but we do want to notify the server and throw an error // on the client. response.error(err.message || err); self._onSCError(err); } else { SCEmitter.prototype.emit.call(self, 'authenticate', data.token); response.end(); } }; this.auth.saveToken(this.options.authTokenName, data.token, {}, triggerAuthenticate); } else { response.error('No token data provided by #setAuthToken event'); } }, '#removeAuthToken': function (data, response) { var self = this; this.auth.removeToken(this.options.authTokenName, function (err) { if (err) { // Non-fatal error - Do not close the connection response.error(err.message || err); self._onSCError(err); } else { SCEmitter.prototype.emit.call(self, 'removeAuthToken'); response.end(); } }); }, '#disconnect': function (data) { this.transport.close(data.code, data.data); } }; SCSocket.prototype._callIdGenerator = function () { return this._cid++; }; SCSocket.prototype.getState = function () { return this.state; }; SCSocket.prototype.getBytesReceived = function () { return this.transport.getBytesReceived(); }; SCSocket.prototype.removeAuthToken = function (callback) { var self = this; this.auth.removeToken(this.options.authTokenName, function (err) { callback && callback(err); if (err) { // Non-fatal error - Do not close the connection self._onSCError(err); } else { SCEmitter.prototype.emit.call(self, 'removeAuthToken'); } }); }; SCSocket.prototype.connect = SCSocket.prototype.open = function () { var self = this; if (this.state == this.CLOSED) { clearTimeout(this._reconnectTimeout); this.state = this.CONNECTING; if (this.transport) { this.transport.off(); } this.transport = new SCTransport(this.auth, this.options); this.transport.on('open', function (status) { self.state = self.OPEN; self._onSCOpen(status); }); this.transport.on('error', function (err) { self._onSCError(err); }); this.transport.on('close', function (code, data) { self.state = self.CLOSED; self._onSCClose(code, data); }); this.transport.on('openAbort', function (code, data) { self.state = self.CLOSED; self._onSCClose(code, data, true); }); this.transport.on('event', function (event, data, res) { self._onSCEvent(event, data, res); }); } }; SCSocket.prototype.reconnect = function () { this.disconnect(); this.connect(); }; SCSocket.prototype.disconnect = function (code, data) { code = code || 1000; if (this.state == this.OPEN) { var packet = { code: code, data: data }; this.transport.emit('#disconnect', packet); this.transport.close(code); } else if (this.state == this.CONNECTING) { this.transport.close(code); } }; // Perform client-initiated authentication by providing an encrypted token string SCSocket.prototype.authenticate = function (encryptedAuthToken, callback) { var self = this; this.emit('#authenticate', encryptedAuthToken, function (err, authStatus) { if (err) { callback && callback(err, authStatus); } else { self.auth.saveToken(self.options.authTokenName, encryptedAuthToken, {}, function (err) { callback && callback(err, authStatus); if (err) { self._onSCError(err); } else if (authStatus.isAuthenticated) { SCEmitter.prototype.emit.call(self, 'authenticate', encryptedAuthToken); } }); } }); }; SCSocket.prototype._tryReconnect = function (initialDelay) { var self = this; var exponent = this._connectAttempts++; var reconnectOptions = this.options.autoReconnectOptions; var timeout; if (initialDelay == null || exponent > 0) { var initialTimeout = Math.round(reconnectOptions.initialDelay + (reconnectOptions.randomness || 0) * Math.random()); timeout = Math.round(initialTimeout * Math.pow(reconnectOptions.multiplier, exponent)); } else { timeout = initialDelay; } if (timeout > reconnectOptions.maxDelay) { timeout = reconnectOptions.maxDelay; } clearTimeout(this._reconnectTimeout); this._reconnectTimeout = setTimeout(function () { self.connect(); }, timeout); }; SCSocket.prototype._onSCOpen = function (status) { var self = this; if (status) { this.id = status.id; this.pingTimeout = status.pingTimeout; this.transport.pingTimeout = this.pingTimeout; } this._connectAttempts = 0; if (this.options.autoProcessSubscriptions) { this.processPendingSubscriptions(); } else { this.pendingConnectCallback = true; } // If the user invokes the callback while in autoProcessSubscriptions mode, it // won't break anything - The processPendingSubscriptions() call will be a no-op. SCEmitter.prototype.emit.call(this, 'connect', status, function () { self.processPendingSubscriptions(); }); this._flushEmitBuffer(); }; SCSocket.prototype._onSCError = function (err) { var self = this; // Throw error in different stack frame so that error handling // cannot interfere with a reconnect action. setTimeout(function () { if (self.listeners('error').length < 1) { throw err; } else { SCEmitter.prototype.emit.call(self, 'error', err); } }, 0); }; SCSocket.prototype._suspendSubscriptions = function () { var channel, newState; for (var channelName in this._channels) { if (this._channels.hasOwnProperty(channelName)) { channel = this._channels[channelName]; if (channel.state == channel.SUBSCRIBED || channel.state == channel.PENDING) { newState = channel.PENDING; } else { newState = channel.UNSUBSCRIBED; } this._triggerChannelUnsubscribe(channel, newState); } } }; SCSocket.prototype._onSCClose = function (code, data, openAbort) { var self = this; this.id = null; if (this.transport) { this.transport.off(); } clearTimeout(this._reconnectTimeout); this._suspendSubscriptions(); if (openAbort) { SCEmitter.prototype.emit.call(self, 'connectAbort', code, data); } else { SCEmitter.prototype.emit.call(self, 'disconnect', code, data); } // Try to reconnect // on server ping timeout (4000) // or on client pong timeout (4001) // or on close without status (1005) // or on handshake failure (4003) // or on socket hung up (1006) if (this.options.autoReconnect) { if (code == 4000 || code == 4001 || code == 1005) { // If there is a ping or pong timeout or socket closes without // status, don't wait before trying to reconnect - These could happen // if the client wakes up after a period of inactivity and in this case we // want to re-establish the connection as soon as possible. this._tryReconnect(0); } else if (code != 1000) { this._tryReconnect(); } } if (!SCSocket.ignoreStatuses[code]) { var err = new Error(SCSocket.errorStatuses[code] || 'Socket connection failed for unknown reasons'); err.code = code; this._onSCError(err); } }; SCSocket.prototype._onSCEvent = function (event, data, res) { var handler = this._privateEventHandlerMap[event]; if (handler) { handler.call(this, data, res); } else { SCEmitter.prototype.emit.call(this, event, data, function () { res && res.callback.apply(res, arguments); }); } }; SCSocket.prototype.parse = function (message) { return this.transport.parse(message); }; SCSocket.prototype.stringify = function (object) { return this.transport.stringify(object); }; SCSocket.prototype._flushEmitBuffer = function () { var currentNode = this._emitBuffer.head; var nextNode; while (currentNode) { nextNode = currentNode.next; var eventObject = currentNode.data; currentNode.detach(); this.transport.emitRaw(eventObject); currentNode = nextNode; } }; SCSocket.prototype._handleEventAckTimeout = function (eventObject, eventNode) { var errorMessage = "Event response for '" + eventObject.event + "' timed out"; var error = new Error(errorMessage); error.type = 'timeout'; var callback = eventObject.callback; delete eventObject.callback; if (eventNode) { eventNode.detach(); } callback.call(eventObject, error, eventObject); this._onSCError(error); }; SCSocket.prototype._emit = function (event, data, callback) { var self = this; if (this.state == this.CLOSED) { this.connect(); } var eventObject = { event: event, data: data, callback: callback }; var eventNode = new LinkedList.Item(); eventNode.data = eventObject; // Events which do not have a callback will be treated as volatile if (callback) { eventObject.timeout = setTimeout(function () { self._handleEventAckTimeout(eventObject, eventNode); }, this.ackTimeout); } this._emitBuffer.append(eventNode); if (this.state == this.OPEN) { this._flushEmitBuffer(); } }; SCSocket.prototype.emit = function (event, data, callback) { if (this._localEvents[event] == null) { this._emit(event, data, callback); } else { SCEmitter.prototype.emit.call(this, event, data); } }; SCSocket.prototype.publish = function (channelName, data, callback) { var pubData = { channel: channelName, data: data }; this.emit('#publish', pubData, function (err) { callback && callback(err); }); }; SCSocket.prototype._triggerChannelSubscribe = function (channel) { var channelName = channel.name; if (channel.state != channel.SUBSCRIBED) { channel.state = channel.SUBSCRIBED; channel.emit('subscribe', channelName); SCEmitter.prototype.emit.call(this, 'subscribe', channelName); } }; SCSocket.prototype._triggerChannelSubscribeFail = function (err, channel) { var channelName = channel.name; if (channel.state != channel.UNSUBSCRIBED) { channel.state = channel.UNSUBSCRIBED; channel.emit('subscribeFail', err, channelName); SCEmitter.prototype.emit.call(this, 'subscribeFail', err, channelName); } }; // Cancel any pending subscribe callback SCSocket.prototype._cancelPendingSubscribeCallback = function (channel) { if (channel._pendingSubscriptionCid != null) { this.transport.cancelPendingResponse(channel._pendingSubscriptionCid); delete channel._pendingSubscriptionCid; } }; SCSocket.prototype._trySubscribe = function (channel) { var self = this; // We can only ever have one pending subscribe action at any given time on a channel if (this.state == this.OPEN && !this.pendingConnectCallback && channel._pendingSubscriptionCid == null) { var options = { noTimeout: true }; channel._pendingSubscriptionCid = this.transport.emit( '#subscribe', channel.name, options, function (err) { delete channel._pendingSubscriptionCid; if (err) { self._triggerChannelSubscribeFail(err, channel); } else { self._triggerChannelSubscribe(channel); } } ); } }; SCSocket.prototype.subscribe = function (channelName) { var channel = this._channels[channelName]; if (!channel) { channel = new SCChannel(channelName, this); this._channels[channelName] = channel; } if (channel.state == channel.UNSUBSCRIBED) { channel.state = channel.PENDING; this._trySubscribe(channel); } return channel; }; SCSocket.prototype._triggerChannelUnsubscribe = function (channel, newState) { var channelName = channel.name; var oldState = channel.state; if (newState) { channel.state = newState; } else { channel.state = channel.UNSUBSCRIBED; } this._cancelPendingSubscribeCallback(channel); if (oldState == channel.SUBSCRIBED) { channel.emit('unsubscribe', channelName); SCEmitter.prototype.emit.call(this, 'unsubscribe', channelName); } }; SCSocket.prototype._tryUnsubscribe = function (channel) { var self = this; if (this.state == this.OPEN) { var options = { noTimeout: true }; // If there is a pending subscribe action, cancel the callback this._cancelPendingSubscribeCallback(channel); // This operation cannot fail because the TCP protocol guarantees delivery // so long as the connection remains open. If the connection closes, // the server will automatically unsubscribe the socket and thus complete // the operation on the server side. this.transport.emit('#unsubscribe', channel.name, options); } }; SCSocket.prototype.unsubscribe = function (channelName) { var channel = this._channels[channelName]; if (channel) { if (channel.state != channel.UNSUBSCRIBED) { this._triggerChannelUnsubscribe(channel); this._tryUnsubscribe(channel); } } }; SCSocket.prototype.channel = function (channelName) { var currentChannel = this._channels[channelName]; if (!currentChannel) { currentChannel = new SCChannel(channelName, this); this._channels[channelName] = currentChannel; } return currentChannel; }; SCSocket.prototype.destroyChannel = function (channelName) { var channel = this._channels[channelName]; channel.unwatch(); channel.unsubscribe(); delete this._channels[channelName]; }; SCSocket.prototype.subscriptions = function (includePending) { var subs = []; var channel, includeChannel; for (var channelName in this._channels) { if (this._channels.hasOwnProperty(channelName)) { channel = this._channels[channelName]; if (includePending) { includeChannel = channel && (channel.state == channel.SUBSCRIBED || channel.state == channel.PENDING); } else { includeChannel = channel && channel.state == channel.SUBSCRIBED; } if (includeChannel) { subs.push(channelName); } } } return subs; }; SCSocket.prototype.isSubscribed = function (channel, includePending) { var channel = this._channels[channel]; if (includePending) { return !!channel && (channel.state == channel.SUBSCRIBED || channel.state == channel.PENDING); } return !!channel && channel.state == channel.SUBSCRIBED; }; SCSocket.prototype.processPendingSubscriptions = function () { var self = this; this.pendingConnectCallback = false; var channels = []; for (var channelName in this._channels) { if (this._channels.hasOwnProperty(channelName)) { channels.push(channelName); } } for (var i in this._channels) { if (this._channels.hasOwnProperty(i)) { (function (channel) { if (channel.state == channel.PENDING) { self._trySubscribe(channel); } })(this._channels[i]); } } }; SCSocket.prototype.watch = function (channelName, handler) { if (typeof handler != 'function') { throw new Error('No handler function was provided'); } this._channelEmitter.on(channelName, handler); }; SCSocket.prototype.unwatch = function (channelName, handler) { if (handler) { this._channelEmitter.removeListener(channelName, handler); } else { this._channelEmitter.removeAllListeners(channelName); } }; SCSocket.prototype.watchers = function (channelName) { return this._channelEmitter.listeners(channelName); }; module.exports = SCSocket; }).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) },{"./auth":2,"./objectcreate":3,"./response":4,"./sctransport":6,"linked-list":8,"querystring":18,"sc-channel":9,"sc-emitter":11}],6:[function(require,module,exports){ var WebSocket = require('sc-ws'); var SCEmitter = require('sc-emitter').SCEmitter; var formatter = require('sc-formatter'); var Response = require('./response').Response; var querystring = require('querystring'); var SCTransport = function (authEngine, options) { this.state = this.CLOSED; this.auth = authEngine; this.options = options; this.pingTimeout = options.ackTimeout; this.callIdGenerator = options.callIdGenerator; this._pingTimeoutTicker = null; this._callbackMap = {}; this.open(); }; SCTransport.prototype = Object.create(SCEmitter.prototype); SCTransport.CONNECTING = SCTransport.prototype.CONNECTING = 'connecting'; SCTransport.OPEN = SCTransport.prototype.OPEN = 'open'; SCTransport.CLOSED = SCTransport.prototype.CLOSED = 'closed'; SCTransport.prototype.uri = function () { var query = this.options.query || {}; var schema = this.options.secure ? 'wss' : 'ws'; var port = ''; if (this.options.port && (('wss' == schema && this.options.port != 443) || ('ws' == schema && this.options.port != 80))) { port = ':' + this.options.port; } if (this.options.timestampRequests) { query[this.options.timestampParam] = (new Date()).getTime(); } query = querystring.stringify(query); if (query.length) { query = '?' + query; } return schema + '://' + this.options.hostname + port + this.options.path + query; }; SCTransport.prototype.open = function () { var self = this; this.state = this.CONNECTING; var uri = this.uri(); var wsSocket = new WebSocket(uri, null, this.options); wsSocket.binaryType = this.options.binaryType; this.socket = wsSocket; wsSocket.onopen = function () { self._onOpen(); }; wsSocket.onclose = function (event) { self._onClose(event.code, event.reason); }; wsSocket.onmessage = function (message, flags) { self._onMessage(message.data); }; wsSocket.onerror = function (error) { // The onclose event will be called automatically after the onerror event // if the socket is connected - Otherwise, if it's in the middle of // connecting, we want to close it manually with a 1006 - This is necessary // to prevent inconsistent behavior when running the client in Node.js // vs in a browser. if (self.state === self.CONNECTING) { self._onClose(1006); } }; }; SCTransport.prototype._onOpen = function () { var self = this; this._resetPingTimeout(); this._handshake(function (err, status) { if (err) { self._onError(err); self._onClose(4003); self.socket.close(4003); } else { self.state = self.OPEN; SCEmitter.prototype.emit.call(self, 'open', status); self._resetPingTimeout(); } }); }; SCTransport.prototype._handshake = function (callback) { var self = this; this.auth.loadToken(this.options.authTokenName, function (err, token) { if (err) { callback(err); } else { // Don't wait for this.state to be 'open'. // The underlying WebSocket (this.socket) is already open. var options = { force: true }; self.emit('#handshake', { authToken: token }, options, callback); } }); }; SCTransport.prototype._onClose = function (code, data) { delete this.socket.onopen; delete this.socket.onclose; delete this.socket.onmessage; delete this.socket.onerror; if (this.state == this.OPEN) { this.state = this.CLOSED; SCEmitter.prototype.emit.call(this, 'close', code, data); } else if (this.state == this.CONNECTING) { this.state = this.CLOSED; SCEmitter.prototype.emit.call(this, 'openAbort', code, data); } }; SCTransport.prototype._onMessage = function (message) { SCEmitter.prototype.emit.call(this, 'event', 'message', message); // If ping if (message == '1') { this._resetPingTimeout(); if (this.socket.readyState == this.socket.OPEN) { this.socket.send('2'); } } else { var obj; try { obj = this.parse(message); } catch (err) { obj = message; } var event = obj.event; if (event) { var response = new Response(this, obj.cid); SCEmitter.prototype.emit.call(this, 'event', event, obj.data, response); } else if (obj.rid != null) { var eventObject = this._callbackMap[obj.rid]; if (eventObject) { clearTimeout(eventObject.timeout); delete this._callbackMap[obj.rid]; if (eventObject.callback) { eventObject.callback(obj.error, obj.data); } } if (obj.error) { this._onError(obj.error); } } else { SCEmitter.prototype.emit.call(this, 'event', 'raw', obj); } } }; SCTransport.prototype._onError = function (err) { SCEmitter.prototype.emit.call(this, 'error', err); }; SCTransport.prototype._resetPingTimeout = function () { var self = this; var now = (new Date()).getTime(); clearTimeout(this._pingTimeoutTicker); this._pingTimeoutTicker = setTimeout(function () { self._onClose(4000); self.socket.close(4000); }, this.pingTimeout); }; SCTransport.prototype.getBytesReceived = function () { return this.socket.bytesReceived; }; SCTransport.prototype.close = function (code, data) { code = code || 1000; if (this.state == this.OPEN) { var packet = { code: code, data: data }; this.emit('#disconnect', packet); this._onClose(code, data); this.socket.close(code); } else if (this.state == this.CONNECTING) { this._onClose(code, data); this.socket.close(code); } }; SCTransport.prototype.emitRaw = function (eventObject) { eventObject.cid = this.callIdGenerator(); if (eventObject.callback) { this._callbackMap[eventObject.cid] = eventObject; } var simpleEventObject = { event: eventObject.event, data: eventObject.data, cid: eventObject.cid }; this.sendObject(simpleEventObject); return eventObject.cid; }; SCTransport.prototype._handleEventAckTimeout = function (eventObject) { var errorMessage = "Event response for '" + eventObject.event + "' timed out"; var error = new Error(errorMessage); error.type = 'timeout'; if (eventObject.cid) { delete this._callbackMap[eventObject.cid]; } var callback = eventObject.callback; delete eventObject.callback; callback.call(eventObject, error, eventObject); this._onError(error); }; // The last two optional arguments (a and b) can be options and/or callback SCTransport.prototype.emit = function (event, data, a, b) { var self = this; var callback, options; if (b) { options = a; callback = b; } else { if (a instanceof Function) { options = {}; callback = a; } else { options = a; } } var eventObject = { event: event, data: data, callback: callback }; if (callback && !options.noTimeout) { eventObject.timeout = setTimeout(function () { self._handleEventAckTimeout(eventObject); }, this.options.ackTimeout); } var cid = null; if (this.state == this.OPEN || options.force) { cid = this.emitRaw(eventObject); } return cid; }; SCTransport.prototype.cancelPendingResponse = function (cid) { delete this._callbackMap[cid]; }; SCTransport.prototype.parse = function (message) { return formatter.parse(message); }; SCTransport.prototype.stringify = function (object) { return formatter.stringify(object); }; SCTransport.prototype.send = function (data) { if (this.socket.readyState != this.socket.OPEN) { this._onClose(1005); } else { this.socket.send(data); } }; SCTransport.prototype.sendObject = function (object) { var str, formatError; try { str = this.stringify(object); } catch (err) { formatError = err; this._onError(formatError); } if (!formatError) { this.send(str); } }; module.exports.SCTransport = SCTransport; },{"./response":4,"querystring":18,"sc-emitter":11,"sc-formatter":14,"sc-ws":15}],7:[function(require,module,exports){ 'use strict'; /** * Constants. */ var errorMessage; errorMessage = 'An argument without append, prepend, ' + 'or detach methods was given to `List'; /** * Creates a new List: A linked list is a bit like an Array, but * knows nothing about how many items are in it, and knows only about its * first (`head`) and last (`tail`) items. Each item (e.g. `head`, `tail`, * &c.) knows which item comes before or after it (its more like the * implementation of the DOM in JavaScript). * @global * @private * @constructor * @class Represents an instance of List. */ function List(/*items...*/) { if (arguments.length) { return List.from(arguments); } } var ListPrototype; ListPrototype = List.prototype; /** * Creates a new list from the arguments (each a list item) passed in. * @name List.of * @param {...ListItem} [items] - Zero or more items to attach. * @returns {list} - A new instance of List. */ List.of = function (/*items...*/) { return List.from.call(this, arguments); }; /** * Creates a new list from the given array-like object (each a list item) * passed in. * @name List.from * @param {ListItem[]} [items] - The items to append. * @returns {list} - A new instance of List. */ List.from = function (items) { var list = new this(), length, iterator, item; if (items && (length = items.length)) { iterator = -1; while (++iterator < length) { item = items[iterator]; if (item !== null && item !== undefined) { list.append(item); } } } return list; }; /** * List#head * Default to `null`. */ ListPrototype.head = null; /** * List#tail * Default to `null`. */ ListPrototype.tail = null; /** * Returns the list's items as an array. This does *not* detach the items. * @name List#toArray * @returns {ListItem[]} - An array of (still attached) ListItems. */ ListPrototype.toArray = function () { var item = this.head, result = []; while (item) { result.push(item); item = item.next; } return result; }; /** * Prepends the given item to the list: Item will be the new first item * (`head`). * @name List#prepend * @param {ListItem} item - The item to prepend. * @returns {ListItem} - An instance of ListItem (the given item). */ ListPrototype.prepend = function (item) { if (!item) { return false; } if (!item.append || !item.prepend || !item.detach) { throw new Error(errorMessage + '#prepend`.'); } var self, head; // Cache self. self = this; // If self has a first item, defer prepend to the first items prepend // method, and return the result. head = self.head; if (head) { return head.prepend(item); } // ...otherwise, there is no `head` (or `tail`) item yet. // Detach the prependee. item.detach(); // Set the prependees parent list to reference self. item.list = self; // Set self's first item to the prependee, and return the item. self.head = item; return item; }; /** * Appends the given item to the list: Item will be the new last item (`tail`) * if the list had a first item, and its first item (`head`) otherwise. * @name List#append * @param {ListItem} item - The item to append. * @returns {ListItem} - An instance of ListItem (the given item). */ ListPrototype.append = function (item) { if (!item) { return false; } if (!item.append || !item.prepend || !item.detach) { throw new Error(errorMessage + '#append`.'); } var self, head, tail; // Cache self. self = this; // If self has a last item, defer appending to the last items append // method, and return the result. tail = self.tail; if (tail) { return tail.append(item); } // If self has a first item, defer appending to the first items append // method, and return the result. head = self.head; if (head) { return head.append(item); } // ...otherwise, there is no `tail` or `head` item yet. // Detach the appendee. item.detach(); // Set the appendees parent list to reference self. item.list = self; // Set self's first item to the appendee, and return the item. self.head = item; return item; }; /** * Creates a new ListItem: A linked list item is a bit like DOM node: * It knows only about its "parent" (`list`), the item before it (`prev`), * and the item after it (`next`). * @global * @private * @constructor * @class Represents an instance of ListItem. */ function ListItem() {} List.Item = ListItem; var ListItemPrototype = ListItem.prototype; ListItemPrototype.next = null; ListItemPrototype.prev = null; ListItemPrototype.list = null; /** * Detaches the item operated on from its parent list. * @name ListItem#detach * @returns {ListItem} - The item operated on. */ ListItemPrototype.detach = function () { // Cache self, the parent list, and the previous and next items. var self = this, list = self.list, prev = self.prev, next = self.next; // If the item is already detached, return self. if (!list) { return self; } // If self is the last item in the parent list, link the lists last item // to the previous item. if (list.tail === self) { list.tail = prev; } // If self is the first item in the parent list, link the lists first item // to the next item. if (list.head === self) { list.head = next; } // If both the last and first items in the parent list are the same, // remove the link to the last item. if (list.tail === list.head) { list.tail = null; } // If a previous item exists, link its next item to selfs next item. if (prev) { prev.next = next; } // If a next item exists, link its previous item to selfs previous item. if (next) { next.prev = prev; } // Remove links from self to both the next and previous items, and to the // parent list. self.prev = self.next = self.list = null; // Return self. return self; }; /** * Prepends the given item *before* the item operated on. * @name ListItem#prepend * @param {ListItem} item - The item to prepend. * @returns {ListItem} - The item operated on, or false when that item is not * attached. */ ListItemPrototype.prepend = function (item) { if (!item || !item.append || !item.prepend || !item.detach) { throw new Error(errorMessage + 'Item#prepend`.'); } // Cache self, the parent list, and the previous item. var self = this, list = self.list, prev = self.prev; // If self is detached, return false. if (!list) { return false; } // Detach the prependee. item.detach(); // If self has a previous item... if (prev) { // ...link the prependees previous item, to selfs previous item. item.prev = prev; // ...link the previous items next item, to self. prev.next = item; } // Set the prependees next item to self. item.next = self; // Set the prependees parent list to selfs parent list. item.list = list; // Set the previous item of self to the prependee. self.prev = item; // If self is the first item in the parent list, link the lists first item // to the prependee. if (self === list.head) { list.head = item; } // If the the parent list has no last item, link the lists last item to // self. if (!list.tail) { list.tail = self; } // Return the prependee. return item; }; /** * Appends the given item *after* the item operated on. * @name ListItem#append * @param {ListItem} item - The item to append. * @returns {ListItem} - The item operated on, or false when that item is not * attached. */ ListItemPrototype.append = function (item) { // If item is falsey, return false. if (!item || !item.append || !item.prepend || !item.detach) { throw new Error(errorMessage + 'Item#append`.'); } // Cache self, the parent list, and the next item. var self = this, list = self.list, next = self.next; // If self is detached, return false. if (!list) { return false; } // Detach the appendee. item.detach(); // If self has a next item... if (next) { // ...link the appendees next item, to selfs next item. item.next = next; // ...link the next items previous item, to the appendee. next.prev = item; } // Set the appendees previous item to self. item.prev = self; // Set the appendees parent list to selfs parent list. item.list = list; // Set the next item of self to the appendee. self.next = item; // If the the parent list has no last item or if self is the parent lists // last item, link the lists last item to the appendee. if (self === list.tail || !list.tail) { list.tail = item; } // Return the appendee. return item; }; /** * Expose `List`. */ module.exports = List; },{}],8:[function(require,module,exports){ 'use strict'; module.exports = require('./_source/linked-list.js'); },{"./_source/linked-list.js":7}],9:[function(require,module,exports){ var SCEmitter = require('sc-emitter').SCEmitter; if (!Object.create) { Object.create = require('./objectcreate'); } var SCChannel = function (name, client) { var self = this; SCEmitter.call(this); this.PENDING = 'pending'; this.SUBSCRIBED = 'subscribed'; this.UNSUBSCRIBED = 'unsubscribed'; this.name = name; this.state = this.UNSUBSCRIBED; this.client = client; }; SCChannel.prototype = Object.create(SCEmitter.prototype); SCChannel.prototype.getState = function () { return this.state; }; SCChannel.prototype.subscribe = function () { this.client.subscribe(this.name); }; SCChannel.prototype.unsubscribe = function () { this.client.unsubscribe(this.name); }; SCChannel.prototype.isSubscribed = function (includePending) { return this.client.isSubscribed(this.name, includePending); }; SCChannel.prototype.publish = function (data, callback) { this.client.publish(this.name, data, callback); }; SCChannel.prototype.watch = function (handler) { this.client.watch(this.name, handler); }; SCChannel.prototype.unwatch = function (handler) { this.client.unwatch(this.name, handler); }; SCChannel.prototype.watchers = function () { return this.client.watchers(this.name); }; SCChannel.prototype.destroy = function () { this.client.destroyChannel(this.name); }; module.exports.SCChannel = SCChannel; },{"./objectcreate":10,"sc-emitter":11}],10:[function(require,module,exports){ arguments[4][3][0].apply(exports,arguments) },{"dup":3}],11:[function(require,module,exports){ var Emitter = require('component-emitter'); if (!Object.create) { Object.create = require('./objectcreate'); } var SCEmitter = function () { Emitter.call(this); }; SCEmitter.prototype = Object.create(Emitter.prototype); SCEmitter.prototype.emit = function (event) { if (event == 'error' && this.domain) { // Emit the error on the domain if it has one. // See https://github.com/joyent/node/blob/ef4344311e19a4f73c031508252b21712b22fe8a/lib/events.js#L78-85 var err = arguments[1]; if (!err) { err = new Error('Uncaught, unspecified "error" event.'); } err.domainEmitter = this; err.domain = this.domain; err.domainThrown = false; this.domain.emit('error', err); } Emitter.prototype.emit.apply(this, arguments); }; module.exports.SCEmitter = SCEmitter; },{"./objectcreate":13,"component-emitter":12}],12:[function(require,module,exports){ /** * Expose `Emitter`. */ module.exports = Emitter; /** * Initialize a new `Emitter`. * * @api public */ function Emitter(obj) { if (obj) return mixin(obj); }; /** * Mixin the emitter properties. * * @param {Object} obj * @return {Object} * @api private */ function mixin(obj) { for (var key in Emitter.prototype) { obj[key] = Emitter.prototype[key]; } return obj; } /** * Listen on the given `event` with `fn`. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.on = Emitter.prototype.addEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; (this._callbacks['$' + event] = this._callbacks['$' + event] || []) .push(fn); return this; }; /** * Adds an `event` listener that will be invoked a single * time then automatically removed. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.once = function(event, fn){ function on() { this.off(event, on); fn.apply(this, arguments); } on.fn = fn; this.on(event, on); return this; }; /** * Remove the given callback for `event` or all * registered callbacks. * * @param {String} event * @param {Function} fn * @return {Emitter} * @api public */ Emitter.prototype.off = Emitter.prototype.removeListener = Emitter.prototype.removeAllListeners = Emitter.prototype.removeEventListener = function(event, fn){ this._callbacks = this._callbacks || {}; // all if (0 == arguments.length) { this._callbacks = {}; return this; } // specific event var callbacks = this._callbacks['$' + event]; if (!callbacks) return this; // remove all handlers if (1 == arguments.length) { delete this._callbacks['$' + event]; return this; } // remove specific handler var cb; for (var i = 0; i < callbacks.length; i++) { cb = callbacks[i]; if (cb === fn || cb.fn === fn) { callbacks.splice(i, 1); break; } } return this; }; /** * Emit `event` with the given args. * * @param {String} event * @param {Mixed} ... * @return {Emitter} */ Emitter.prototype.emit = function(event){ this._callbacks = this._callbacks || {}; var args = [].slice.call(arguments, 1) , callbacks = this._callbacks['$' + event]; if (callbacks) { callbacks = callbacks.slice(0); for (var i = 0, len = callbacks.length; i < len; ++i) { callbacks[i].apply(this, args); } } return this; }; /** * Return array of callbacks for `event`. * * @param {String} event * @return {Array} * @api public */ Emitter.prototype.listeners = function(event){ this._callbacks = this._callbacks || {}; return this._callbacks['$' + event] || []; }; /** * Check if this emitter has `event` handlers. * * @param {String} event * @return {Boolean} * @api public */ Emitter.prototype.hasListeners = function(event){ return !! this.listeners(event).length; }; },{}],13:[function(require,module,exports){ arguments[4][3][0].apply(exports,arguments) },{"dup":3}],14:[function(require,module,exports){ (function (global){ var base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; module.exports.parse = function (input) { if (input == null) { return null; } var message = input.toString(); try { return JSON.parse(message); } catch (err) {} return message; }; var arrayBufferToBase64 = function (arraybuffer) { var bytes = new Uint8Array(arraybuffer); var len = bytes.length; var base64 = ''; for (var i = 0; i < len; i += 3) { base64 += base64Chars[bytes[i] >> 2]; base64 += base64Chars[((bytes[i] & 3) << 4) | (bytes[i + 1] >> 4)]; base64 += base64Chars[((bytes[i + 1] & 15) << 2) | (bytes[i + 2] >> 6)]; base64 += base64Chars[bytes[i + 2] & 63]; } if ((len % 3) === 2) { base64 = base64.substring(0, base64.length - 1) + '='; } else if (len % 3 === 1) { base64 = base64.substring(0, base64.length - 2) + '=='; } return base64; }; var isOwnDescendant = function (object, ancestors) { for (var i in