diff --git a/lib/SugarClient/client.js b/lib/SugarClient/client.js new file mode 100644 index 0000000..0b8e366 --- /dev/null +++ b/lib/SugarClient/client.js @@ -0,0 +1,143 @@ +class SugarClient { + static initClass() { + this.host = null; + this.Primus = null; + } + + constructor(userId, authToken) { + this.primusUrl = this.primusUrl.bind(this); + this.connect = this.connect.bind(this); + this.disconnect = this.disconnect.bind(this); + this.receiveData = this.receiveData.bind(this); + this.subscribeTo = this.subscribeTo.bind(this); + this.unsubscribeFrom = this.unsubscribeFrom.bind(this); + this.on = this.on.bind(this); + this.off = this.off.bind(this); + this.emit = this.emit.bind(this); + this.__subscribeToChannels = this.__subscribeToChannels.bind(this); + this.__subscribeTo = this.__subscribeTo.bind(this); + this.createEvent = this.createEvent.bind(this); + this.userId = userId; + this.authToken = authToken; + this.events = { }; + this.subscriptions = { }; + this.initializePrimus(); + } + + initializePrimus() { + if (SugarClient.Primus == null) { throw 'SugarClient.Primus is not defined'; } + if (SugarClient.host == null) { throw 'SugarClient.host is not defined'; } + this.primus = SugarClient.Primus.connect(SugarClient.host, { + websockets: true, + network: true, + manual: true + } + ); + + this.primus.on('outgoing::url', this.primusUrl); + return this.primus.on('data', this.receiveData); + } + + host() { + return SugarClient.host; + } + + primusUrl(baseUrl) { + if (this.userId && this.authToken) { + return baseUrl.query = `user_id=${ this.userId }&auth_token=${ this.authToken }`; + } + } + + connect() { + this.disconnect(); + return this.primus.open(); + } + + disconnect() { + let key; + let userKeys = []; + userKeys = ((() => { + const result = []; + for (key in this.subscriptions) { + const _ = this.subscriptions[key]; + if (key.match(/^(session|user):/i)) { + result.push(key); + } + } + return result; + })()); + for (key of userKeys) { delete this.subscriptions[key]; } + this.userKey = (this.loggedIn = null); + return this.primus.end(); + } + + receiveData(data) { + if (data.type === 'connection') { + if (console && console.info) { + console.info('[CONNECTED] ', data); + } + this.loggedIn = data.loggedIn; + this.userKey = data.userKey; + this.subscriptions[this.userKey] = true; + return setTimeout(this.__subscribeToChannels, 100); + } else { + return this.emit(data); + } + } + + subscribeTo(channel) { + if (this.subscriptions[channel]) { return false; } + this.subscriptions[channel] = true; + return this.__subscribeTo(channel); + } + + unsubscribeFrom(channel) { + if (!this.subscriptions[channel]) { return; } + delete this.subscriptions[channel]; + return this.primus.write({action: 'Unsubscribe', params: { channel }}); + } + + on(type, callback) { + if (!this.events[type]) { this.events[type] = []; } + return this.events[type].push(callback); + } + + off(type, callback) { + if (callback && this.events[type]) { + return this.events[type] = this.events[type].filter(cb => cb !== callback); + } else { + return delete this.events[type]; + } + } + + emit(data) { + const callbacks = this.events[data.type] || []; + return callbacks.map((callback) => callback(data)); + } + + __subscribeToChannels() { + return (() => { + const result = []; + for (let channel in this.subscriptions) { + const _ = this.subscriptions[channel]; + result.push(this.__subscribeTo(channel)); + } + return result; + })(); + } + + __subscribeTo(channel) { + return this.primus.write({action: 'Subscribe', params: { channel }}); + } + + createEvent(type, channel, data) { + return this.primus.write({ + action: 'Event', + params: { type, channel, data }}); + } +} +SugarClient.initClass(); + +if (typeof module !== 'undefined' && module !== null) { + module.exports = SugarClient; +} diff --git a/lib/SugarClient/primus.js b/lib/SugarClient/primus.js new file mode 100644 index 0000000..e444a52 --- /dev/null +++ b/lib/SugarClient/primus.js @@ -0,0 +1,5962 @@ +(function UMDish(name, context, definition, plugins) { + context[name] = definition.call(context); + for (var i = 0; i < plugins.length; i++) { + plugins[i](context[name]) + } + if (typeof module !== "undefined" && module.exports) { + module.exports = context[name]; + } else if (typeof define === "function" && define.amd) { + define(function reference() { return context[name]; }); + } +})("Primus", this || {}, function wrapper() { + var define, module, exports + , Primus = (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 10000 || !(match = regex.exec(ms))) return 0; + + amount = parseFloat(match[1]); + + switch (match[2].toLowerCase()) { + case 'years': + case 'year': + case 'yrs': + case 'yr': + case 'y': + return amount * year; + + case 'weeks': + case 'week': + case 'wks': + case 'wk': + case 'w': + return amount * week; + + case 'days': + case 'day': + case 'd': + return amount * day; + + case 'hours': + case 'hour': + case 'hrs': + case 'hr': + case 'h': + return amount * hour; + + case 'minutes': + case 'minute': + case 'mins': + case 'min': + case 'm': + return amount * minute; + + case 'seconds': + case 'second': + case 'secs': + case 'sec': + case 's': + return amount * second; + + default: + return amount; + } +}; + +},{}],6:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Wrap callbacks to prevent double execution. + * + * @param {Function} fn Function that should only be called once. + * @returns {Function} A wrapped callback which prevents execution. + * @api public + */ +module.exports = function one(fn) { + var called = 0 + , value; + + /** + * The function that prevents double execution. + * + * @api private + */ + function onetime() { + if (called) return value; + + called = 1; + value = fn.apply(this, arguments); + fn = null; + + return value; + } + + // + // To make debugging more easy we want to use the name of the supplied + // function. So when you look at the functions that are assigned to event + // listeners you don't see a load of `onetime` functions but actually the + // names of the functions that this module will call. + // + onetime.displayName = fn.displayName || fn.name || onetime.displayName || onetime.name; + return onetime; +}; + +},{}],7:[function(_dereq_,module,exports){ +// shim for using process in browser +var process = module.exports = {}; + +// cached from whatever global is present so that test runners that stub it +// don't break things. But we need to wrap it in a try catch in case it is +// wrapped in strict mode code which doesn't define any globals. It's inside a +// function because try/catches deoptimize in certain engines. + +var cachedSetTimeout; +var cachedClearTimeout; + +function defaultSetTimout() { + throw new Error('setTimeout has not been defined'); +} +function defaultClearTimeout () { + throw new Error('clearTimeout has not been defined'); +} +(function () { + try { + if (typeof setTimeout === 'function') { + cachedSetTimeout = setTimeout; + } else { + cachedSetTimeout = defaultSetTimout; + } + } catch (e) { + cachedSetTimeout = defaultSetTimout; + } + try { + if (typeof clearTimeout === 'function') { + cachedClearTimeout = clearTimeout; + } else { + cachedClearTimeout = defaultClearTimeout; + } + } catch (e) { + cachedClearTimeout = defaultClearTimeout; + } +} ()) +function runTimeout(fun) { + if (cachedSetTimeout === setTimeout) { + //normal enviroments in sane situations + return setTimeout(fun, 0); + } + // if setTimeout wasn't available but was latter defined + if ((cachedSetTimeout === defaultSetTimout || !cachedSetTimeout) && setTimeout) { + cachedSetTimeout = setTimeout; + return setTimeout(fun, 0); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedSetTimeout(fun, 0); + } catch(e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedSetTimeout.call(null, fun, 0); + } catch(e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error + return cachedSetTimeout.call(this, fun, 0); + } + } + + +} +function runClearTimeout(marker) { + if (cachedClearTimeout === clearTimeout) { + //normal enviroments in sane situations + return clearTimeout(marker); + } + // if clearTimeout wasn't available but was latter defined + if ((cachedClearTimeout === defaultClearTimeout || !cachedClearTimeout) && clearTimeout) { + cachedClearTimeout = clearTimeout; + return clearTimeout(marker); + } + try { + // when when somebody has screwed with setTimeout but no I.E. maddness + return cachedClearTimeout(marker); + } catch (e){ + try { + // When we are in I.E. but the script has been evaled so I.E. doesn't trust the global object when called normally + return cachedClearTimeout.call(null, marker); + } catch (e){ + // same as above but when it's a version of I.E. that must have the global object for 'this', hopfully our context correct otherwise it will throw a global error. + // Some versions of I.E. have different rules for clearTimeout vs setTimeout + return cachedClearTimeout.call(this, marker); + } + } + + + +} +var queue = []; +var draining = false; +var currentQueue; +var queueIndex = -1; + +function cleanUpNextTick() { + if (!draining || !currentQueue) { + return; + } + draining = false; + if (currentQueue.length) { + queue = currentQueue.concat(queue); + } else { + queueIndex = -1; + } + if (queue.length) { + drainQueue(); + } +} + +function drainQueue() { + if (draining) { + return; + } + var timeout = runTimeout(cleanUpNextTick); + draining = true; + + var len = queue.length; + while(len) { + currentQueue = queue; + queue = []; + while (++queueIndex < len) { + if (currentQueue) { + currentQueue[queueIndex].run(); + } + } + queueIndex = -1; + len = queue.length; + } + currentQueue = null; + draining = false; + runClearTimeout(timeout); +} + +process.nextTick = function (fun) { + var args = new Array(arguments.length - 1); + if (arguments.length > 1) { + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + } + queue.push(new Item(fun, args)); + if (queue.length === 1 && !draining) { + runTimeout(drainQueue); + } +}; + +// v8 likes predictible objects +function Item(fun, array) { + this.fun = fun; + this.array = array; +} +Item.prototype.run = function () { + this.fun.apply(null, this.array); +}; +process.title = 'browser'; +process.browser = true; +process.env = {}; +process.argv = []; +process.version = ''; // empty string to avoid regexp issues +process.versions = {}; + +function noop() {} + +process.on = noop; +process.addListener = noop; +process.once = noop; +process.off = noop; +process.removeListener = noop; +process.removeAllListeners = noop; +process.emit = noop; +process.prependListener = noop; +process.prependOnceListener = noop; + +process.listeners = function (name) { return [] } + +process.binding = function (name) { + throw new Error('process.binding is not supported'); +}; + +process.cwd = function () { return '/' }; +process.chdir = function (dir) { + throw new Error('process.chdir is not supported'); +}; +process.umask = function() { return 0; }; + +},{}],8:[function(_dereq_,module,exports){ +'use strict'; + +var has = Object.prototype.hasOwnProperty + , undef; + +/** + * Decode a URI encoded string. + * + * @param {String} input The URI encoded string. + * @returns {String|Null} The decoded string. + * @api private + */ +function decode(input) { + try { + return decodeURIComponent(input.replace(/\+/g, ' ')); + } catch (e) { + return null; + } +} + +/** + * Attempts to encode a given input. + * + * @param {String} input The string that needs to be encoded. + * @returns {String|Null} The encoded string. + * @api private + */ +function encode(input) { + try { + return encodeURIComponent(input); + } catch (e) { + return null; + } +} + +/** + * Simple query string parser. + * + * @param {String} query The query string that needs to be parsed. + * @returns {Object} + * @api public + */ +function querystring(query) { + var parser = /([^=?#&]+)=?([^&]*)/g + , result = {} + , part; + + while (part = parser.exec(query)) { + var key = decode(part[1]) + , value = decode(part[2]); + + // + // Prevent overriding of existing properties. This ensures that build-in + // methods like `toString` or __proto__ are not overriden by malicious + // querystrings. + // + // In the case if failed decoding, we want to omit the key/value pairs + // from the result. + // + if (key === null || value === null || key in result) continue; + result[key] = value; + } + + return result; +} + +/** + * Transform a query string to an object. + * + * @param {Object} obj Object that should be transformed. + * @param {String} prefix Optional prefix. + * @returns {String} + * @api public + */ +function querystringify(obj, prefix) { + prefix = prefix || ''; + + var pairs = [] + , value + , key; + + // + // Optionally prefix with a '?' if needed + // + if ('string' !== typeof prefix) prefix = '?'; + + for (key in obj) { + if (has.call(obj, key)) { + value = obj[key]; + + // + // Edge cases where we actually want to encode the value to an empty + // string instead of the stringified value. + // + if (!value && (value === null || value === undef || isNaN(value))) { + value = ''; + } + + key = encode(key); + value = encode(value); + + // + // If we failed to encode the strings, we should bail out as we don't + // want to add invalid strings to the query. + // + if (key === null || value === null) continue; + pairs.push(key +'='+ value); + } + } + + return pairs.length ? prefix + pairs.join('&') : ''; +} + +// +// Expose the module. +// +exports.stringify = querystringify; +exports.parse = querystring; + +},{}],9:[function(_dereq_,module,exports){ +'use strict'; + +var EventEmitter = _dereq_('eventemitter3') + , millisecond = _dereq_('millisecond') + , destroy = _dereq_('demolish') + , Tick = _dereq_('tick-tock') + , one = _dereq_('one-time'); + +/** + * Returns sane defaults about a given value. + * + * @param {String} name Name of property we want. + * @param {Recovery} selfie Recovery instance that got created. + * @param {Object} opts User supplied options we want to check. + * @returns {Number} Some default value. + * @api private + */ +function defaults(name, selfie, opts) { + return millisecond( + name in opts ? opts[name] : (name in selfie ? selfie[name] : Recovery[name]) + ); +} + +/** + * Attempt to recover your connection with reconnection attempt. + * + * @constructor + * @param {Object} options Configuration + * @api public + */ +function Recovery(options) { + var recovery = this; + + if (!(recovery instanceof Recovery)) return new Recovery(options); + + options = options || {}; + + recovery.attempt = null; // Stores the current reconnect attempt. + recovery._fn = null; // Stores the callback. + + recovery['reconnect timeout'] = defaults('reconnect timeout', recovery, options); + recovery.retries = defaults('retries', recovery, options); + recovery.factor = defaults('factor', recovery, options); + recovery.max = defaults('max', recovery, options); + recovery.min = defaults('min', recovery, options); + recovery.timers = new Tick(recovery); +} + +Recovery.prototype = new EventEmitter(); +Recovery.prototype.constructor = Recovery; + +Recovery['reconnect timeout'] = '30 seconds'; // Maximum time to wait for an answer. +Recovery.max = Infinity; // Maximum delay. +Recovery.min = '500 ms'; // Minimum delay. +Recovery.retries = 10; // Maximum amount of retries. +Recovery.factor = 2; // Exponential back off factor. + +/** + * Start a new reconnect procedure. + * + * @returns {Recovery} + * @api public + */ +Recovery.prototype.reconnect = function reconnect() { + var recovery = this; + + return recovery.backoff(function backedoff(err, opts) { + opts.duration = (+new Date()) - opts.start; + + if (err) return recovery.emit('reconnect failed', err, opts); + + recovery.emit('reconnected', opts); + }, recovery.attempt); +}; + +/** + * Exponential back off algorithm for retry operations. It uses a randomized + * retry so we don't DDOS our server when it goes down under pressure. + * + * @param {Function} fn Callback to be called after the timeout. + * @param {Object} opts Options for configuring the timeout. + * @returns {Recovery} + * @api private + */ +Recovery.prototype.backoff = function backoff(fn, opts) { + var recovery = this; + + opts = opts || recovery.attempt || {}; + + // + // Bailout when we already have a back off process running. We shouldn't call + // the callback then. + // + if (opts.backoff) return recovery; + + opts['reconnect timeout'] = defaults('reconnect timeout', recovery, opts); + opts.retries = defaults('retries', recovery, opts); + opts.factor = defaults('factor', recovery, opts); + opts.max = defaults('max', recovery, opts); + opts.min = defaults('min', recovery, opts); + + opts.start = +opts.start || +new Date(); + opts.duration = +opts.duration || 0; + opts.attempt = +opts.attempt || 0; + + // + // Bailout if we are about to make too much attempts. + // + if (opts.attempt === opts.retries) { + fn.call(recovery, new Error('Unable to recover'), opts); + return recovery; + } + + // + // Prevent duplicate back off attempts using the same options object and + // increment our attempt as we're about to have another go at this thing. + // + opts.backoff = true; + opts.attempt++; + + recovery.attempt = opts; + + // + // Calculate the timeout, but make it randomly so we don't retry connections + // at the same interval and defeat the purpose. This exponential back off is + // based on the work of: + // + // http://dthain.blogspot.nl/2009/02/exponential-backoff-in-distributed.html + // + opts.scheduled = opts.attempt !== 1 + ? Math.min(Math.round( + (Math.random() + 1) * opts.min * Math.pow(opts.factor, opts.attempt - 1) + ), opts.max) + : opts.min; + + recovery.timers.setTimeout('reconnect', function delay() { + opts.duration = (+new Date()) - opts.start; + opts.backoff = false; + recovery.timers.clear('reconnect, timeout'); + + // + // Create a `one` function which can only be called once. So we can use the + // same function for different types of invocations to create a much better + // and usable API. + // + var connect = recovery._fn = one(function connect(err) { + recovery.reset(); + + if (err) return recovery.backoff(fn, opts); + + fn.call(recovery, undefined, opts); + }); + + recovery.emit('reconnect', opts, connect); + recovery.timers.setTimeout('timeout', function timeout() { + var err = new Error('Failed to reconnect in a timely manner'); + opts.duration = (+new Date()) - opts.start; + + recovery.emit('reconnect timeout', err, opts); + connect(err); + }, opts['reconnect timeout']); + }, opts.scheduled); + + // + // Emit a `reconnecting` event with current reconnect options. This allows + // them to update the UI and provide their users with feedback. + // + recovery.emit('reconnect scheduled', opts); + + return recovery; +}; + +/** + * Check if the reconnection process is currently reconnecting. + * + * @returns {Boolean} + * @api public + */ +Recovery.prototype.reconnecting = function reconnecting() { + return !!this.attempt; +}; + +/** + * Tell our reconnection procedure that we're passed. + * + * @param {Error} err Reconnection failed. + * @returns {Recovery} + * @api public + */ +Recovery.prototype.reconnected = function reconnected(err) { + if (this._fn) this._fn(err); + return this; +}; + +/** + * Reset the reconnection attempt so it can be re-used again. + * + * @returns {Recovery} + * @api public + */ +Recovery.prototype.reset = function reset() { + this._fn = this.attempt = null; + this.timers.clear('reconnect, timeout'); + + return this; +}; + +/** + * Clean up the instance. + * + * @type {Function} + * @returns {Boolean} + * @api public + */ +Recovery.prototype.destroy = destroy('timers attempt _fn'); + +// +// Expose the module. +// +module.exports = Recovery; + +},{"demolish":1,"eventemitter3":10,"millisecond":5,"one-time":6,"tick-tock":12}],10:[function(_dereq_,module,exports){ +'use strict'; + +// +// We store our EE objects in a plain object whose properties are event names. +// If `Object.create(null)` is not supported we prefix the event names with a +// `~` to make sure that the built-in object properties are not overridden or +// used as an attack vector. +// We also assume that `Object.create(null)` is available when the event name +// is an ES6 Symbol. +// +var prefix = typeof Object.create !== 'function' ? '~' : false; + +/** + * Representation of a single EventEmitter function. + * + * @param {Function} fn Event handler to be called. + * @param {Mixed} context Context for function execution. + * @param {Boolean} once Only emit once + * @api private + */ +function EE(fn, context, once) { + this.fn = fn; + this.context = context; + this.once = once || false; +} + +/** + * Minimal EventEmitter interface that is molded against the Node.js + * EventEmitter interface. + * + * @constructor + * @api public + */ +function EventEmitter() { /* Nothing to set */ } + +/** + * Holds the assigned EventEmitters by name. + * + * @type {Object} + * @private + */ +EventEmitter.prototype._events = undefined; + +/** + * Return a list of assigned event listeners. + * + * @param {String} event The events that should be listed. + * @param {Boolean} exists We only need to know if there are listeners. + * @returns {Array|Boolean} + * @api public + */ +EventEmitter.prototype.listeners = function listeners(event, exists) { + var evt = prefix ? prefix + event : event + , available = this._events && this._events[evt]; + + if (exists) return !!available; + if (!available) return []; + if (available.fn) return [available.fn]; + + for (var i = 0, l = available.length, ee = new Array(l); i < l; i++) { + ee[i] = available[i].fn; + } + + return ee; +}; + +/** + * Emit an event to all registered event listeners. + * + * @param {String} event The name of the event. + * @returns {Boolean} Indication if we've emitted an event. + * @api public + */ +EventEmitter.prototype.emit = function emit(event, a1, a2, a3, a4, a5) { + var evt = prefix ? prefix + event : event; + + if (!this._events || !this._events[evt]) return false; + + var listeners = this._events[evt] + , len = arguments.length + , args + , i; + + if ('function' === typeof listeners.fn) { + if (listeners.once) this.removeListener(event, listeners.fn, undefined, true); + + switch (len) { + case 1: return listeners.fn.call(listeners.context), true; + case 2: return listeners.fn.call(listeners.context, a1), true; + case 3: return listeners.fn.call(listeners.context, a1, a2), true; + case 4: return listeners.fn.call(listeners.context, a1, a2, a3), true; + case 5: return listeners.fn.call(listeners.context, a1, a2, a3, a4), true; + case 6: return listeners.fn.call(listeners.context, a1, a2, a3, a4, a5), true; + } + + for (i = 1, args = new Array(len -1); i < len; i++) { + args[i - 1] = arguments[i]; + } + + listeners.fn.apply(listeners.context, args); + } else { + var length = listeners.length + , j; + + for (i = 0; i < length; i++) { + if (listeners[i].once) this.removeListener(event, listeners[i].fn, undefined, true); + + switch (len) { + case 1: listeners[i].fn.call(listeners[i].context); break; + case 2: listeners[i].fn.call(listeners[i].context, a1); break; + case 3: listeners[i].fn.call(listeners[i].context, a1, a2); break; + default: + if (!args) for (j = 1, args = new Array(len -1); j < len; j++) { + args[j - 1] = arguments[j]; + } + + listeners[i].fn.apply(listeners[i].context, args); + } + } + } + + return true; +}; + +/** + * Register a new EventListener for the given event. + * + * @param {String} event Name of the event. + * @param {Functon} fn Callback function. + * @param {Mixed} context The context of the function. + * @api public + */ +EventEmitter.prototype.on = function on(event, fn, context) { + var listener = new EE(fn, context || this) + , evt = prefix ? prefix + event : event; + + if (!this._events) this._events = prefix ? {} : Object.create(null); + if (!this._events[evt]) this._events[evt] = listener; + else { + if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [ + this._events[evt], listener + ]; + } + + return this; +}; + +/** + * Add an EventListener that's only called once. + * + * @param {String} event Name of the event. + * @param {Function} fn Callback function. + * @param {Mixed} context The context of the function. + * @api public + */ +EventEmitter.prototype.once = function once(event, fn, context) { + var listener = new EE(fn, context || this, true) + , evt = prefix ? prefix + event : event; + + if (!this._events) this._events = prefix ? {} : Object.create(null); + if (!this._events[evt]) this._events[evt] = listener; + else { + if (!this._events[evt].fn) this._events[evt].push(listener); + else this._events[evt] = [ + this._events[evt], listener + ]; + } + + return this; +}; + +/** + * Remove event listeners. + * + * @param {String} event The event we want to remove. + * @param {Function} fn The listener that we need to find. + * @param {Mixed} context Only remove listeners matching this context. + * @param {Boolean} once Only remove once listeners. + * @api public + */ +EventEmitter.prototype.removeListener = function removeListener(event, fn, context, once) { + var evt = prefix ? prefix + event : event; + + if (!this._events || !this._events[evt]) return this; + + var listeners = this._events[evt] + , events = []; + + if (fn) { + if (listeners.fn) { + if ( + listeners.fn !== fn + || (once && !listeners.once) + || (context && listeners.context !== context) + ) { + events.push(listeners); + } + } else { + for (var i = 0, length = listeners.length; i < length; i++) { + if ( + listeners[i].fn !== fn + || (once && !listeners[i].once) + || (context && listeners[i].context !== context) + ) { + events.push(listeners[i]); + } + } + } + } + + // + // Reset the array, or remove it completely if we have no more listeners. + // + if (events.length) { + this._events[evt] = events.length === 1 ? events[0] : events; + } else { + delete this._events[evt]; + } + + return this; +}; + +/** + * Remove all listeners or only the listeners for the specified event. + * + * @param {String} event The event want to remove all listeners for. + * @api public + */ +EventEmitter.prototype.removeAllListeners = function removeAllListeners(event) { + if (!this._events) return this; + + if (event) delete this._events[prefix ? prefix + event : event]; + else this._events = prefix ? {} : Object.create(null); + + return this; +}; + +// +// Alias methods names because people roll like that. +// +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; +EventEmitter.prototype.addListener = EventEmitter.prototype.on; + +// +// This function doesn't apply anymore. +// +EventEmitter.prototype.setMaxListeners = function setMaxListeners() { + return this; +}; + +// +// Expose the prefix. +// +EventEmitter.prefixed = prefix; + +// +// Expose the module. +// +if ('undefined' !== typeof module) { + module.exports = EventEmitter; +} + +},{}],11:[function(_dereq_,module,exports){ +'use strict'; + +/** + * Check if we're required to add a port number. + * + * @see https://url.spec.whatwg.org/#default-port + * @param {Number|String} port Port number we need to check + * @param {String} protocol Protocol we need to check against. + * @returns {Boolean} Is it a default port for the given protocol + * @api private + */ +module.exports = function required(port, protocol) { + protocol = protocol.split(':')[0]; + port = +port; + + if (!port) return false; + + switch (protocol) { + case 'http': + case 'ws': + return port !== 80; + + case 'https': + case 'wss': + return port !== 443; + + case 'ftp': + return port !== 21; + + case 'gopher': + return port !== 70; + + case 'file': + return false; + } + + return port !== 0; +}; + +},{}],12:[function(_dereq_,module,exports){ +(function (setImmediate,clearImmediate){(function (){ +'use strict'; + +var has = Object.prototype.hasOwnProperty + , ms = _dereq_('millisecond'); + +/** + * Timer instance. + * + * @constructor + * @param {Object} timer New timer instance. + * @param {Function} clear Clears the timer instance. + * @param {Function} duration Duration of the timer. + * @param {Function} fn The functions that need to be executed. + * @api private + */ +function Timer(timer, clear, duration, fn) { + this.start = +(new Date()); + this.duration = duration; + this.clear = clear; + this.timer = timer; + this.fns = [fn]; +} + +/** + * Calculate the time left for a given timer. + * + * @returns {Number} Time in milliseconds. + * @api public + */ +Timer.prototype.remaining = function remaining() { + return this.duration - this.taken(); +}; + +/** + * Calculate the amount of time it has taken since we've set the timer. + * + * @returns {Number} + * @api public + */ +Timer.prototype.taken = function taken() { + return +(new Date()) - this.start; +}; + +/** + * Custom wrappers for the various of clear{whatever} functions. We cannot + * invoke them directly as this will cause thrown errors in Google Chrome with + * an Illegal Invocation Error + * + * @see #2 + * @type {Function} + * @api private + */ +function unsetTimeout(id) { clearTimeout(id); } +function unsetInterval(id) { clearInterval(id); } +function unsetImmediate(id) { clearImmediate(id); } + +/** + * Simple timer management. + * + * @constructor + * @param {Mixed} context Context of the callbacks that we execute. + * @api public + */ +function Tick(context) { + if (!(this instanceof Tick)) return new Tick(context); + + this.timers = {}; + this.context = context || this; +} + +/** + * Return a function which will just iterate over all assigned callbacks and + * optionally clear the timers from memory if needed. + * + * @param {String} name Name of the timer we need to execute. + * @param {Boolean} clear Also clear from memory. + * @returns {Function} + * @api private + */ +Tick.prototype.tock = function ticktock(name, clear) { + var tock = this; + + return function tickedtock() { + if (!(name in tock.timers)) return; + + var timer = tock.timers[name] + , fns = timer.fns.slice() + , l = fns.length + , i = 0; + + if (clear) tock.clear(name); + else tock.start = +new Date(); + + for (; i < l; i++) { + fns[i].call(tock.context); + } + }; +}; + +/** + * Add a new timeout. + * + * @param {String} name Name of the timer. + * @param {Function} fn Completion callback. + * @param {Mixed} time Duration of the timer. + * @returns {Tick} + * @api public + */ +Tick.prototype.setTimeout = function timeout(name, fn, time) { + var tick = this + , tock; + + if (tick.timers[name]) { + tick.timers[name].fns.push(fn); + return tick; + } + + tock = ms(time); + tick.timers[name] = new Timer( + setTimeout(tick.tock(name, true), ms(time)), + unsetTimeout, + tock, + fn + ); + + return tick; +}; + +/** + * Add a new interval. + * + * @param {String} name Name of the timer. + * @param {Function} fn Completion callback. + * @param {Mixed} time Interval of the timer. + * @returns {Tick} + * @api public + */ +Tick.prototype.setInterval = function interval(name, fn, time) { + var tick = this + , tock; + + if (tick.timers[name]) { + tick.timers[name].fns.push(fn); + return tick; + } + + tock = ms(time); + tick.timers[name] = new Timer( + setInterval(tick.tock(name), ms(time)), + unsetInterval, + tock, + fn + ); + + return tick; +}; + +/** + * Add a new setImmediate. + * + * @param {String} name Name of the timer. + * @param {Function} fn Completion callback. + * @returns {Tick} + * @api public + */ +Tick.prototype.setImmediate = function immediate(name, fn) { + var tick = this; + + if ('function' !== typeof setImmediate) return tick.setTimeout(name, fn, 0); + + if (tick.timers[name]) { + tick.timers[name].fns.push(fn); + return tick; + } + + tick.timers[name] = new Timer( + setImmediate(tick.tock(name, true)), + unsetImmediate, + 0, + fn + ); + + return tick; +}; + +/** + * Check if we have a timer set. + * + * @param {String} name + * @returns {Boolean} + * @api public + */ +Tick.prototype.active = function active(name) { + return name in this.timers; +}; + +/** + * Properly clean up all timeout references. If no arguments are supplied we + * will attempt to clear every single timer that is present. + * + * @param {Arguments} ..args.. The names of the timeouts we need to clear + * @returns {Tick} + * @api public + */ +Tick.prototype.clear = function clear() { + var args = arguments.length ? arguments : [] + , tick = this + , timer, i, l; + + if (args.length === 1 && 'string' === typeof args[0]) { + args = args[0].split(/[, ]+/); + } + + if (!args.length) { + for (timer in tick.timers) { + if (has.call(tick.timers, timer)) args.push(timer); + } + } + + for (i = 0, l = args.length; i < l; i++) { + timer = tick.timers[args[i]]; + + if (!timer) continue; + timer.clear(timer.timer); + + timer.fns = timer.timer = timer.clear = null; + delete tick.timers[args[i]]; + } + + return tick; +}; + +/** + * Adjust a timeout or interval to a new duration. + * + * @returns {Tick} + * @api public + */ +Tick.prototype.adjust = function adjust(name, time) { + var interval + , tick = this + , tock = ms(time) + , timer = tick.timers[name]; + + if (!timer) return tick; + + interval = timer.clear === unsetInterval; + timer.clear(timer.timer); + timer.start = +(new Date()); + timer.duration = tock; + timer.timer = (interval ? setInterval : setTimeout)(tick.tock(name, !interval), tock); + + return tick; +}; + +/** + * We will no longer use this module, prepare your self for global cleanups. + * + * @returns {Boolean} + * @api public + */ +Tick.prototype.end = Tick.prototype.destroy = function end() { + if (!this.context) return false; + + this.clear(); + this.context = this.timers = null; + + return true; +}; + +// +// Expose the timer factory. +// +Tick.Timer = Timer; +module.exports = Tick; + +}).call(this)}).call(this,_dereq_("timers").setImmediate,_dereq_("timers").clearImmediate) +},{"millisecond":5,"timers":13}],13:[function(_dereq_,module,exports){ +(function (setImmediate,clearImmediate){(function (){ +var nextTick = _dereq_('process/browser.js').nextTick; +var apply = Function.prototype.apply; +var slice = Array.prototype.slice; +var immediateIds = {}; +var nextImmediateId = 0; + +// DOM APIs, for completeness + +exports.setTimeout = function() { + return new Timeout(apply.call(setTimeout, window, arguments), clearTimeout); +}; +exports.setInterval = function() { + return new Timeout(apply.call(setInterval, window, arguments), clearInterval); +}; +exports.clearTimeout = +exports.clearInterval = function(timeout) { timeout.close(); }; + +function Timeout(id, clearFn) { + this._id = id; + this._clearFn = clearFn; +} +Timeout.prototype.unref = Timeout.prototype.ref = function() {}; +Timeout.prototype.close = function() { + this._clearFn.call(window, this._id); +}; + +// Does not start the time, just sets up the members needed. +exports.enroll = function(item, msecs) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = msecs; +}; + +exports.unenroll = function(item) { + clearTimeout(item._idleTimeoutId); + item._idleTimeout = -1; +}; + +exports._unrefActive = exports.active = function(item) { + clearTimeout(item._idleTimeoutId); + + var msecs = item._idleTimeout; + if (msecs >= 0) { + item._idleTimeoutId = setTimeout(function onTimeout() { + if (item._onTimeout) + item._onTimeout(); + }, msecs); + } +}; + +// That's not how node.js implements it but the exposed api is the same. +exports.setImmediate = typeof setImmediate === "function" ? setImmediate : function(fn) { + var id = nextImmediateId++; + var args = arguments.length < 2 ? false : slice.call(arguments, 1); + + immediateIds[id] = true; + + nextTick(function onNextTick() { + if (immediateIds[id]) { + // fn.call() is faster so we optimize for the common use-case + // @see http://jsperf.com/call-apply-segu + if (args) { + fn.apply(null, args); + } else { + fn.call(null); + } + // Prevent ids from leaking + exports.clearImmediate(id); + } + }); + + return id; +}; + +exports.clearImmediate = typeof clearImmediate === "function" ? clearImmediate : function(id) { + delete immediateIds[id]; +}; +}).call(this)}).call(this,_dereq_("timers").setImmediate,_dereq_("timers").clearImmediate) +},{"process/browser.js":7,"timers":13}],14:[function(_dereq_,module,exports){ +(function (global){(function (){ +'use strict'; + +var required = _dereq_('requires-port') + , qs = _dereq_('querystringify') + , controlOrWhitespace = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/ + , CRHTLF = /[\n\r\t]/g + , slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\// + , port = /:\d+$/ + , protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i + , windowsDriveLetter = /^[a-zA-Z]:/; + +/** + * Remove control characters and whitespace from the beginning of a string. + * + * @param {Object|String} str String to trim. + * @returns {String} A new string representing `str` stripped of control + * characters and whitespace from its beginning. + * @public + */ +function trimLeft(str) { + return (str ? str : '').toString().replace(controlOrWhitespace, ''); +} + +/** + * These are the parse rules for the URL parser, it informs the parser + * about: + * + * 0. The char it Needs to parse, if it's a string it should be done using + * indexOf, RegExp using exec and NaN means set as current value. + * 1. The property we should set when parsing this value. + * 2. Indication if it's backwards or forward parsing, when set as number it's + * the value of extra chars that should be split off. + * 3. Inherit from location if non existing in the parser. + * 4. `toLowerCase` the resulting value. + */ +var rules = [ + ['#', 'hash'], // Extract from the back. + ['?', 'query'], // Extract from the back. + function sanitize(address, url) { // Sanitize what is left of the address + return isSpecial(url.protocol) ? address.replace(/\\/g, '/') : address; + }, + ['/', 'pathname'], // Extract from the back. + ['@', 'auth', 1], // Extract from the front. + [NaN, 'host', undefined, 1, 1], // Set left over value. + [/:(\d*)$/, 'port', undefined, 1], // RegExp the back. + [NaN, 'hostname', undefined, 1, 1] // Set left over. +]; + +/** + * These properties should not be copied or inherited from. This is only needed + * for all non blob URL's as a blob URL does not include a hash, only the + * origin. + * + * @type {Object} + * @private + */ +var ignore = { hash: 1, query: 1 }; + +/** + * The location object differs when your code is loaded through a normal page, + * Worker or through a worker using a blob. And with the blobble begins the + * trouble as the location object will contain the URL of the blob, not the + * location of the page where our code is loaded in. The actual origin is + * encoded in the `pathname` so we can thankfully generate a good "default" + * location from it so we can generate proper relative URL's again. + * + * @param {Object|String} loc Optional default location object. + * @returns {Object} lolcation object. + * @public + */ +function lolcation(loc) { + var globalVar; + + if (typeof window !== 'undefined') globalVar = window; + else if (typeof global !== 'undefined') globalVar = global; + else if (typeof self !== 'undefined') globalVar = self; + else globalVar = {}; + + var location = globalVar.location || {}; + loc = loc || location; + + var finaldestination = {} + , type = typeof loc + , key; + + if ('blob:' === loc.protocol) { + finaldestination = new Url(unescape(loc.pathname), {}); + } else if ('string' === type) { + finaldestination = new Url(loc, {}); + for (key in ignore) delete finaldestination[key]; + } else if ('object' === type) { + for (key in loc) { + if (key in ignore) continue; + finaldestination[key] = loc[key]; + } + + if (finaldestination.slashes === undefined) { + finaldestination.slashes = slashes.test(loc.href); + } + } + + return finaldestination; +} + +/** + * Check whether a protocol scheme is special. + * + * @param {String} The protocol scheme of the URL + * @return {Boolean} `true` if the protocol scheme is special, else `false` + * @private + */ +function isSpecial(scheme) { + return ( + scheme === 'file:' || + scheme === 'ftp:' || + scheme === 'http:' || + scheme === 'https:' || + scheme === 'ws:' || + scheme === 'wss:' + ); +} + +/** + * @typedef ProtocolExtract + * @type Object + * @property {String} protocol Protocol matched in the URL, in lowercase. + * @property {Boolean} slashes `true` if protocol is followed by "//", else `false`. + * @property {String} rest Rest of the URL that is not part of the protocol. + */ + +/** + * Extract protocol information from a URL with/without double slash ("//"). + * + * @param {String} address URL we want to extract from. + * @param {Object} location + * @return {ProtocolExtract} Extracted information. + * @private + */ +function extractProtocol(address, location) { + address = trimLeft(address); + address = address.replace(CRHTLF, ''); + location = location || {}; + + var match = protocolre.exec(address); + var protocol = match[1] ? match[1].toLowerCase() : ''; + var forwardSlashes = !!match[2]; + var otherSlashes = !!match[3]; + var slashesCount = 0; + var rest; + + if (forwardSlashes) { + if (otherSlashes) { + rest = match[2] + match[3] + match[4]; + slashesCount = match[2].length + match[3].length; + } else { + rest = match[2] + match[4]; + slashesCount = match[2].length; + } + } else { + if (otherSlashes) { + rest = match[3] + match[4]; + slashesCount = match[3].length; + } else { + rest = match[4] + } + } + + if (protocol === 'file:') { + if (slashesCount >= 2) { + rest = rest.slice(2); + } + } else if (isSpecial(protocol)) { + rest = match[4]; + } else if (protocol) { + if (forwardSlashes) { + rest = rest.slice(2); + } + } else if (slashesCount >= 2 && isSpecial(location.protocol)) { + rest = match[4]; + } + + return { + protocol: protocol, + slashes: forwardSlashes || isSpecial(protocol), + slashesCount: slashesCount, + rest: rest + }; +} + +/** + * Resolve a relative URL pathname against a base URL pathname. + * + * @param {String} relative Pathname of the relative URL. + * @param {String} base Pathname of the base URL. + * @return {String} Resolved pathname. + * @private + */ +function resolve(relative, base) { + if (relative === '') return base; + + var path = (base || '/').split('/').slice(0, -1).concat(relative.split('/')) + , i = path.length + , last = path[i - 1] + , unshift = false + , up = 0; + + while (i--) { + if (path[i] === '.') { + path.splice(i, 1); + } else if (path[i] === '..') { + path.splice(i, 1); + up++; + } else if (up) { + if (i === 0) unshift = true; + path.splice(i, 1); + up--; + } + } + + if (unshift) path.unshift(''); + if (last === '.' || last === '..') path.push(''); + + return path.join('/'); +} + +/** + * The actual URL instance. Instead of returning an object we've opted-in to + * create an actual constructor as it's much more memory efficient and + * faster and it pleases my OCD. + * + * It is worth noting that we should not use `URL` as class name to prevent + * clashes with the global URL instance that got introduced in browsers. + * + * @constructor + * @param {String} address URL we want to parse. + * @param {Object|String} [location] Location defaults for relative paths. + * @param {Boolean|Function} [parser] Parser for the query string. + * @private + */ +function Url(address, location, parser) { + address = trimLeft(address); + address = address.replace(CRHTLF, ''); + + if (!(this instanceof Url)) { + return new Url(address, location, parser); + } + + var relative, extracted, parse, instruction, index, key + , instructions = rules.slice() + , type = typeof location + , url = this + , i = 0; + + // + // The following if statements allows this module two have compatibility with + // 2 different API: + // + // 1. Node.js's `url.parse` api which accepts a URL, boolean as arguments + // where the boolean indicates that the query string should also be parsed. + // + // 2. The `URL` interface of the browser which accepts a URL, object as + // arguments. The supplied object will be used as default values / fall-back + // for relative paths. + // + if ('object' !== type && 'string' !== type) { + parser = location; + location = null; + } + + if (parser && 'function' !== typeof parser) parser = qs.parse; + + location = lolcation(location); + + // + // Extract protocol information before running the instructions. + // + extracted = extractProtocol(address || '', location); + relative = !extracted.protocol && !extracted.slashes; + url.slashes = extracted.slashes || relative && location.slashes; + url.protocol = extracted.protocol || location.protocol || ''; + address = extracted.rest; + + // + // When the authority component is absent the URL starts with a path + // component. + // + if ( + extracted.protocol === 'file:' && ( + extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) || + (!extracted.slashes && + (extracted.protocol || + extracted.slashesCount < 2 || + !isSpecial(url.protocol))) + ) { + instructions[3] = [/(.*)/, 'pathname']; + } + + for (; i < instructions.length; i++) { + instruction = instructions[i]; + + if (typeof instruction === 'function') { + address = instruction(address, url); + continue; + } + + parse = instruction[0]; + key = instruction[1]; + + if (parse !== parse) { + url[key] = address; + } else if ('string' === typeof parse) { + index = parse === '@' + ? address.lastIndexOf(parse) + : address.indexOf(parse); + + if (~index) { + if ('number' === typeof instruction[2]) { + url[key] = address.slice(0, index); + address = address.slice(index + instruction[2]); + } else { + url[key] = address.slice(index); + address = address.slice(0, index); + } + } + } else if ((index = parse.exec(address))) { + url[key] = index[1]; + address = address.slice(0, index.index); + } + + url[key] = url[key] || ( + relative && instruction[3] ? location[key] || '' : '' + ); + + // + // Hostname, host and protocol should be lowercased so they can be used to + // create a proper `origin`. + // + if (instruction[4]) url[key] = url[key].toLowerCase(); + } + + // + // Also parse the supplied query string in to an object. If we're supplied + // with a custom parser as function use that instead of the default build-in + // parser. + // + if (parser) url.query = parser(url.query); + + // + // If the URL is relative, resolve the pathname against the base URL. + // + if ( + relative + && location.slashes + && url.pathname.charAt(0) !== '/' + && (url.pathname !== '' || location.pathname !== '') + ) { + url.pathname = resolve(url.pathname, location.pathname); + } + + // + // Default to a / for pathname if none exists. This normalizes the URL + // to always have a / + // + if (url.pathname.charAt(0) !== '/' && isSpecial(url.protocol)) { + url.pathname = '/' + url.pathname; + } + + // + // We should not add port numbers if they are already the default port number + // for a given protocol. As the host also contains the port number we're going + // override it with the hostname which contains no port number. + // + if (!required(url.port, url.protocol)) { + url.host = url.hostname; + url.port = ''; + } + + // + // Parse down the `auth` for the username and password. + // + url.username = url.password = ''; + + if (url.auth) { + index = url.auth.indexOf(':'); + + if (~index) { + url.username = url.auth.slice(0, index); + url.username = encodeURIComponent(decodeURIComponent(url.username)); + + url.password = url.auth.slice(index + 1); + url.password = encodeURIComponent(decodeURIComponent(url.password)) + } else { + url.username = encodeURIComponent(decodeURIComponent(url.auth)); + } + + url.auth = url.password ? url.username +':'+ url.password : url.username; + } + + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host + ? url.protocol +'//'+ url.host + : 'null'; + + // + // The href is just the compiled result. + // + url.href = url.toString(); +} + +/** + * This is convenience method for changing properties in the URL instance to + * insure that they all propagate correctly. + * + * @param {String} part Property we need to adjust. + * @param {Mixed} value The newly assigned value. + * @param {Boolean|Function} fn When setting the query, it will be the function + * used to parse the query. + * When setting the protocol, double slash will be + * removed from the final url if it is true. + * @returns {URL} URL instance for chaining. + * @public + */ +function set(part, value, fn) { + var url = this; + + switch (part) { + case 'query': + if ('string' === typeof value && value.length) { + value = (fn || qs.parse)(value); + } + + url[part] = value; + break; + + case 'port': + url[part] = value; + + if (!required(value, url.protocol)) { + url.host = url.hostname; + url[part] = ''; + } else if (value) { + url.host = url.hostname +':'+ value; + } + + break; + + case 'hostname': + url[part] = value; + + if (url.port) value += ':'+ url.port; + url.host = value; + break; + + case 'host': + url[part] = value; + + if (port.test(value)) { + value = value.split(':'); + url.port = value.pop(); + url.hostname = value.join(':'); + } else { + url.hostname = value; + url.port = ''; + } + + break; + + case 'protocol': + url.protocol = value.toLowerCase(); + url.slashes = !fn; + break; + + case 'pathname': + case 'hash': + if (value) { + var char = part === 'pathname' ? '/' : '#'; + url[part] = value.charAt(0) !== char ? char + value : value; + } else { + url[part] = value; + } + break; + + case 'username': + case 'password': + url[part] = encodeURIComponent(value); + break; + + case 'auth': + var index = value.indexOf(':'); + + if (~index) { + url.username = value.slice(0, index); + url.username = encodeURIComponent(decodeURIComponent(url.username)); + + url.password = value.slice(index + 1); + url.password = encodeURIComponent(decodeURIComponent(url.password)); + } else { + url.username = encodeURIComponent(decodeURIComponent(value)); + } + } + + for (var i = 0; i < rules.length; i++) { + var ins = rules[i]; + + if (ins[4]) url[ins[1]] = url[ins[1]].toLowerCase(); + } + + url.auth = url.password ? url.username +':'+ url.password : url.username; + + url.origin = url.protocol !== 'file:' && isSpecial(url.protocol) && url.host + ? url.protocol +'//'+ url.host + : 'null'; + + url.href = url.toString(); + + return url; +} + +/** + * Transform the properties back in to a valid and full URL string. + * + * @param {Function} stringify Optional query stringify function. + * @returns {String} Compiled version of the URL. + * @public + */ +function toString(stringify) { + if (!stringify || 'function' !== typeof stringify) stringify = qs.stringify; + + var query + , url = this + , host = url.host + , protocol = url.protocol; + + if (protocol && protocol.charAt(protocol.length - 1) !== ':') protocol += ':'; + + var result = + protocol + + ((url.protocol && url.slashes) || isSpecial(url.protocol) ? '//' : ''); + + if (url.username) { + result += url.username; + if (url.password) result += ':'+ url.password; + result += '@'; + } else if (url.password) { + result += ':'+ url.password; + result += '@'; + } else if ( + url.protocol !== 'file:' && + isSpecial(url.protocol) && + !host && + url.pathname !== '/' + ) { + // + // Add back the empty userinfo, otherwise the original invalid URL + // might be transformed into a valid one with `url.pathname` as host. + // + result += '@'; + } + + // + // Trailing colon is removed from `url.host` when it is parsed. If it still + // ends with a colon, then add back the trailing colon that was removed. This + // prevents an invalid URL from being transformed into a valid one. + // + if (host[host.length - 1] === ':' || (port.test(url.hostname) && !url.port)) { + host += ':'; + } + + result += host + url.pathname; + + query = 'object' === typeof url.query ? stringify(url.query) : url.query; + if (query) result += '?' !== query.charAt(0) ? '?'+ query : query; + + if (url.hash) result += url.hash; + + return result; +} + +Url.prototype = { set: set, toString: toString }; + +// +// Expose the URL parser and some additional properties that might be useful for +// others or testing. +// +Url.extractProtocol = extractProtocol; +Url.location = lolcation; +Url.trimLeft = trimLeft; +Url.qs = qs; + +module.exports = Url; + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"querystringify":8,"requires-port":11}],15:[function(_dereq_,module,exports){ +'use strict'; + +var alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split('') + , length = 64 + , map = {} + , seed = 0 + , i = 0 + , prev; + +/** + * Return a string representing the specified number. + * + * @param {Number} num The number to convert. + * @returns {String} The string representation of the number. + * @api public + */ +function encode(num) { + var encoded = ''; + + do { + encoded = alphabet[num % length] + encoded; + num = Math.floor(num / length); + } while (num > 0); + + return encoded; +} + +/** + * Return the integer value specified by the given string. + * + * @param {String} str The string to convert. + * @returns {Number} The integer value represented by the string. + * @api public + */ +function decode(str) { + var decoded = 0; + + for (i = 0; i < str.length; i++) { + decoded = decoded * length + map[str.charAt(i)]; + } + + return decoded; +} + +/** + * Yeast: A tiny growing id generator. + * + * @returns {String} A unique id. + * @api public + */ +function yeast() { + var now = encode(+new Date()); + + if (now !== prev) return seed = 0, prev = now; + return now +'.'+ encode(seed++); +} + +// +// Map each character to its index. +// +for (; i < length; i++) map[alphabet[i]] = i; + +// +// Expose the `yeast`, `encode` and `decode` functions. +// +yeast.encode = encode; +yeast.decode = decode; +module.exports = yeast; + +},{}],16:[function(_dereq_,module,exports){ +/*globals require, define */ +'use strict'; + +var EventEmitter = _dereq_('eventemitter3') + , TickTock = _dereq_('tick-tock') + , Recovery = _dereq_('recovery') + , qs = _dereq_('querystringify') + , inherits = _dereq_('inherits') + , destroy = _dereq_('demolish') + , yeast = _dereq_('yeast') + , u2028 = /\u2028/g + , u2029 = /\u2029/g; + +/** + * Context assertion, ensure that some of our public Primus methods are called + * with the correct context to ensure that + * + * @param {Primus} self The context of the function. + * @param {String} method The method name. + * @api private + */ +function context(self, method) { + if (self instanceof Primus) return; + + var failure = new Error('Primus#'+ method + '\'s context should called with a Primus instance'); + + if ('function' !== typeof self.listeners || !self.listeners('error').length) { + throw failure; + } + + self.emit('error', failure); +} + +// +// Sets the default connection URL, it uses the default origin of the browser +// when supported but degrades for older browsers. In Node.js, we cannot guess +// where the user wants to connect to, so we just default to localhost. +// +var defaultUrl; + +try { + if (location.origin) { + defaultUrl = location.origin; + } else { + defaultUrl = location.protocol +'//'+ location.host; + } +} catch (e) { + defaultUrl = 'http://127.0.0.1'; +} + +/** + * Primus is a real-time library agnostic framework for establishing real-time + * connections with servers. + * + * Options: + * - reconnect, configuration for the reconnect process. + * - manual, don't automatically call `.open` to start the connection. + * - websockets, force the use of WebSockets, even when you should avoid them. + * - timeout, connect timeout, server didn't respond in a timely manner. + * - pingTimeout, The maximum amount of time to wait for the server to send a ping. + * - network, Use network events as leading method for network connection drops. + * - strategy, Reconnection strategies. + * - transport, Transport options. + * - url, uri, The URL to use connect with the server. + * + * @constructor + * @param {String} url The URL of your server. + * @param {Object} options The configuration. + * @api public + */ +function Primus(url, options) { + if (!(this instanceof Primus)) return new Primus(url, options); + + Primus.Stream.call(this); + + if ('function' !== typeof this.client) { + return this.critical(new Error( + 'The client library has not been compiled correctly, see '+ + 'https://github.com/primus/primus#client-library for more details' + )); + } + + if ('object' === typeof url) { + options = url; + url = options.url || options.uri || defaultUrl; + } else { + options = options || {}; + } + + if ('ping' in options || 'pong' in options) { + return this.critical(new Error( + 'The `ping` and `pong` options have been removed' + )); + } + + var primus = this; + + // The maximum number of messages that can be placed in queue. + options.queueSize = 'queueSize' in options ? options.queueSize : Infinity; + + // Connection timeout duration. + options.timeout = 'timeout' in options ? options.timeout : 10e3; + + // Stores the back off configuration. + options.reconnect = 'reconnect' in options ? options.reconnect : {}; + + // Heartbeat ping interval. + options.pingTimeout = 'pingTimeout' in options ? options.pingTimeout : 45000; + + // Reconnect strategies. + options.strategy = 'strategy' in options ? options.strategy : []; + + // Custom transport options. + options.transport = 'transport' in options ? options.transport : {}; + + primus.buffer = []; // Stores premature send data. + primus.writable = true; // Silly stream compatibility. + primus.readable = true; // Silly stream compatibility. + primus.url = primus.parse(url || defaultUrl); // Parse the URL to a readable format. + primus.readyState = Primus.CLOSED; // The readyState of the connection. + primus.options = options; // Reference to the supplied options. + primus.timers = new TickTock(this); // Contains all our timers. + primus.socket = null; // Reference to the internal connection. + primus.disconnect = false; // Did we receive a disconnect packet? + primus.transport = options.transport; // Transport options. + primus.transformers = { // Message transformers. + outgoing: [], + incoming: [] + }; + + // + // Create our reconnection instance. + // + primus.recovery = new Recovery(options.reconnect); + + // + // Parse the reconnection strategy. It can have the following strategies: + // + // - timeout: Reconnect when we have a network timeout. + // - disconnect: Reconnect when we have an unexpected disconnect. + // - online: Reconnect when we're back online. + // + if ('string' === typeof options.strategy) { + options.strategy = options.strategy.split(/\s?,\s?/g); + } + + if (false === options.strategy) { + // + // Strategies are disabled, but we still need an empty array to join it in + // to nothing. + // + options.strategy = []; + } else if (!options.strategy.length) { + options.strategy.push('disconnect', 'online'); + + // + // Timeout based reconnection should only be enabled conditionally. When + // authorization is enabled it could trigger. + // + if (!this.authorization) options.strategy.push('timeout'); + } + + options.strategy = options.strategy.join(',').toLowerCase(); + + // + // Force the use of WebSockets, even when we've detected some potential + // broken WebSocket implementation. + // + if ('websockets' in options) { + primus.AVOID_WEBSOCKETS = !options.websockets; + } + + // + // Force or disable the use of NETWORK events as leading client side + // disconnection detection. + // + if ('network' in options) { + primus.NETWORK_EVENTS = options.network; + } + + // + // Check if the user wants to manually initialise a connection. If they don't, + // we want to do it after a really small timeout so we give the users enough + // time to listen for `error` events etc. + // + if (!options.manual) primus.timers.setTimeout('open', function open() { + primus.timers.clear('open'); + primus.open(); + }, 0); + + primus.initialise(options); +} + +/** + * Simple require wrapper to make browserify, node and require.js play nice. + * + * @param {String} name The module to require. + * @returns {Object|Undefined} The module that we required. + * @api private + */ +Primus.requires = Primus.require = function requires(name) { + if ('function' !== typeof _dereq_) return undefined; + + return !('function' === typeof define && define.amd) + ? _dereq_(name) + : undefined; +}; + +// +// It's possible that we're running in Node.js or in a Node.js compatible +// environment. In this cases we try to inherit from the Stream base class. +// +try { + Primus.Stream = Primus.requires('stream'); +} catch (e) { } + +if (!Primus.Stream) Primus.Stream = EventEmitter; + +inherits(Primus, Primus.Stream); + +/** + * Primus readyStates, used internally to set the correct ready state. + * + * @type {Number} + * @private + */ +Primus.OPENING = 1; // We're opening the connection. +Primus.CLOSED = 2; // No active connection. +Primus.OPEN = 3; // The connection is open. + +/** + * Are we working with a potentially broken WebSockets implementation? This + * boolean can be used by transformers to remove `WebSockets` from their + * supported transports. + * + * @type {Boolean} + * @private + */ +Primus.prototype.AVOID_WEBSOCKETS = false; + +/** + * Some browsers support registering emitting `online` and `offline` events when + * the connection has been dropped on the client. We're going to detect it in + * a simple `try {} catch (e) {}` statement so we don't have to do complicated + * feature detection. + * + * @type {Boolean} + * @private + */ +Primus.prototype.NETWORK_EVENTS = false; +Primus.prototype.online = true; + +try { + if ( + Primus.prototype.NETWORK_EVENTS = 'onLine' in navigator + && (window.addEventListener || document.body.attachEvent) + ) { + if (!navigator.onLine) { + Primus.prototype.online = false; + } + } +} catch (e) { } + +/** + * The Ark contains all our plugins definitions. It's namespaced by + * name => plugin. + * + * @type {Object} + * @private + */ +Primus.prototype.ark = {}; + +/** + * Simple emit wrapper that returns a function that emits an event once it's + * called. This makes it easier for transports to emit specific events. + * + * @returns {Function} A function that will emit the event when called. + * @api public + */ +Primus.prototype.emits = _dereq_('emits'); + +/** + * Return the given plugin. + * + * @param {String} name The name of the plugin. + * @returns {Object|undefined} The plugin or undefined. + * @api public + */ +Primus.prototype.plugin = function plugin(name) { + context(this, 'plugin'); + + if (name) return this.ark[name]; + + var plugins = {}; + + for (name in this.ark) { + plugins[name] = this.ark[name]; + } + + return plugins; +}; + +/** + * Checks if the given event is an emitted event by Primus. + * + * @param {String} evt The event name. + * @returns {Boolean} Indication of the event is reserved for internal use. + * @api public + */ +Primus.prototype.reserved = function reserved(evt) { + return (/^(incoming|outgoing)::/).test(evt) + || evt in this.reserved.events; +}; + +/** + * The actual events that are used by the client. + * + * @type {Object} + * @public + */ +Primus.prototype.reserved.events = { + 'reconnect scheduled': 1, + 'reconnect timeout': 1, + 'readyStateChange': 1, + 'reconnect failed': 1, + 'reconnected': 1, + 'reconnect': 1, + 'offline': 1, + 'timeout': 1, + 'destroy': 1, + 'online': 1, + 'error': 1, + 'close': 1, + 'open': 1, + 'data': 1, + 'end': 1 +}; + +/** + * Initialise the Primus and setup all parsers and internal listeners. + * + * @param {Object} options The original options object. + * @returns {Primus} + * @api private + */ +Primus.prototype.initialise = function initialise(options) { + var primus = this; + + primus.recovery + .on('reconnected', primus.emits('reconnected')) + .on('reconnect failed', primus.emits('reconnect failed', function failed(next) { + primus.emit('end'); + next(); + })) + .on('reconnect timeout', primus.emits('reconnect timeout')) + .on('reconnect scheduled', primus.emits('reconnect scheduled')) + .on('reconnect', primus.emits('reconnect', function reconnect(next) { + primus.emit('outgoing::reconnect'); + next(); + })); + + primus.on('outgoing::open', function opening() { + var readyState = primus.readyState; + + primus.readyState = Primus.OPENING; + if (readyState !== primus.readyState) { + primus.emit('readyStateChange', 'opening'); + } + }); + + primus.on('incoming::open', function opened() { + var readyState = primus.readyState; + + if (primus.recovery.reconnecting()) { + primus.recovery.reconnected(); + } + + // + // The connection has been opened so we should set our state to + // (writ|read)able so our stream compatibility works as intended. + // + primus.writable = true; + primus.readable = true; + + // + // Make sure we are flagged as `online` as we've successfully opened the + // connection. + // + if (!primus.online) { + primus.online = true; + primus.emit('online'); + } + + primus.readyState = Primus.OPEN; + if (readyState !== primus.readyState) { + primus.emit('readyStateChange', 'open'); + } + + primus.heartbeat(); + + if (primus.buffer.length) { + var data = primus.buffer.slice() + , length = data.length + , i = 0; + + primus.buffer.length = 0; + + for (; i < length; i++) { + primus._write(data[i]); + } + } + + primus.emit('open'); + }); + + primus.on('incoming::ping', function ping(time) { + primus.online = true; + primus.heartbeat(); + primus.emit('outgoing::pong', time); + primus._write('primus::pong::'+ time); + }); + + primus.on('incoming::error', function error(e) { + var connect = primus.timers.active('connect') + , err = e; + + // + // When the error is not an Error instance we try to normalize it. + // + if ('string' === typeof e) { + err = new Error(e); + } else if (!(e instanceof Error) && 'object' === typeof e) { + // + // BrowserChannel and SockJS returns an object which contains some + // details of the error. In order to have a proper error we "copy" the + // details in an Error instance. + // + err = new Error(e.message || e.reason); + for (var key in e) { + if (Object.prototype.hasOwnProperty.call(e, key)) + err[key] = e[key]; + } + } + // + // We're still doing a reconnect attempt, it could be that we failed to + // connect because the server was down. Failing connect attempts should + // always emit an `error` event instead of a `open` event. + // + // + if (primus.recovery.reconnecting()) return primus.recovery.reconnected(err); + if (primus.listeners('error').length) primus.emit('error', err); + + // + // We received an error while connecting, this most likely the result of an + // unauthorized access to the server. + // + if (connect) { + if (~primus.options.strategy.indexOf('timeout')) { + primus.recovery.reconnect(); + } else { + primus.end(); + } + } + }); + + primus.on('incoming::data', function message(raw) { + primus.decoder(raw, function decoding(err, data) { + // + // Do a "safe" emit('error') when we fail to parse a message. We don't + // want to throw here as listening to errors should be optional. + // + if (err) return primus.listeners('error').length && primus.emit('error', err); + + // + // Handle all "primus::" prefixed protocol messages. + // + if (primus.protocol(data)) return; + primus.transforms(primus, primus, 'incoming', data, raw); + }); + }); + + primus.on('incoming::end', function end() { + var readyState = primus.readyState; + + // + // This `end` started with the receiving of a primus::server::close packet + // which indicated that the user/developer on the server closed the + // connection and it was not a result of a network disruption. So we should + // kill the connection without doing a reconnect. + // + if (primus.disconnect) { + primus.disconnect = false; + + return primus.end(); + } + + // + // Always set the readyState to closed, and if we're still connecting, close + // the connection so we're sure that everything after this if statement block + // is only executed because our readyState is set to `open`. + // + primus.readyState = Primus.CLOSED; + if (readyState !== primus.readyState) { + primus.emit('readyStateChange', 'end'); + } + + if (primus.timers.active('connect')) primus.end(); + if (readyState !== Primus.OPEN) { + return primus.recovery.reconnecting() + ? primus.recovery.reconnect() + : false; + } + + this.writable = false; + this.readable = false; + + // + // Clear all timers in case we're not going to reconnect. + // + this.timers.clear(); + + // + // Fire the `close` event as an indication of connection disruption. + // This is also fired by `primus#end` so it is emitted in all cases. + // + primus.emit('close'); + + // + // The disconnect was unintentional, probably because the server has + // shutdown, so if the reconnection is enabled start a reconnect procedure. + // + if (~primus.options.strategy.indexOf('disconnect')) { + return primus.recovery.reconnect(); + } + + primus.emit('outgoing::end'); + primus.emit('end'); + }); + + // + // Setup the real-time client. + // + primus.client(); + + // + // Process the potential plugins. + // + for (var plugin in primus.ark) { + primus.ark[plugin].call(primus, primus, options); + } + + // + // NOTE: The following code is only required if we're supporting network + // events as it requires access to browser globals. + // + if (!primus.NETWORK_EVENTS) return primus; + + /** + * Handler for offline notifications. + * + * @api private + */ + primus.offlineHandler = function offline() { + if (!primus.online) return; // Already or still offline, bailout. + + primus.online = false; + primus.emit('offline'); + primus.end(); + + // + // It is certainly possible that we're in a reconnection loop and that the + // user goes offline. In this case we want to kill the existing attempt so + // when the user goes online, it will attempt to reconnect freshly again. + // + primus.recovery.reset(); + }; + + /** + * Handler for online notifications. + * + * @api private + */ + primus.onlineHandler = function online() { + if (primus.online) return; // Already or still online, bailout. + + primus.online = true; + primus.emit('online'); + + if (~primus.options.strategy.indexOf('online')) { + primus.recovery.reconnect(); + } + }; + + if (window.addEventListener) { + window.addEventListener('offline', primus.offlineHandler, false); + window.addEventListener('online', primus.onlineHandler, false); + } else if (document.body.attachEvent){ + document.body.attachEvent('onoffline', primus.offlineHandler); + document.body.attachEvent('ononline', primus.onlineHandler); + } + + return primus; +}; + +/** + * Really dead simple protocol parser. We simply assume that every message that + * is prefixed with `primus::` could be used as some sort of protocol definition + * for Primus. + * + * @param {String} msg The data. + * @returns {Boolean} Is a protocol message. + * @api private + */ +Primus.prototype.protocol = function protocol(msg) { + if ( + 'string' !== typeof msg + || msg.indexOf('primus::') !== 0 + ) return false; + + var last = msg.indexOf(':', 8) + , value = msg.slice(last + 2); + + switch (msg.slice(8, last)) { + case 'ping': + this.emit('incoming::ping', +value); + break; + + case 'server': + // + // The server is closing the connection, forcefully disconnect so we don't + // reconnect again. + // + if ('close' === value) { + this.disconnect = true; + } + break; + + case 'id': + this.emit('incoming::id', value); + break; + + // + // Unknown protocol, somebody is probably sending `primus::` prefixed + // messages. + // + default: + return false; + } + + return true; +}; + +/** + * Execute the set of message transformers from Primus on the incoming or + * outgoing message. + * This function and it's content should be in sync with Spark#transforms in + * spark.js. + * + * @param {Primus} primus Reference to the Primus instance with message transformers. + * @param {Spark|Primus} connection Connection that receives or sends data. + * @param {String} type The type of message, 'incoming' or 'outgoing'. + * @param {Mixed} data The data to send or that has been received. + * @param {String} raw The raw encoded data. + * @returns {Primus} + * @api public + */ +Primus.prototype.transforms = function transforms(primus, connection, type, data, raw) { + var packet = { data: data } + , fns = primus.transformers[type]; + + // + // Iterate in series over the message transformers so we can allow optional + // asynchronous execution of message transformers which could for example + // retrieve additional data from the server, do extra decoding or even + // message validation. + // + (function transform(index, done) { + var transformer = fns[index++]; + + if (!transformer) return done(); + + if (1 === transformer.length) { + if (false === transformer.call(connection, packet)) { + // + // When false is returned by an incoming transformer it means that's + // being handled by the transformer and we should not emit the `data` + // event. + // + return; + } + + return transform(index, done); + } + + transformer.call(connection, packet, function finished(err, arg) { + if (err) return connection.emit('error', err); + if (false === arg) return; + + transform(index, done); + }); + }(0, function done() { + // + // We always emit 2 arguments for the data event, the first argument is the + // parsed data and the second argument is the raw string that we received. + // This allows you, for example, to do some validation on the parsed data + // and then save the raw string in your database without the stringify + // overhead. + // + if ('incoming' === type) return connection.emit('data', packet.data, raw); + + connection._write(packet.data); + })); + + return this; +}; + +/** + * Retrieve the current id from the server. + * + * @param {Function} fn Callback function. + * @returns {Primus} + * @api public + */ +Primus.prototype.id = function id(fn) { + if (this.socket && this.socket.id) return fn(this.socket.id); + + this._write('primus::id::'); + return this.once('incoming::id', fn); +}; + +/** + * Establish a connection with the server. When this function is called we + * assume that we don't have any open connections. If you do call it when you + * have a connection open, it could cause duplicate connections. + * + * @returns {Primus} + * @api public + */ +Primus.prototype.open = function open() { + context(this, 'open'); + + // + // Only start a `connection timeout` procedure if we're not reconnecting as + // that shouldn't count as an initial connection. This should be started + // before the connection is opened to capture failing connections and kill the + // timeout. + // + if (!this.recovery.reconnecting() && this.options.timeout) this.timeout(); + + this.emit('outgoing::open'); + return this; +}; + +/** + * Send a new message. + * + * @param {Mixed} data The data that needs to be written. + * @returns {Boolean} Always returns true as we don't support back pressure. + * @api public + */ +Primus.prototype.write = function write(data) { + context(this, 'write'); + this.transforms(this, this, 'outgoing', data); + + return true; +}; + +/** + * The actual message writer. + * + * @param {Mixed} data The message that needs to be written. + * @returns {Boolean} Successful write to the underlaying transport. + * @api private + */ +Primus.prototype._write = function write(data) { + var primus = this; + + // + // The connection is closed, normally this would already be done in the + // `spark.write` method, but as `_write` is used internally, we should also + // add the same check here to prevent potential crashes by writing to a dead + // socket. + // + if (Primus.OPEN !== primus.readyState) { + // + // If the buffer is at capacity, remove the first item. + // + if (this.buffer.length === this.options.queueSize) { + this.buffer.splice(0, 1); + } + + this.buffer.push(data); + return false; + } + + primus.encoder(data, function encoded(err, packet) { + // + // Do a "safe" emit('error') when we fail to parse a message. We don't + // want to throw here as listening to errors should be optional. + // + if (err) return primus.listeners('error').length && primus.emit('error', err); + + // + // Hack 1: \u2028 and \u2029 are allowed inside a JSON string, but JavaScript + // defines them as newline separators. Unescaped control characters are not + // allowed inside JSON strings, so this causes an error at parse time. We + // work around this issue by escaping these characters. This can cause + // errors with JSONP requests or if the string is just evaluated. + // + if ('string' === typeof packet) { + if (~packet.indexOf('\u2028')) packet = packet.replace(u2028, '\\u2028'); + if (~packet.indexOf('\u2029')) packet = packet.replace(u2029, '\\u2029'); + } + + primus.emit('outgoing::data', packet); + }); + + return true; +}; + +/** + * Set a timer that, upon expiration, closes the client. + * + * @returns {Primus} + * @api private + */ +Primus.prototype.heartbeat = function heartbeat() { + if (!this.options.pingTimeout) return this; + + this.timers.clear('heartbeat'); + this.timers.setTimeout('heartbeat', function expired() { + // + // The network events already captured the offline event. + // + if (!this.online) return; + + this.online = false; + this.emit('offline'); + this.emit('incoming::end'); + }, this.options.pingTimeout); + + return this; +}; + +/** + * Start a connection timeout. + * + * @returns {Primus} + * @api private + */ +Primus.prototype.timeout = function timeout() { + var primus = this; + + /** + * Remove all references to the timeout listener as we've received an event + * that can be used to determine state. + * + * @api private + */ + function remove() { + primus.removeListener('error', remove) + .removeListener('open', remove) + .removeListener('end', remove) + .timers.clear('connect'); + } + + primus.timers.setTimeout('connect', function expired() { + remove(); // Clean up old references. + + if (primus.readyState === Primus.OPEN || primus.recovery.reconnecting()) { + return; + } + + primus.emit('timeout'); + + // + // We failed to connect to the server. + // + if (~primus.options.strategy.indexOf('timeout')) { + primus.recovery.reconnect(); + } else { + primus.end(); + } + }, primus.options.timeout); + + return primus.on('error', remove) + .on('open', remove) + .on('end', remove); +}; + +/** + * Close the connection completely. + * + * @param {Mixed} data last packet of data. + * @returns {Primus} + * @api public + */ +Primus.prototype.end = function end(data) { + context(this, 'end'); + + if ( + this.readyState === Primus.CLOSED + && !this.timers.active('connect') + && !this.timers.active('open') + ) { + // + // If we are reconnecting stop the reconnection procedure. + // + if (this.recovery.reconnecting()) { + this.recovery.reset(); + this.emit('end'); + } + + return this; + } + + if (data !== undefined) this.write(data); + + this.writable = false; + this.readable = false; + + var readyState = this.readyState; + this.readyState = Primus.CLOSED; + + if (readyState !== this.readyState) { + this.emit('readyStateChange', 'end'); + } + + this.timers.clear(); + this.emit('outgoing::end'); + this.emit('close'); + this.emit('end'); + + return this; +}; + +/** + * Completely demolish the Primus instance and forcefully nuke all references. + * + * @returns {Boolean} + * @api public + */ +Primus.prototype.destroy = destroy('url timers options recovery socket transport transformers', { + before: 'end', + after: ['removeAllListeners', function detach() { + if (!this.NETWORK_EVENTS) return; + + if (window.addEventListener) { + window.removeEventListener('offline', this.offlineHandler); + window.removeEventListener('online', this.onlineHandler); + } else if (document.body.attachEvent){ + document.body.detachEvent('onoffline', this.offlineHandler); + document.body.detachEvent('ononline', this.onlineHandler); + } + }] +}); + +/** + * Create a shallow clone of a given object. + * + * @param {Object} obj The object that needs to be cloned. + * @returns {Object} Copy. + * @api private + */ +Primus.prototype.clone = function clone(obj) { + return this.merge({}, obj); +}; + +/** + * Merge different objects in to one target object. + * + * @param {Object} target The object where everything should be merged in. + * @returns {Object} Original target with all merged objects. + * @api private + */ +Primus.prototype.merge = function merge(target) { + for (var i = 1, key, obj; i < arguments.length; i++) { + obj = arguments[i]; + + for (key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) + target[key] = obj[key]; + } + } + + return target; +}; + +/** + * Parse the connection string. + * + * @type {Function} + * @param {String} url Connection URL. + * @returns {Object} Parsed connection. + * @api private + */ +Primus.prototype.parse = _dereq_('url-parse'); + +/** + * Parse a query string. + * + * @param {String} query The query string that needs to be parsed. + * @returns {Object} Parsed query string. + * @api private + */ +Primus.prototype.querystring = qs.parse; +/** + * Transform a query string object back into string equiv. + * + * @param {Object} obj The query string object. + * @returns {String} + * @api private + */ +Primus.prototype.querystringify = qs.stringify; + +/** + * Generates a connection URI. + * + * @param {String} protocol The protocol that should used to crate the URI. + * @returns {String|options} The URL. + * @api private + */ +Primus.prototype.uri = function uri(options) { + var url = this.url + , server = [] + , qsa = false; + + // + // Query strings are only allowed when we've received clearance for it. + // + if (options.query) qsa = true; + + options = options || {}; + options.protocol = 'protocol' in options + ? options.protocol + : 'http:'; + options.query = url.query && qsa + ? url.query.slice(1) + : false; + options.secure = 'secure' in options + ? options.secure + : url.protocol === 'https:' || url.protocol === 'wss:'; + options.auth = 'auth' in options + ? options.auth + : url.auth; + options.pathname = 'pathname' in options + ? options.pathname + : this.pathname; + options.port = 'port' in options + ? +options.port + : +url.port || (options.secure ? 443 : 80); + + // + // We need to make sure that we create a unique connection URL every time to + // prevent back forward cache from becoming an issue. We're doing this by + // forcing an cache busting query string in to the URL. + // + var querystring = this.querystring(options.query || ''); + querystring._primuscb = yeast(); + options.query = this.querystringify(querystring); + + // + // Allow transformation of the options before we construct a full URL from it. + // + this.emit('outgoing::url', options); + + // + // Automatically suffix the protocol so we can supply `ws:` and `http:` and + // it gets transformed correctly. + // + server.push(options.secure ? options.protocol.replace(':', 's:') : options.protocol, ''); + + server.push(options.auth ? options.auth +'@'+ url.host : url.host); + + // + // Pathnames are optional as some Transformers would just use the pathname + // directly. + // + if (options.pathname) server.push(options.pathname.slice(1)); + + // + // Optionally add a search query. + // + if (qsa) server[server.length - 1] += '?'+ options.query; + else delete options.query; + + if (options.object) return options; + return server.join('/'); +}; + +/** + * Register a new message transformer. This allows you to easily manipulate incoming + * and outgoing data which is particularity handy for plugins that want to send + * meta data together with the messages. + * + * @param {String} type Incoming or outgoing + * @param {Function} fn A new message transformer. + * @returns {Primus} + * @api public + */ +Primus.prototype.transform = function transform(type, fn) { + context(this, 'transform'); + + if (!(type in this.transformers)) { + return this.critical(new Error('Invalid transformer type')); + } + + this.transformers[type].push(fn); + return this; +}; + +/** + * A critical error has occurred, if we have an `error` listener, emit it there. + * If not, throw it, so we get a stack trace + proper error message. + * + * @param {Error} err The critical error. + * @returns {Primus} + * @api private + */ +Primus.prototype.critical = function critical(err) { + if (this.emit('error', err)) return this; + + throw err; +}; + +/** + * Syntax sugar, adopt a Socket.IO like API. + * + * @param {String} url The URL we want to connect to. + * @param {Object} options Connection options. + * @returns {Primus} + * @api public + */ +Primus.connect = function connect(url, options) { + return new Primus(url, options); +}; + +// +// Expose the EventEmitter so it can be re-used by wrapping libraries we're also +// exposing the Stream interface. +// +Primus.EventEmitter = EventEmitter; + +// +// These libraries are automatically inserted at the server-side using the +// Primus#library method. +// +Primus.prototype.client = function client() { + var onmessage = this.emits('incoming::data') + , onerror = this.emits('incoming::error') + , onopen = this.emits('incoming::open') + , onclose = this.emits('incoming::end') + , primus = this + , socket; + + // + // Select an available Engine.IO factory. + // + var factory = (function factory() { + if ('undefined' !== typeof eio) return eio; + + try { + var Socket = Primus.requires('engine.io-client').Socket; + + return function eio(options) { + return new Socket(options); + } + } catch (e) {} + + return undefined; + })(); + + if (!factory) return primus.critical(new Error( + 'Missing required `engine.io-client` module. ' + + 'Please run `npm install --save engine.io-client`' + )); + + // + // Connect to the given URL. + // + primus.on('outgoing::open', function opening() { + primus.emit('outgoing::end'); + + primus.socket = socket = factory(primus.merge(primus.transport, + primus.url, + primus.uri({ protocol: 'http:', query: true, object: true }), { + // + // Never remember upgrades as switching from a WIFI to a 3G connection + // could still get your connection blocked as 3G connections are usually + // behind a reverse proxy so ISP's can optimize mobile traffic by + // caching requests. + // + rememberUpgrade: false, + + // + // Binary support in Engine.IO breaks a shit things. Turn it off for now. + // + forceBase64: true, + + // + // Force timestamps on every single connection. Engine.IO only does this + // for polling by default, but WebSockets require an explicit `true` + // boolean. + // + timestampRequests: true, + path: this.pathname, + transports: !primus.AVOID_WEBSOCKETS + ? ['polling', 'websocket'] + : ['polling'] + })); + + // + // Setup the Event handlers. + // + socket.on('message', onmessage); + socket.on('error', onerror); + socket.on('close', onclose); + socket.on('open', onopen); + }); + + // + // We need to write a new message to the socket. + // + primus.on('outgoing::data', function write(message) { + if (socket) socket.send(message); + }); + + // + // Attempt to reconnect the socket. + // + primus.on('outgoing::reconnect', function reconnect() { + primus.emit('outgoing::open'); + }); + + // + // We need to close the socket. + // + primus.on('outgoing::end', function close() { + if (!socket) return; + + socket.removeListener('message', onmessage); + socket.removeListener('error', onerror); + socket.removeListener('close', onclose); + socket.removeListener('open', onopen); + socket.close(); + socket = null; + }); +}; +Primus.prototype.authorization = false; +Primus.prototype.pathname = "/sugar"; +Primus.prototype.encoder = function encoder(data, fn) { + var err; + + try { data = JSON.stringify(data); } + catch (e) { err = e; } + + fn(err, data); +}; +Primus.prototype.decoder = function decoder(data, fn) { + var err; + + if ('string' !== typeof data) return fn(err, data); + + try { data = JSON.parse(data); } + catch (e) { err = e; } + + fn(err, data); +}; +Primus.prototype.version = "8.0.7"; + +if ( + 'undefined' !== typeof document + && 'undefined' !== typeof navigator +) { + // + // Hack 2: If you press ESC in FireFox it will close all active connections. + // Normally this makes sense, when your page is still loading. But versions + // before FireFox 22 will close all connections including WebSocket connections + // after page load. One way to prevent this is to do a `preventDefault()` and + // cancel the operation before it bubbles up to the browsers default handler. + // It needs to be added as `keydown` event, if it's added keyup it will not be + // able to prevent the connection from being closed. + // + if (document.addEventListener) { + document.addEventListener('keydown', function keydown(e) { + if (e.keyCode !== 27 || !e.preventDefault) return; + + e.preventDefault(); + }, false); + } + + // + // Hack 3: This is a Mac/Apple bug only, when you're behind a reverse proxy or + // have you network settings set to `automatic proxy discovery` the safari + // browser will crash when the WebSocket constructor is initialised. There is + // no way to detect the usage of these proxies available in JavaScript so we + // need to do some nasty browser sniffing. This only affects Safari versions + // lower then 5.1.4 + // + var ua = (navigator.userAgent || '').toLowerCase() + , parsed = ua.match(/.+(?:rv|it|ra|ie)[/: ](\d+)\.(\d+)(?:\.(\d+))?/) || [] + , version = +[parsed[1], parsed[2]].join('.'); + + if ( + !~ua.indexOf('chrome') + && ~ua.indexOf('safari') + && version < 534.54 + ) { + Primus.prototype.AVOID_WEBSOCKETS = true; + } +} + +// +// Expose the library. +// +module.exports = Primus; + +},{"demolish":1,"emits":2,"eventemitter3":3,"inherits":4,"querystringify":8,"recovery":9,"tick-tock":12,"url-parse":14,"yeast":15}]},{},[16])(16) +; + return Primus; +}, +[ +function (Primus) { +(function (f) { + var g; + + if (typeof window !== 'undefined') { + g = window; + } else if (typeof self !== 'undefined') { + g = self; + } + + g.eio = f(); +})(function () { +var eio = (function () { + 'use strict'; + + function _typeof(obj) { + "@babel/helpers - typeof"; + + return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { + return typeof obj; + } : function (obj) { + return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; + }, _typeof(obj); + } + function _classCallCheck(instance, Constructor) { + if (!(instance instanceof Constructor)) { + throw new TypeError("Cannot call a class as a function"); + } + } + function _defineProperties(target, props) { + for (var i = 0; i < props.length; i++) { + var descriptor = props[i]; + descriptor.enumerable = descriptor.enumerable || false; + descriptor.configurable = true; + if ("value" in descriptor) descriptor.writable = true; + Object.defineProperty(target, _toPropertyKey(descriptor.key), descriptor); + } + } + function _createClass(Constructor, protoProps, staticProps) { + if (protoProps) _defineProperties(Constructor.prototype, protoProps); + if (staticProps) _defineProperties(Constructor, staticProps); + Object.defineProperty(Constructor, "prototype", { + writable: false + }); + return Constructor; + } + function _extends() { + _extends = Object.assign ? Object.assign.bind() : function (target) { + for (var i = 1; i < arguments.length; i++) { + var source = arguments[i]; + for (var key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + target[key] = source[key]; + } + } + } + return target; + }; + return _extends.apply(this, arguments); + } + function _inherits(subClass, superClass) { + if (typeof superClass !== "function" && superClass !== null) { + throw new TypeError("Super expression must either be null or a function"); + } + subClass.prototype = Object.create(superClass && superClass.prototype, { + constructor: { + value: subClass, + writable: true, + configurable: true + } + }); + Object.defineProperty(subClass, "prototype", { + writable: false + }); + if (superClass) _setPrototypeOf(subClass, superClass); + } + function _getPrototypeOf(o) { + _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf.bind() : function _getPrototypeOf(o) { + return o.__proto__ || Object.getPrototypeOf(o); + }; + return _getPrototypeOf(o); + } + function _setPrototypeOf(o, p) { + _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { + o.__proto__ = p; + return o; + }; + return _setPrototypeOf(o, p); + } + function _isNativeReflectConstruct() { + if (typeof Reflect === "undefined" || !Reflect.construct) return false; + if (Reflect.construct.sham) return false; + if (typeof Proxy === "function") return true; + try { + Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {})); + return true; + } catch (e) { + return false; + } + } + function _construct(Parent, args, Class) { + if (_isNativeReflectConstruct()) { + _construct = Reflect.construct.bind(); + } else { + _construct = function _construct(Parent, args, Class) { + var a = [null]; + a.push.apply(a, args); + var Constructor = Function.bind.apply(Parent, a); + var instance = new Constructor(); + if (Class) _setPrototypeOf(instance, Class.prototype); + return instance; + }; + } + return _construct.apply(null, arguments); + } + function _isNativeFunction(fn) { + return Function.toString.call(fn).indexOf("[native code]") !== -1; + } + function _wrapNativeSuper(Class) { + var _cache = typeof Map === "function" ? new Map() : undefined; + _wrapNativeSuper = function _wrapNativeSuper(Class) { + if (Class === null || !_isNativeFunction(Class)) return Class; + if (typeof Class !== "function") { + throw new TypeError("Super expression must either be null or a function"); + } + if (typeof _cache !== "undefined") { + if (_cache.has(Class)) return _cache.get(Class); + _cache.set(Class, Wrapper); + } + function Wrapper() { + return _construct(Class, arguments, _getPrototypeOf(this).constructor); + } + Wrapper.prototype = Object.create(Class.prototype, { + constructor: { + value: Wrapper, + enumerable: false, + writable: true, + configurable: true + } + }); + return _setPrototypeOf(Wrapper, Class); + }; + return _wrapNativeSuper(Class); + } + function _assertThisInitialized(self) { + if (self === void 0) { + throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); + } + return self; + } + function _possibleConstructorReturn(self, call) { + if (call && (typeof call === "object" || typeof call === "function")) { + return call; + } else if (call !== void 0) { + throw new TypeError("Derived constructors may only return object or undefined"); + } + return _assertThisInitialized(self); + } + function _createSuper(Derived) { + var hasNativeReflectConstruct = _isNativeReflectConstruct(); + return function _createSuperInternal() { + var Super = _getPrototypeOf(Derived), + result; + if (hasNativeReflectConstruct) { + var NewTarget = _getPrototypeOf(this).constructor; + result = Reflect.construct(Super, arguments, NewTarget); + } else { + result = Super.apply(this, arguments); + } + return _possibleConstructorReturn(this, result); + }; + } + function _superPropBase(object, property) { + while (!Object.prototype.hasOwnProperty.call(object, property)) { + object = _getPrototypeOf(object); + if (object === null) break; + } + return object; + } + function _get() { + if (typeof Reflect !== "undefined" && Reflect.get) { + _get = Reflect.get.bind(); + } else { + _get = function _get(target, property, receiver) { + var base = _superPropBase(target, property); + if (!base) return; + var desc = Object.getOwnPropertyDescriptor(base, property); + if (desc.get) { + return desc.get.call(arguments.length < 3 ? target : receiver); + } + return desc.value; + }; + } + return _get.apply(this, arguments); + } + function _toPrimitive(input, hint) { + if (typeof input !== "object" || input === null) return input; + var prim = input[Symbol.toPrimitive]; + if (prim !== undefined) { + var res = prim.call(input, hint || "default"); + if (typeof res !== "object") return res; + throw new TypeError("@@toPrimitive must return a primitive value."); + } + return (hint === "string" ? String : Number)(input); + } + function _toPropertyKey(arg) { + var key = _toPrimitive(arg, "string"); + return typeof key === "symbol" ? key : String(key); + } + + var PACKET_TYPES = Object.create(null); // no Map = no polyfill + PACKET_TYPES["open"] = "0"; + PACKET_TYPES["close"] = "1"; + PACKET_TYPES["ping"] = "2"; + PACKET_TYPES["pong"] = "3"; + PACKET_TYPES["message"] = "4"; + PACKET_TYPES["upgrade"] = "5"; + PACKET_TYPES["noop"] = "6"; + var PACKET_TYPES_REVERSE = Object.create(null); + Object.keys(PACKET_TYPES).forEach(function (key) { + PACKET_TYPES_REVERSE[PACKET_TYPES[key]] = key; + }); + var ERROR_PACKET = { + type: "error", + data: "parser error" + }; + + var withNativeBlob = typeof Blob === "function" || typeof Blob !== "undefined" && Object.prototype.toString.call(Blob) === "[object BlobConstructor]"; + var withNativeArrayBuffer$1 = typeof ArrayBuffer === "function"; + // ArrayBuffer.isView method is not defined in IE10 + var isView = function isView(obj) { + return typeof ArrayBuffer.isView === "function" ? ArrayBuffer.isView(obj) : obj && obj.buffer instanceof ArrayBuffer; + }; + var encodePacket = function encodePacket(_ref, supportsBinary, callback) { + var type = _ref.type, + data = _ref.data; + if (withNativeBlob && data instanceof Blob) { + if (supportsBinary) { + return callback(data); + } else { + return encodeBlobAsBase64(data, callback); + } + } else if (withNativeArrayBuffer$1 && (data instanceof ArrayBuffer || isView(data))) { + if (supportsBinary) { + return callback(data); + } else { + return encodeBlobAsBase64(new Blob([data]), callback); + } + } + // plain string + return callback(PACKET_TYPES[type] + (data || "")); + }; + var encodeBlobAsBase64 = function encodeBlobAsBase64(data, callback) { + var fileReader = new FileReader(); + fileReader.onload = function () { + var content = fileReader.result.split(",")[1]; + callback("b" + content); + }; + return fileReader.readAsDataURL(data); + }; + + /* + * base64-arraybuffer 1.0.1 + * Copyright (c) 2022 Niklas von Hertzen + * Released under MIT License + */ + var chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; + // Use a lookup table to find the index. + var lookup = typeof Uint8Array === 'undefined' ? [] : new Uint8Array(256); + for (var i$1 = 0; i$1 < chars.length; i$1++) { + lookup[chars.charCodeAt(i$1)] = i$1; + } + var decode$1 = function decode(base64) { + var bufferLength = base64.length * 0.75, + len = base64.length, + i, + p = 0, + encoded1, + encoded2, + encoded3, + encoded4; + if (base64[base64.length - 1] === '=') { + bufferLength--; + if (base64[base64.length - 2] === '=') { + bufferLength--; + } + } + var arraybuffer = new ArrayBuffer(bufferLength), + bytes = new Uint8Array(arraybuffer); + for (i = 0; i < len; i += 4) { + encoded1 = lookup[base64.charCodeAt(i)]; + encoded2 = lookup[base64.charCodeAt(i + 1)]; + encoded3 = lookup[base64.charCodeAt(i + 2)]; + encoded4 = lookup[base64.charCodeAt(i + 3)]; + bytes[p++] = encoded1 << 2 | encoded2 >> 4; + bytes[p++] = (encoded2 & 15) << 4 | encoded3 >> 2; + bytes[p++] = (encoded3 & 3) << 6 | encoded4 & 63; + } + return arraybuffer; + }; + + var withNativeArrayBuffer = typeof ArrayBuffer === "function"; + var decodePacket = function decodePacket(encodedPacket, binaryType) { + if (typeof encodedPacket !== "string") { + return { + type: "message", + data: mapBinary(encodedPacket, binaryType) + }; + } + var type = encodedPacket.charAt(0); + if (type === "b") { + return { + type: "message", + data: decodeBase64Packet(encodedPacket.substring(1), binaryType) + }; + } + var packetType = PACKET_TYPES_REVERSE[type]; + if (!packetType) { + return ERROR_PACKET; + } + return encodedPacket.length > 1 ? { + type: PACKET_TYPES_REVERSE[type], + data: encodedPacket.substring(1) + } : { + type: PACKET_TYPES_REVERSE[type] + }; + }; + var decodeBase64Packet = function decodeBase64Packet(data, binaryType) { + if (withNativeArrayBuffer) { + var decoded = decode$1(data); + return mapBinary(decoded, binaryType); + } else { + return { + base64: true, + data: data + }; // fallback for old browsers + } + }; + + var mapBinary = function mapBinary(data, binaryType) { + switch (binaryType) { + case "blob": + return data instanceof ArrayBuffer ? new Blob([data]) : data; + case "arraybuffer": + default: + return data; + // assuming the data is already an ArrayBuffer + } + }; + + var SEPARATOR = String.fromCharCode(30); // see https://en.wikipedia.org/wiki/Delimiter#ASCII_delimited_text + var encodePayload = function encodePayload(packets, callback) { + // some packets may be added to the array while encoding, so the initial length must be saved + var length = packets.length; + var encodedPackets = new Array(length); + var count = 0; + packets.forEach(function (packet, i) { + // force base64 encoding for binary packets + encodePacket(packet, false, function (encodedPacket) { + encodedPackets[i] = encodedPacket; + if (++count === length) { + callback(encodedPackets.join(SEPARATOR)); + } + }); + }); + }; + var decodePayload = function decodePayload(encodedPayload, binaryType) { + var encodedPackets = encodedPayload.split(SEPARATOR); + var packets = []; + for (var i = 0; i < encodedPackets.length; i++) { + var decodedPacket = decodePacket(encodedPackets[i], binaryType); + packets.push(decodedPacket); + if (decodedPacket.type === "error") { + break; + } + } + return packets; + }; + var protocol = 4; + + /** + * 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; + } + } + + // Remove event specific arrays for event types that no + // one is subscribed for to avoid memory leak. + if (callbacks.length === 0) { + delete this._callbacks['$' + event]; + } + 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 = new Array(arguments.length - 1), + callbacks = this._callbacks['$' + event]; + for (var i = 1; i < arguments.length; i++) { + args[i - 1] = arguments[i]; + } + if (callbacks) { + callbacks = callbacks.slice(0); + for (var i = 0, len = callbacks.length; i < len; ++i) { + callbacks[i].apply(this, args); + } + } + return this; + }; + + // alias used for reserved events (protected method) + Emitter.prototype.emitReserved = Emitter.prototype.emit; + + /** + * 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; + }; + + var globalThisShim = function () { + if (typeof self !== "undefined") { + return self; + } else if (typeof window !== "undefined") { + return window; + } else { + return Function("return this")(); + } + }(); + + function pick(obj) { + for (var _len = arguments.length, attr = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { + attr[_key - 1] = arguments[_key]; + } + return attr.reduce(function (acc, k) { + if (obj.hasOwnProperty(k)) { + acc[k] = obj[k]; + } + return acc; + }, {}); + } + // Keep a reference to the real timeout functions so they can be used when overridden + var NATIVE_SET_TIMEOUT = setTimeout; + var NATIVE_CLEAR_TIMEOUT = clearTimeout; + function installTimerFunctions(obj, opts) { + if (opts.useNativeTimers) { + obj.setTimeoutFn = NATIVE_SET_TIMEOUT.bind(globalThisShim); + obj.clearTimeoutFn = NATIVE_CLEAR_TIMEOUT.bind(globalThisShim); + } else { + obj.setTimeoutFn = setTimeout.bind(globalThisShim); + obj.clearTimeoutFn = clearTimeout.bind(globalThisShim); + } + } + // base64 encoded buffers are about 33% bigger (https://en.wikipedia.org/wiki/Base64) + var BASE64_OVERHEAD = 1.33; + // we could also have used `new Blob([obj]).size`, but it isn't supported in IE9 + function byteLength(obj) { + if (typeof obj === "string") { + return utf8Length(obj); + } + // arraybuffer or blob + return Math.ceil((obj.byteLength || obj.size) * BASE64_OVERHEAD); + } + function utf8Length(str) { + var c = 0, + length = 0; + for (var i = 0, l = str.length; i < l; i++) { + c = str.charCodeAt(i); + if (c < 0x80) { + length += 1; + } else if (c < 0x800) { + length += 2; + } else if (c < 0xd800 || c >= 0xe000) { + length += 3; + } else { + i++; + length += 4; + } + } + return length; + } + + var TransportError = /*#__PURE__*/function (_Error) { + _inherits(TransportError, _Error); + var _super = _createSuper(TransportError); + function TransportError(reason, description, context) { + var _this; + _classCallCheck(this, TransportError); + _this = _super.call(this, reason); + _this.description = description; + _this.context = context; + _this.type = "TransportError"; + return _this; + } + return _createClass(TransportError); + }( /*#__PURE__*/_wrapNativeSuper(Error)); + var Transport = /*#__PURE__*/function (_Emitter) { + _inherits(Transport, _Emitter); + var _super2 = _createSuper(Transport); + /** + * Transport abstract constructor. + * + * @param {Object} options. + * @api private + */ + function Transport(opts) { + var _this2; + _classCallCheck(this, Transport); + _this2 = _super2.call(this); + _this2.writable = false; + installTimerFunctions(_assertThisInitialized(_this2), opts); + _this2.opts = opts; + _this2.query = opts.query; + _this2.readyState = ""; + _this2.socket = opts.socket; + return _this2; + } + /** + * Emits an error. + * + * @param {String} reason + * @param description + * @param context - the error context + * @return {Transport} for chaining + * @api protected + */ + _createClass(Transport, [{ + key: "onError", + value: function onError(reason, description, context) { + _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(this, "error", new TransportError(reason, description, context)); + return this; + } + /** + * Opens the transport. + * + * @api public + */ + }, { + key: "open", + value: function open() { + if ("closed" === this.readyState || "" === this.readyState) { + this.readyState = "opening"; + this.doOpen(); + } + return this; + } + /** + * Closes the transport. + * + * @api public + */ + }, { + key: "close", + value: function close() { + if ("opening" === this.readyState || "open" === this.readyState) { + this.doClose(); + this.onClose(); + } + return this; + } + /** + * Sends multiple packets. + * + * @param {Array} packets + * @api public + */ + }, { + key: "send", + value: function send(packets) { + if ("open" === this.readyState) { + this.write(packets); + } + } + /** + * Called upon open + * + * @api protected + */ + }, { + key: "onOpen", + value: function onOpen() { + this.readyState = "open"; + this.writable = true; + _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(this, "open"); + } + /** + * Called with data. + * + * @param {String} data + * @api protected + */ + }, { + key: "onData", + value: function onData(data) { + var packet = decodePacket(data, this.socket.binaryType); + this.onPacket(packet); + } + /** + * Called with a decoded packet. + * + * @api protected + */ + }, { + key: "onPacket", + value: function onPacket(packet) { + _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(this, "packet", packet); + } + /** + * Called upon close. + * + * @api protected + */ + }, { + key: "onClose", + value: function onClose(details) { + this.readyState = "closed"; + _get(_getPrototypeOf(Transport.prototype), "emitReserved", this).call(this, "close", details); + } + }]); + return Transport; + }(Emitter); + + // imported from https://github.com/unshiftio/yeast + + var alphabet = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_'.split(''), + length = 64, + map = {}; + var seed = 0, + i = 0, + prev; + /** + * Return a string representing the specified number. + * + * @param {Number} num The number to convert. + * @returns {String} The string representation of the number. + * @api public + */ + function encode$1(num) { + var encoded = ''; + do { + encoded = alphabet[num % length] + encoded; + num = Math.floor(num / length); + } while (num > 0); + return encoded; + } + /** + * Yeast: A tiny growing id generator. + * + * @returns {String} A unique id. + * @api public + */ + function yeast() { + var now = encode$1(+new Date()); + if (now !== prev) return seed = 0, prev = now; + return now + '.' + encode$1(seed++); + } + // + // Map each character to its index. + // + for (; i < length; i++) { + map[alphabet[i]] = i; + } + + // imported from https://github.com/galkn/querystring + /** + * Compiles a querystring + * Returns string representation of the object + * + * @param {Object} + * @api private + */ + function encode(obj) { + var str = ''; + for (var i in obj) { + if (obj.hasOwnProperty(i)) { + if (str.length) str += '&'; + str += encodeURIComponent(i) + '=' + encodeURIComponent(obj[i]); + } + } + return str; + } + /** + * Parses a simple querystring into an object + * + * @param {String} qs + * @api private + */ + function decode(qs) { + var qry = {}; + var pairs = qs.split('&'); + for (var i = 0, l = pairs.length; i < l; i++) { + var pair = pairs[i].split('='); + qry[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1]); + } + return qry; + } + + // imported from https://github.com/component/has-cors + var value = false; + try { + value = typeof XMLHttpRequest !== 'undefined' && 'withCredentials' in new XMLHttpRequest(); + } catch (err) { + // if XMLHttp support is disabled in IE then it will throw + // when trying to create + } + var hasCORS = value; + + // browser shim for xmlhttprequest module + function XHR(opts) { + var xdomain = opts.xdomain; + // XMLHttpRequest can be disabled on IE + try { + if ("undefined" !== typeof XMLHttpRequest && (!xdomain || hasCORS)) { + return new XMLHttpRequest(); + } + } catch (e) {} + if (!xdomain) { + try { + return new globalThisShim[["Active"].concat("Object").join("X")]("Microsoft.XMLHTTP"); + } catch (e) {} + } + } + + function empty() {} + var hasXHR2 = function () { + var xhr = new XHR({ + xdomain: false + }); + return null != xhr.responseType; + }(); + var Polling = /*#__PURE__*/function (_Transport) { + _inherits(Polling, _Transport); + var _super = _createSuper(Polling); + /** + * XHR Polling constructor. + * + * @param {Object} opts + * @api public + */ + function Polling(opts) { + var _this; + _classCallCheck(this, Polling); + _this = _super.call(this, opts); + _this.polling = false; + if (typeof location !== "undefined") { + var isSSL = "https:" === location.protocol; + var port = location.port; + // some user agents have empty `location.port` + if (!port) { + port = isSSL ? "443" : "80"; + } + _this.xd = typeof location !== "undefined" && opts.hostname !== location.hostname || port !== opts.port; + _this.xs = opts.secure !== isSSL; + } + /** + * XHR supports binary + */ + var forceBase64 = opts && opts.forceBase64; + _this.supportsBinary = hasXHR2 && !forceBase64; + return _this; + } + /** + * Transport name. + */ + _createClass(Polling, [{ + key: "name", + get: function get() { + return "polling"; + } + /** + * Opens the socket (triggers polling). We write a PING message to determine + * when the transport is open. + * + * @api private + */ + }, { + key: "doOpen", + value: function doOpen() { + this.poll(); + } + /** + * Pauses polling. + * + * @param {Function} callback upon buffers are flushed and transport is paused + * @api private + */ + }, { + key: "pause", + value: function pause(onPause) { + var _this2 = this; + this.readyState = "pausing"; + var pause = function pause() { + _this2.readyState = "paused"; + onPause(); + }; + if (this.polling || !this.writable) { + var total = 0; + if (this.polling) { + total++; + this.once("pollComplete", function () { + --total || pause(); + }); + } + if (!this.writable) { + total++; + this.once("drain", function () { + --total || pause(); + }); + } + } else { + pause(); + } + } + /** + * Starts polling cycle. + * + * @api public + */ + }, { + key: "poll", + value: function poll() { + this.polling = true; + this.doPoll(); + this.emitReserved("poll"); + } + /** + * Overloads onData to detect payloads. + * + * @api private + */ + }, { + key: "onData", + value: function onData(data) { + var _this3 = this; + var callback = function callback(packet) { + // if its the first message we consider the transport open + if ("opening" === _this3.readyState && packet.type === "open") { + _this3.onOpen(); + } + // if its a close packet, we close the ongoing requests + if ("close" === packet.type) { + _this3.onClose({ + description: "transport closed by the server" + }); + return false; + } + // otherwise bypass onData and handle the message + _this3.onPacket(packet); + }; + // decode payload + decodePayload(data, this.socket.binaryType).forEach(callback); + // if an event did not trigger closing + if ("closed" !== this.readyState) { + // if we got data we're not polling + this.polling = false; + this.emitReserved("pollComplete"); + if ("open" === this.readyState) { + this.poll(); + } + } + } + /** + * For polling, send a close packet. + * + * @api private + */ + }, { + key: "doClose", + value: function doClose() { + var _this4 = this; + var close = function close() { + _this4.write([{ + type: "close" + }]); + }; + if ("open" === this.readyState) { + close(); + } else { + // in case we're trying to close while + // handshaking is in progress (GH-164) + this.once("open", close); + } + } + /** + * Writes a packets payload. + * + * @param {Array} data packets + * @param {Function} drain callback + * @api private + */ + }, { + key: "write", + value: function write(packets) { + var _this5 = this; + this.writable = false; + encodePayload(packets, function (data) { + _this5.doWrite(data, function () { + _this5.writable = true; + _this5.emitReserved("drain"); + }); + }); + } + /** + * Generates uri for connection. + * + * @api private + */ + }, { + key: "uri", + value: function uri() { + var query = this.query || {}; + var schema = this.opts.secure ? "https" : "http"; + var port = ""; + // cache busting is forced + if (false !== this.opts.timestampRequests) { + query[this.opts.timestampParam] = yeast(); + } + if (!this.supportsBinary && !query.sid) { + query.b64 = 1; + } + // avoid port if default for schema + if (this.opts.port && ("https" === schema && Number(this.opts.port) !== 443 || "http" === schema && Number(this.opts.port) !== 80)) { + port = ":" + this.opts.port; + } + var encodedQuery = encode(query); + var ipv6 = this.opts.hostname.indexOf(":") !== -1; + return schema + "://" + (ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) + port + this.opts.path + (encodedQuery.length ? "?" + encodedQuery : ""); + } + /** + * Creates a request. + * + * @param {String} method + * @api private + */ + }, { + key: "request", + value: function request() { + var opts = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; + _extends(opts, { + xd: this.xd, + xs: this.xs + }, this.opts); + return new Request(this.uri(), opts); + } + /** + * Sends data. + * + * @param {String} data to send. + * @param {Function} called upon flush. + * @api private + */ + }, { + key: "doWrite", + value: function doWrite(data, fn) { + var _this6 = this; + var req = this.request({ + method: "POST", + data: data + }); + req.on("success", fn); + req.on("error", function (xhrStatus, context) { + _this6.onError("xhr post error", xhrStatus, context); + }); + } + /** + * Starts a poll cycle. + * + * @api private + */ + }, { + key: "doPoll", + value: function doPoll() { + var _this7 = this; + var req = this.request(); + req.on("data", this.onData.bind(this)); + req.on("error", function (xhrStatus, context) { + _this7.onError("xhr poll error", xhrStatus, context); + }); + this.pollXhr = req; + } + }]); + return Polling; + }(Transport); + var Request = /*#__PURE__*/function (_Emitter) { + _inherits(Request, _Emitter); + var _super2 = _createSuper(Request); + /** + * Request constructor + * + * @param {Object} options + * @api public + */ + function Request(uri, opts) { + var _this8; + _classCallCheck(this, Request); + _this8 = _super2.call(this); + installTimerFunctions(_assertThisInitialized(_this8), opts); + _this8.opts = opts; + _this8.method = opts.method || "GET"; + _this8.uri = uri; + _this8.async = false !== opts.async; + _this8.data = undefined !== opts.data ? opts.data : null; + _this8.create(); + return _this8; + } + /** + * Creates the XHR object and sends the request. + * + * @api private + */ + _createClass(Request, [{ + key: "create", + value: function create() { + var _this9 = this; + var opts = pick(this.opts, "agent", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "autoUnref"); + opts.xdomain = !!this.opts.xd; + opts.xscheme = !!this.opts.xs; + var xhr = this.xhr = new XHR(opts); + try { + xhr.open(this.method, this.uri, this.async); + try { + if (this.opts.extraHeaders) { + xhr.setDisableHeaderCheck && xhr.setDisableHeaderCheck(true); + for (var i in this.opts.extraHeaders) { + if (this.opts.extraHeaders.hasOwnProperty(i)) { + xhr.setRequestHeader(i, this.opts.extraHeaders[i]); + } + } + } + } catch (e) {} + if ("POST" === this.method) { + try { + xhr.setRequestHeader("Content-type", "text/plain;charset=UTF-8"); + } catch (e) {} + } + try { + xhr.setRequestHeader("Accept", "*/*"); + } catch (e) {} + // ie6 check + if ("withCredentials" in xhr) { + xhr.withCredentials = this.opts.withCredentials; + } + if (this.opts.requestTimeout) { + xhr.timeout = this.opts.requestTimeout; + } + xhr.onreadystatechange = function () { + if (4 !== xhr.readyState) return; + if (200 === xhr.status || 1223 === xhr.status) { + _this9.onLoad(); + } else { + // make sure the `error` event handler that's user-set + // does not throw in the same tick and gets caught here + _this9.setTimeoutFn(function () { + _this9.onError(typeof xhr.status === "number" ? xhr.status : 0); + }, 0); + } + }; + xhr.send(this.data); + } catch (e) { + // Need to defer since .create() is called directly from the constructor + // and thus the 'error' event can only be only bound *after* this exception + // occurs. Therefore, also, we cannot throw here at all. + this.setTimeoutFn(function () { + _this9.onError(e); + }, 0); + return; + } + if (typeof document !== "undefined") { + this.index = Request.requestsCount++; + Request.requests[this.index] = this; + } + } + /** + * Called upon error. + * + * @api private + */ + }, { + key: "onError", + value: function onError(err) { + this.emitReserved("error", err, this.xhr); + this.cleanup(true); + } + /** + * Cleans up house. + * + * @api private + */ + }, { + key: "cleanup", + value: function cleanup(fromError) { + if ("undefined" === typeof this.xhr || null === this.xhr) { + return; + } + this.xhr.onreadystatechange = empty; + if (fromError) { + try { + this.xhr.abort(); + } catch (e) {} + } + if (typeof document !== "undefined") { + delete Request.requests[this.index]; + } + this.xhr = null; + } + /** + * Called upon load. + * + * @api private + */ + }, { + key: "onLoad", + value: function onLoad() { + var data = this.xhr.responseText; + if (data !== null) { + this.emitReserved("data", data); + this.emitReserved("success"); + this.cleanup(); + } + } + /** + * Aborts the request. + * + * @api public + */ + }, { + key: "abort", + value: function abort() { + this.cleanup(); + } + }]); + return Request; + }(Emitter); + Request.requestsCount = 0; + Request.requests = {}; + /** + * Aborts pending requests when unloading the window. This is needed to prevent + * memory leaks (e.g. when using IE) and to ensure that no spurious error is + * emitted. + */ + if (typeof document !== "undefined") { + // @ts-ignore + if (typeof attachEvent === "function") { + // @ts-ignore + attachEvent("onunload", unloadHandler); + } else if (typeof addEventListener === "function") { + var terminationEvent = "onpagehide" in globalThisShim ? "pagehide" : "unload"; + addEventListener(terminationEvent, unloadHandler, false); + } + } + function unloadHandler() { + for (var i in Request.requests) { + if (Request.requests.hasOwnProperty(i)) { + Request.requests[i].abort(); + } + } + } + + var nextTick = function () { + var isPromiseAvailable = typeof Promise === "function" && typeof Promise.resolve === "function"; + if (isPromiseAvailable) { + return function (cb) { + return Promise.resolve().then(cb); + }; + } else { + return function (cb, setTimeoutFn) { + return setTimeoutFn(cb, 0); + }; + } + }(); + var WebSocket = globalThisShim.WebSocket || globalThisShim.MozWebSocket; + var usingBrowserWebSocket = true; + var defaultBinaryType = "arraybuffer"; + + // detect ReactNative environment + var isReactNative = typeof navigator !== "undefined" && typeof navigator.product === "string" && navigator.product.toLowerCase() === "reactnative"; + var WS = /*#__PURE__*/function (_Transport) { + _inherits(WS, _Transport); + var _super = _createSuper(WS); + /** + * WebSocket transport constructor. + * + * @api {Object} connection options + * @api public + */ + function WS(opts) { + var _this; + _classCallCheck(this, WS); + _this = _super.call(this, opts); + _this.supportsBinary = !opts.forceBase64; + return _this; + } + /** + * Transport name. + * + * @api public + */ + _createClass(WS, [{ + key: "name", + get: function get() { + return "websocket"; + } + /** + * Opens socket. + * + * @api private + */ + }, { + key: "doOpen", + value: function doOpen() { + if (!this.check()) { + // let probe timeout + return; + } + var uri = this.uri(); + var protocols = this.opts.protocols; + // React Native only supports the 'headers' option, and will print a warning if anything else is passed + var opts = isReactNative ? {} : pick(this.opts, "agent", "perMessageDeflate", "pfx", "key", "passphrase", "cert", "ca", "ciphers", "rejectUnauthorized", "localAddress", "protocolVersion", "origin", "maxPayload", "family", "checkServerIdentity"); + if (this.opts.extraHeaders) { + opts.headers = this.opts.extraHeaders; + } + try { + this.ws = usingBrowserWebSocket && !isReactNative ? protocols ? new WebSocket(uri, protocols) : new WebSocket(uri) : new WebSocket(uri, protocols, opts); + } catch (err) { + return this.emitReserved("error", err); + } + this.ws.binaryType = this.socket.binaryType || defaultBinaryType; + this.addEventListeners(); + } + /** + * Adds event listeners to the socket + * + * @api private + */ + }, { + key: "addEventListeners", + value: function addEventListeners() { + var _this2 = this; + this.ws.onopen = function () { + if (_this2.opts.autoUnref) { + _this2.ws._socket.unref(); + } + _this2.onOpen(); + }; + this.ws.onclose = function (closeEvent) { + return _this2.onClose({ + description: "websocket connection closed", + context: closeEvent + }); + }; + this.ws.onmessage = function (ev) { + return _this2.onData(ev.data); + }; + this.ws.onerror = function (e) { + return _this2.onError("websocket error", e); + }; + } + /** + * Writes data to socket. + * + * @param {Array} array of packets. + * @api private + */ + }, { + key: "write", + value: function write(packets) { + var _this3 = this; + this.writable = false; + // encodePacket efficient as it uses WS framing + // no need for encodePayload + var _loop = function _loop(i) { + var packet = packets[i]; + var lastPacket = i === packets.length - 1; + encodePacket(packet, _this3.supportsBinary, function (data) { + // always create a new object (GH-437) + var opts = {}; + // Sometimes the websocket has already been closed but the browser didn't + // have a chance of informing us about it yet, in that case send will + // throw an error + try { + if (usingBrowserWebSocket) { + // TypeError is thrown when passing the second argument on Safari + _this3.ws.send(data); + } + } catch (e) {} + if (lastPacket) { + // fake drain + // defer to next tick to allow Socket to clear writeBuffer + nextTick(function () { + _this3.writable = true; + _this3.emitReserved("drain"); + }, _this3.setTimeoutFn); + } + }); + }; + for (var i = 0; i < packets.length; i++) { + _loop(i); + } + } + /** + * Closes socket. + * + * @api private + */ + }, { + key: "doClose", + value: function doClose() { + if (typeof this.ws !== "undefined") { + this.ws.close(); + this.ws = null; + } + } + /** + * Generates uri for connection. + * + * @api private + */ + }, { + key: "uri", + value: function uri() { + var query = this.query || {}; + var schema = this.opts.secure ? "wss" : "ws"; + var port = ""; + // avoid port if default for schema + if (this.opts.port && ("wss" === schema && Number(this.opts.port) !== 443 || "ws" === schema && Number(this.opts.port) !== 80)) { + port = ":" + this.opts.port; + } + // append timestamp to URI + if (this.opts.timestampRequests) { + query[this.opts.timestampParam] = yeast(); + } + // communicate binary support capabilities + if (!this.supportsBinary) { + query.b64 = 1; + } + var encodedQuery = encode(query); + var ipv6 = this.opts.hostname.indexOf(":") !== -1; + return schema + "://" + (ipv6 ? "[" + this.opts.hostname + "]" : this.opts.hostname) + port + this.opts.path + (encodedQuery.length ? "?" + encodedQuery : ""); + } + /** + * Feature detection for WebSocket. + * + * @return {Boolean} whether this transport is available. + * @api public + */ + }, { + key: "check", + value: function check() { + return !!WebSocket; + } + }]); + return WS; + }(Transport); + + var transports = { + websocket: WS, + polling: Polling + }; + + // imported from https://github.com/galkn/parseuri + /** + * Parses an URI + * + * @author Steven Levithan (MIT license) + * @api private + */ + var re = /^(?:(?![^:@]+:[^:@\/]*@)(http|https|ws|wss):\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?((?:[a-f0-9]{0,4}:){2,7}[a-f0-9]{0,4}|[^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/; + var parts = ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor']; + function parse(str) { + var src = str, + b = str.indexOf('['), + e = str.indexOf(']'); + if (b != -1 && e != -1) { + str = str.substring(0, b) + str.substring(b, e).replace(/:/g, ';') + str.substring(e, str.length); + } + var m = re.exec(str || ''), + uri = {}, + i = 14; + while (i--) { + uri[parts[i]] = m[i] || ''; + } + if (b != -1 && e != -1) { + uri.source = src; + uri.host = uri.host.substring(1, uri.host.length - 1).replace(/;/g, ':'); + uri.authority = uri.authority.replace('[', '').replace(']', '').replace(/;/g, ':'); + uri.ipv6uri = true; + } + uri.pathNames = pathNames(uri, uri['path']); + uri.queryKey = queryKey(uri, uri['query']); + return uri; + } + function pathNames(obj, path) { + var regx = /\/{2,9}/g, + names = path.replace(regx, "/").split("/"); + if (path.slice(0, 1) == '/' || path.length === 0) { + names.splice(0, 1); + } + if (path.slice(-1) == '/') { + names.splice(names.length - 1, 1); + } + return names; + } + function queryKey(uri, query) { + var data = {}; + query.replace(/(?:^|&)([^&=]*)=?([^&]*)/g, function ($0, $1, $2) { + if ($1) { + data[$1] = $2; + } + }); + return data; + } + + var Socket = /*#__PURE__*/function (_Emitter) { + _inherits(Socket, _Emitter); + var _super = _createSuper(Socket); + /** + * Socket constructor. + * + * @param {String|Object} uri or options + * @param {Object} opts - options + * @api public + */ + function Socket(uri) { + var _this; + var opts = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {}; + _classCallCheck(this, Socket); + _this = _super.call(this); + if (uri && "object" === _typeof(uri)) { + opts = uri; + uri = null; + } + if (uri) { + uri = parse(uri); + opts.hostname = uri.host; + opts.secure = uri.protocol === "https" || uri.protocol === "wss"; + opts.port = uri.port; + if (uri.query) opts.query = uri.query; + } else if (opts.host) { + opts.hostname = parse(opts.host).host; + } + installTimerFunctions(_assertThisInitialized(_this), opts); + _this.secure = null != opts.secure ? opts.secure : typeof location !== "undefined" && "https:" === location.protocol; + if (opts.hostname && !opts.port) { + // if no port is specified manually, use the protocol default + opts.port = _this.secure ? "443" : "80"; + } + _this.hostname = opts.hostname || (typeof location !== "undefined" ? location.hostname : "localhost"); + _this.port = opts.port || (typeof location !== "undefined" && location.port ? location.port : _this.secure ? "443" : "80"); + _this.transports = opts.transports || ["polling", "websocket"]; + _this.readyState = ""; + _this.writeBuffer = []; + _this.prevBufferLen = 0; + _this.opts = _extends({ + path: "/engine.io", + agent: false, + withCredentials: false, + upgrade: true, + timestampParam: "t", + rememberUpgrade: false, + rejectUnauthorized: true, + perMessageDeflate: { + threshold: 1024 + }, + transportOptions: {}, + closeOnBeforeunload: true + }, opts); + _this.opts.path = _this.opts.path.replace(/\/$/, "") + "/"; + if (typeof _this.opts.query === "string") { + _this.opts.query = decode(_this.opts.query); + } + // set on handshake + _this.id = null; + _this.upgrades = null; + _this.pingInterval = null; + _this.pingTimeout = null; + // set on heartbeat + _this.pingTimeoutTimer = null; + if (typeof addEventListener === "function") { + if (_this.opts.closeOnBeforeunload) { + // Firefox closes the connection when the "beforeunload" event is emitted but not Chrome. This event listener + // ensures every browser behaves the same (no "disconnect" event at the Socket.IO level when the page is + // closed/reloaded) + _this.beforeunloadEventListener = function () { + if (_this.transport) { + // silently close the transport + _this.transport.removeAllListeners(); + _this.transport.close(); + } + }; + addEventListener("beforeunload", _this.beforeunloadEventListener, false); + } + if (_this.hostname !== "localhost") { + _this.offlineEventListener = function () { + _this.onClose("transport close", { + description: "network connection lost" + }); + }; + addEventListener("offline", _this.offlineEventListener, false); + } + } + _this.open(); + return _this; + } + /** + * Creates transport of the given type. + * + * @param {String} transport name + * @return {Transport} + * @api private + */ + _createClass(Socket, [{ + key: "createTransport", + value: function createTransport(name) { + var query = _extends({}, this.opts.query); + // append engine.io protocol identifier + query.EIO = protocol; + // transport name + query.transport = name; + // session id if we already have one + if (this.id) query.sid = this.id; + var opts = _extends({}, this.opts.transportOptions[name], this.opts, { + query: query, + socket: this, + hostname: this.hostname, + secure: this.secure, + port: this.port + }); + return new transports[name](opts); + } + /** + * Initializes transport to use and starts probe. + * + * @api private + */ + }, { + key: "open", + value: function open() { + var _this2 = this; + var transport; + if (this.opts.rememberUpgrade && Socket.priorWebsocketSuccess && this.transports.indexOf("websocket") !== -1) { + transport = "websocket"; + } else if (0 === this.transports.length) { + // Emit error on next tick so it can be listened to + this.setTimeoutFn(function () { + _this2.emitReserved("error", "No transports available"); + }, 0); + return; + } else { + transport = this.transports[0]; + } + this.readyState = "opening"; + // Retry with the next transport if the transport is disabled (jsonp: false) + try { + transport = this.createTransport(transport); + } catch (e) { + this.transports.shift(); + this.open(); + return; + } + transport.open(); + this.setTransport(transport); + } + /** + * Sets the current transport. Disables the existing one (if any). + * + * @api private + */ + }, { + key: "setTransport", + value: function setTransport(transport) { + var _this3 = this; + if (this.transport) { + this.transport.removeAllListeners(); + } + // set up transport + this.transport = transport; + // set up transport listeners + transport.on("drain", this.onDrain.bind(this)).on("packet", this.onPacket.bind(this)).on("error", this.onError.bind(this)).on("close", function (reason) { + return _this3.onClose("transport close", reason); + }); + } + /** + * Probes a transport. + * + * @param {String} transport name + * @api private + */ + }, { + key: "probe", + value: function probe(name) { + var _this4 = this; + var transport = this.createTransport(name); + var failed = false; + Socket.priorWebsocketSuccess = false; + var onTransportOpen = function onTransportOpen() { + if (failed) return; + transport.send([{ + type: "ping", + data: "probe" + }]); + transport.once("packet", function (msg) { + if (failed) return; + if ("pong" === msg.type && "probe" === msg.data) { + _this4.upgrading = true; + _this4.emitReserved("upgrading", transport); + if (!transport) return; + Socket.priorWebsocketSuccess = "websocket" === transport.name; + _this4.transport.pause(function () { + if (failed) return; + if ("closed" === _this4.readyState) return; + cleanup(); + _this4.setTransport(transport); + transport.send([{ + type: "upgrade" + }]); + _this4.emitReserved("upgrade", transport); + transport = null; + _this4.upgrading = false; + _this4.flush(); + }); + } else { + var err = new Error("probe error"); + // @ts-ignore + err.transport = transport.name; + _this4.emitReserved("upgradeError", err); + } + }); + }; + function freezeTransport() { + if (failed) return; + // Any callback called by transport should be ignored since now + failed = true; + cleanup(); + transport.close(); + transport = null; + } + // Handle any error that happens while probing + var onerror = function onerror(err) { + var error = new Error("probe error: " + err); + // @ts-ignore + error.transport = transport.name; + freezeTransport(); + _this4.emitReserved("upgradeError", error); + }; + function onTransportClose() { + onerror("transport closed"); + } + // When the socket is closed while we're probing + function onclose() { + onerror("socket closed"); + } + // When the socket is upgraded while we're probing + function onupgrade(to) { + if (transport && to.name !== transport.name) { + freezeTransport(); + } + } + // Remove all listeners on the transport and on self + var cleanup = function cleanup() { + transport.removeListener("open", onTransportOpen); + transport.removeListener("error", onerror); + transport.removeListener("close", onTransportClose); + _this4.off("close", onclose); + _this4.off("upgrading", onupgrade); + }; + transport.once("open", onTransportOpen); + transport.once("error", onerror); + transport.once("close", onTransportClose); + this.once("close", onclose); + this.once("upgrading", onupgrade); + transport.open(); + } + /** + * Called when connection is deemed open. + * + * @api private + */ + }, { + key: "onOpen", + value: function onOpen() { + this.readyState = "open"; + Socket.priorWebsocketSuccess = "websocket" === this.transport.name; + this.emitReserved("open"); + this.flush(); + // we check for `readyState` in case an `open` + // listener already closed the socket + if ("open" === this.readyState && this.opts.upgrade && this.transport.pause) { + var i = 0; + var l = this.upgrades.length; + for (; i < l; i++) { + this.probe(this.upgrades[i]); + } + } + } + /** + * Handles a packet. + * + * @api private + */ + }, { + key: "onPacket", + value: function onPacket(packet) { + if ("opening" === this.readyState || "open" === this.readyState || "closing" === this.readyState) { + this.emitReserved("packet", packet); + // Socket is live - any packet counts + this.emitReserved("heartbeat"); + switch (packet.type) { + case "open": + this.onHandshake(JSON.parse(packet.data)); + break; + case "ping": + this.resetPingTimeout(); + this.sendPacket("pong"); + this.emitReserved("ping"); + this.emitReserved("pong"); + break; + case "error": + var err = new Error("server error"); + // @ts-ignore + err.code = packet.data; + this.onError(err); + break; + case "message": + this.emitReserved("data", packet.data); + this.emitReserved("message", packet.data); + break; + } + } + } + /** + * Called upon handshake completion. + * + * @param {Object} data - handshake obj + * @api private + */ + }, { + key: "onHandshake", + value: function onHandshake(data) { + this.emitReserved("handshake", data); + this.id = data.sid; + this.transport.query.sid = data.sid; + this.upgrades = this.filterUpgrades(data.upgrades); + this.pingInterval = data.pingInterval; + this.pingTimeout = data.pingTimeout; + this.maxPayload = data.maxPayload; + this.onOpen(); + // In case open handler closes socket + if ("closed" === this.readyState) return; + this.resetPingTimeout(); + } + /** + * Sets and resets ping timeout timer based on server pings. + * + * @api private + */ + }, { + key: "resetPingTimeout", + value: function resetPingTimeout() { + var _this5 = this; + this.clearTimeoutFn(this.pingTimeoutTimer); + this.pingTimeoutTimer = this.setTimeoutFn(function () { + _this5.onClose("ping timeout"); + }, this.pingInterval + this.pingTimeout); + if (this.opts.autoUnref) { + this.pingTimeoutTimer.unref(); + } + } + /** + * Called on `drain` event + * + * @api private + */ + }, { + key: "onDrain", + value: function onDrain() { + this.writeBuffer.splice(0, this.prevBufferLen); + // setting prevBufferLen = 0 is very important + // for example, when upgrading, upgrade packet is sent over, + // and a nonzero prevBufferLen could cause problems on `drain` + this.prevBufferLen = 0; + if (0 === this.writeBuffer.length) { + this.emitReserved("drain"); + } else { + this.flush(); + } + } + /** + * Flush write buffers. + * + * @api private + */ + }, { + key: "flush", + value: function flush() { + if ("closed" !== this.readyState && this.transport.writable && !this.upgrading && this.writeBuffer.length) { + var packets = this.getWritablePackets(); + this.transport.send(packets); + // keep track of current length of writeBuffer + // splice writeBuffer and callbackBuffer on `drain` + this.prevBufferLen = packets.length; + this.emitReserved("flush"); + } + } + /** + * Ensure the encoded size of the writeBuffer is below the maxPayload value sent by the server (only for HTTP + * long-polling) + * + * @private + */ + }, { + key: "getWritablePackets", + value: function getWritablePackets() { + var shouldCheckPayloadSize = this.maxPayload && this.transport.name === "polling" && this.writeBuffer.length > 1; + if (!shouldCheckPayloadSize) { + return this.writeBuffer; + } + var payloadSize = 1; // first packet type + for (var i = 0; i < this.writeBuffer.length; i++) { + var data = this.writeBuffer[i].data; + if (data) { + payloadSize += byteLength(data); + } + if (i > 0 && payloadSize > this.maxPayload) { + return this.writeBuffer.slice(0, i); + } + payloadSize += 2; // separator + packet type + } + + return this.writeBuffer; + } + /** + * Sends a message. + * + * @param {String} message. + * @param {Function} callback function. + * @param {Object} options. + * @return {Socket} for chaining. + * @api public + */ + }, { + key: "write", + value: function write(msg, options, fn) { + this.sendPacket("message", msg, options, fn); + return this; + } + }, { + key: "send", + value: function send(msg, options, fn) { + this.sendPacket("message", msg, options, fn); + return this; + } + /** + * Sends a packet. + * + * @param {String} packet type. + * @param {String} data. + * @param {Object} options. + * @param {Function} callback function. + * @api private + */ + }, { + key: "sendPacket", + value: function sendPacket(type, data, options, fn) { + if ("function" === typeof data) { + fn = data; + data = undefined; + } + if ("function" === typeof options) { + fn = options; + options = null; + } + if ("closing" === this.readyState || "closed" === this.readyState) { + return; + } + options = options || {}; + options.compress = false !== options.compress; + var packet = { + type: type, + data: data, + options: options + }; + this.emitReserved("packetCreate", packet); + this.writeBuffer.push(packet); + if (fn) this.once("flush", fn); + this.flush(); + } + /** + * Closes the connection. + * + * @api public + */ + }, { + key: "close", + value: function close() { + var _this6 = this; + var close = function close() { + _this6.onClose("forced close"); + _this6.transport.close(); + }; + var cleanupAndClose = function cleanupAndClose() { + _this6.off("upgrade", cleanupAndClose); + _this6.off("upgradeError", cleanupAndClose); + close(); + }; + var waitForUpgrade = function waitForUpgrade() { + // wait for upgrade to finish since we can't send packets while pausing a transport + _this6.once("upgrade", cleanupAndClose); + _this6.once("upgradeError", cleanupAndClose); + }; + if ("opening" === this.readyState || "open" === this.readyState) { + this.readyState = "closing"; + if (this.writeBuffer.length) { + this.once("drain", function () { + if (_this6.upgrading) { + waitForUpgrade(); + } else { + close(); + } + }); + } else if (this.upgrading) { + waitForUpgrade(); + } else { + close(); + } + } + return this; + } + /** + * Called upon transport error + * + * @api private + */ + }, { + key: "onError", + value: function onError(err) { + Socket.priorWebsocketSuccess = false; + this.emitReserved("error", err); + this.onClose("transport error", err); + } + /** + * Called upon transport close. + * + * @api private + */ + }, { + key: "onClose", + value: function onClose(reason, description) { + if ("opening" === this.readyState || "open" === this.readyState || "closing" === this.readyState) { + // clear timers + this.clearTimeoutFn(this.pingTimeoutTimer); + // stop event from firing again for transport + this.transport.removeAllListeners("close"); + // ensure transport won't stay open + this.transport.close(); + // ignore further transport communication + this.transport.removeAllListeners(); + if (typeof removeEventListener === "function") { + removeEventListener("beforeunload", this.beforeunloadEventListener, false); + removeEventListener("offline", this.offlineEventListener, false); + } + // set ready state + this.readyState = "closed"; + // clear session id + this.id = null; + // emit close event + this.emitReserved("close", reason, description); + // clean buffers after, so users can still + // grab the buffers on `close` event + this.writeBuffer = []; + this.prevBufferLen = 0; + } + } + /** + * Filters upgrades, returning only those matching client transports. + * + * @param {Array} server upgrades + * @api private + * + */ + }, { + key: "filterUpgrades", + value: function filterUpgrades(upgrades) { + var filteredUpgrades = []; + var i = 0; + var j = upgrades.length; + for (; i < j; i++) { + if (~this.transports.indexOf(upgrades[i])) filteredUpgrades.push(upgrades[i]); + } + return filteredUpgrades; + } + }]); + return Socket; + }(Emitter); + Socket.protocol = protocol; + + var browserEntrypoint = (function (uri, opts) { + return new Socket(uri, opts); + }); + + return browserEntrypoint; + +})(); +return eio; +}); + +} +]); \ No newline at end of file diff --git a/lib/sugar.js b/lib/sugar.js index a0e0325..a9b4cad 100644 --- a/lib/sugar.js +++ b/lib/sugar.js @@ -1,10 +1,10 @@ -var SugarClient = require('sugar-client'); +var SugarClient = require('./SugarClient/client'); var JSONAPIClient = require('json-api-client'); var auth = require('./auth'); var config = require('./config'); if (typeof navigator !== 'undefined') { - SugarClient.Primus = require('sugar-client/primus'); + SugarClient.Primus = require('./SugarClient/primus'); SugarClient.host = config.sugarHost; var sugarApiClient = new JSONAPIClient(config.sugarHost, { diff --git a/package-lock.json b/package-lock.json index 10c0a11..08f10f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,7 @@ "license": "Apache-2.0", "dependencies": { "json-api-client": "^7.0.1", - "local-storage": "^2.0.0", - "sugar-client": "^2.0.0" + "local-storage": "^2.0.0" }, "devDependencies": { "blue-tape": "~1.0.0", @@ -798,14 +797,6 @@ "node": ">=0.10.0" } }, - "node_modules/sugar-client": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sugar-client/-/sugar-client-2.0.0.tgz", - "integrity": "sha512-87RfgwzyiQrA77lsBGYFPjHbDVzkcSl5aiubz3n7qPOwf9LLNKe+PvtlNf0bnFS63+gGWe6r8A18iaKLBthw1w==", - "engines": { - "node": ">=14" - } - }, "node_modules/superagent": { "version": "8.0.8", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.8.tgz", @@ -1597,11 +1588,6 @@ "ansi-regex": "^2.0.0" } }, - "sugar-client": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/sugar-client/-/sugar-client-2.0.0.tgz", - "integrity": "sha512-87RfgwzyiQrA77lsBGYFPjHbDVzkcSl5aiubz3n7qPOwf9LLNKe+PvtlNf0bnFS63+gGWe6r8A18iaKLBthw1w==" - }, "superagent": { "version": "8.0.8", "resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.8.tgz", diff --git a/package.json b/package.json index 2e576bb..b4ec2fe 100644 --- a/package.json +++ b/package.json @@ -8,8 +8,7 @@ "repository": "zooniverse/panoptes-javascript-client", "dependencies": { "json-api-client": "^7.0.1", - "local-storage": "^2.0.0", - "sugar-client": "^2.0.0" + "local-storage": "^2.0.0" }, "devDependencies": { "blue-tape": "~1.0.0",