socketcluster-client
Version:
SocketCluster JavaScript client
1,912 lines (1,548 loc) • 57.6 kB
JavaScript
(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