Alexander Petros d1288d202a
Remove old tests from the website (#1733)
The website used to host every past test suite, copied into the www
directory. We no longer need that on the website (and it makes the
codebase impossible to search) so I removed all the old tests and the
new tests are hosted simply at /test.

I also replaced the www.js script with a simpler www.sh one (since we no
longer need to do anything besides copying, really), which allowed me to
remove a node dependency that was only used in that script.
2023-09-19 11:07:24 -05:00

2217 lines
62 KiB
JavaScript

(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(factory((global.Mock = global.Mock || {})));
}(this, (function (exports) { 'use strict';
var commonjsGlobal = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {};
/**
* 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
*/
var requiresPort = 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;
};
var has = Object.prototype.hasOwnProperty;
var 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.
//
var stringify = querystringify;
var parse = querystring;
var querystringify_1 = {
stringify: stringify,
parse: parse
};
var CRHTLF = /[\n\r\t]/g;
var slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//;
var protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i;
var windowsDriveLetter = /^[a-zA-Z]:/;
var whitespace = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/;
/**
* Trim a given string.
*
* @param {String} str String to trim.
* @public
*/
function trimLeft(str) {
return (str ? str : '').toString().replace(whitespace, '');
}
/**
* 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 commonjsGlobal !== 'undefined') { globalVar = commonjsGlobal; }
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 = querystringify_1.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 (!requiresPort(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 || querystringify_1.parse)(value);
}
url[part] = value;
break;
case 'port':
url[part] = value;
if (!requiresPort(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 (/:\d+$/.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 = querystringify_1.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] === ':') { 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 = querystringify_1;
var urlParse = Url;
/*
* This delay allows the thread to finish assigning its on* methods
* before invoking the delay callback. This is purely a timing hack.
* http://geekabyte.blogspot.com/2014/01/javascript-effect-of-setting-settimeout.html
*
* @param {callback: function} the callback which will be invoked after the timeout
* @parma {context: object} the context in which to invoke the function
*/
function delay(callback, context) {
setTimeout(function (timeoutContext) { return callback.call(timeoutContext); }, 4, context);
}
function log(method, message) {
/* eslint-disable no-console */
if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {
console[method].call(null, message);
}
/* eslint-enable no-console */
}
function reject(array, callback) {
if ( array === void 0 ) array = [];
var results = [];
array.forEach(function (itemInArray) {
if (!callback(itemInArray)) {
results.push(itemInArray);
}
});
return results;
}
function filter(array, callback) {
if ( array === void 0 ) array = [];
var results = [];
array.forEach(function (itemInArray) {
if (callback(itemInArray)) {
results.push(itemInArray);
}
});
return results;
}
/*
* EventTarget is an interface implemented by objects that can
* receive events and may have listeners for them.
*
* https://developer.mozilla.org/en-US/docs/Web/API/EventTarget
*/
var EventTarget = function EventTarget() {
this.listeners = {};
};
/*
* Ties a listener function to an event type which can later be invoked via the
* dispatchEvent method.
*
* @param {string} type - the type of event (ie: 'open', 'message', etc.)
* @param {function} listener - callback function to invoke when an event is dispatched matching the type
* @param {boolean} useCapture - N/A TODO: implement useCapture functionality
*/
EventTarget.prototype.addEventListener = function addEventListener (type, listener /* , useCapture */) {
if (typeof listener === 'function') {
if (!Array.isArray(this.listeners[type])) {
this.listeners[type] = [];
}
// Only add the same function once
if (filter(this.listeners[type], function (item) { return item === listener; }).length === 0) {
this.listeners[type].push(listener);
}
}
};
/*
* Removes the listener so it will no longer be invoked via the dispatchEvent method.
*
* @param {string} type - the type of event (ie: 'open', 'message', etc.)
* @param {function} listener - callback function to invoke when an event is dispatched matching the type
* @param {boolean} useCapture - N/A TODO: implement useCapture functionality
*/
EventTarget.prototype.removeEventListener = function removeEventListener (type, removingListener /* , useCapture */) {
var arrayOfListeners = this.listeners[type];
this.listeners[type] = reject(arrayOfListeners, function (listener) { return listener === removingListener; });
};
/*
* Invokes all listener functions that are listening to the given event.type property. Each
* listener will be passed the event as the first argument.
*
* @param {object} event - event object which will be passed to all listeners of the event.type property
*/
EventTarget.prototype.dispatchEvent = function dispatchEvent (event) {
var this$1 = this;
var customArguments = [], len = arguments.length - 1;
while ( len-- > 0 ) customArguments[ len ] = arguments[ len + 1 ];
var eventName = event.type;
var listeners = this.listeners[eventName];
if (!Array.isArray(listeners)) {
return false;
}
listeners.forEach(function (listener) {
if (customArguments.length > 0) {
listener.apply(this$1, customArguments);
} else {
listener.call(this$1, event);
}
});
return true;
};
function trimQueryPartFromURL(url) {
var queryIndex = url.indexOf('?');
return queryIndex >= 0 ? url.slice(0, queryIndex) : url;
}
/*
* The network bridge is a way for the mock websocket object to 'communicate' with
* all available servers. This is a singleton object so it is important that you
* clean up urlMap whenever you are finished.
*/
var NetworkBridge = function NetworkBridge() {
this.urlMap = {};
};
/*
* Attaches a websocket object to the urlMap hash so that it can find the server
* it is connected to and the server in turn can find it.
*
* @param {object} websocket - websocket object to add to the urlMap hash
* @param {string} url
*/
NetworkBridge.prototype.attachWebSocket = function attachWebSocket (websocket, url) {
var serverURL = trimQueryPartFromURL(url);
var connectionLookup = this.urlMap[serverURL];
if (connectionLookup && connectionLookup.server && connectionLookup.websockets.indexOf(websocket) === -1) {
connectionLookup.websockets.push(websocket);
return connectionLookup.server;
}
};
/*
* Attaches a websocket to a room
*/
NetworkBridge.prototype.addMembershipToRoom = function addMembershipToRoom (websocket, room) {
var connectionLookup = this.urlMap[trimQueryPartFromURL(websocket.url)];
if (connectionLookup && connectionLookup.server && connectionLookup.websockets.indexOf(websocket) !== -1) {
if (!connectionLookup.roomMemberships[room]) {
connectionLookup.roomMemberships[room] = [];
}
connectionLookup.roomMemberships[room].push(websocket);
}
};
/*
* Attaches a server object to the urlMap hash so that it can find a websockets
* which are connected to it and so that websockets can in turn can find it.
*
* @param {object} server - server object to add to the urlMap hash
* @param {string} url
*/
NetworkBridge.prototype.attachServer = function attachServer (server, url) {
var serverUrl = trimQueryPartFromURL(url);
var connectionLookup = this.urlMap[serverUrl];
if (!connectionLookup) {
this.urlMap[serverUrl] = {
server: server,
websockets: [],
roomMemberships: {}
};
return server;
}
};
/*
* Finds the server which is 'running' on the given url.
*
* @param {string} url - the url to use to find which server is running on it
*/
NetworkBridge.prototype.serverLookup = function serverLookup (url) {
var serverURL = trimQueryPartFromURL(url);
var connectionLookup = this.urlMap[serverURL];
if (connectionLookup) {
return connectionLookup.server;
}
};
/*
* Finds all websockets which is 'listening' on the given url.
*
* @param {string} url - the url to use to find all websockets which are associated with it
* @param {string} room - if a room is provided, will only return sockets in this room
* @param {class} broadcaster - socket that is broadcasting and is to be excluded from the lookup
*/
NetworkBridge.prototype.websocketsLookup = function websocketsLookup (url, room, broadcaster) {
var serverURL = trimQueryPartFromURL(url);
var websockets;
var connectionLookup = this.urlMap[serverURL];
websockets = connectionLookup ? connectionLookup.websockets : [];
if (room) {
var members = connectionLookup.roomMemberships[room];
websockets = members || [];
}
return broadcaster ? websockets.filter(function (websocket) { return websocket !== broadcaster; }) : websockets;
};
/*
* Removes the entry associated with the url.
*
* @param {string} url
*/
NetworkBridge.prototype.removeServer = function removeServer (url) {
delete this.urlMap[trimQueryPartFromURL(url)];
};
/*
* Removes the individual websocket from the map of associated websockets.
*
* @param {object} websocket - websocket object to remove from the url map
* @param {string} url
*/
NetworkBridge.prototype.removeWebSocket = function removeWebSocket (websocket, url) {
var serverURL = trimQueryPartFromURL(url);
var connectionLookup = this.urlMap[serverURL];
if (connectionLookup) {
connectionLookup.websockets = reject(connectionLookup.websockets, function (socket) { return socket === websocket; });
}
};
/*
* Removes a websocket from a room
*/
NetworkBridge.prototype.removeMembershipFromRoom = function removeMembershipFromRoom (websocket, room) {
var connectionLookup = this.urlMap[trimQueryPartFromURL(websocket.url)];
var memberships = connectionLookup.roomMemberships[room];
if (connectionLookup && memberships !== null) {
connectionLookup.roomMemberships[room] = reject(memberships, function (socket) { return socket === websocket; });
}
};
var networkBridge = new NetworkBridge(); // Note: this is a singleton
/*
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
*/
var CLOSE_CODES = {
CLOSE_NORMAL: 1000,
CLOSE_GOING_AWAY: 1001,
CLOSE_PROTOCOL_ERROR: 1002,
CLOSE_UNSUPPORTED: 1003,
CLOSE_NO_STATUS: 1005,
CLOSE_ABNORMAL: 1006,
UNSUPPORTED_DATA: 1007,
POLICY_VIOLATION: 1008,
CLOSE_TOO_LARGE: 1009,
MISSING_EXTENSION: 1010,
INTERNAL_ERROR: 1011,
SERVICE_RESTART: 1012,
TRY_AGAIN_LATER: 1013,
TLS_HANDSHAKE: 1015
};
var ERROR_PREFIX = {
CONSTRUCTOR_ERROR: "Failed to construct 'WebSocket':",
CLOSE_ERROR: "Failed to execute 'close' on 'WebSocket':",
EVENT: {
CONSTRUCT: "Failed to construct 'Event':",
MESSAGE: "Failed to construct 'MessageEvent':",
CLOSE: "Failed to construct 'CloseEvent':"
}
};
var EventPrototype = function EventPrototype () {};
EventPrototype.prototype.stopPropagation = function stopPropagation () {};
EventPrototype.prototype.stopImmediatePropagation = function stopImmediatePropagation () {};
// if no arguments are passed then the type is set to "undefined" on
// chrome and safari.
EventPrototype.prototype.initEvent = function initEvent (type, bubbles, cancelable) {
if ( type === void 0 ) type = 'undefined';
if ( bubbles === void 0 ) bubbles = false;
if ( cancelable === void 0 ) cancelable = false;
this.type = "" + type;
this.bubbles = Boolean(bubbles);
this.cancelable = Boolean(cancelable);
};
var Event = (function (EventPrototype$$1) {
function Event(type, eventInitConfig) {
if ( eventInitConfig === void 0 ) eventInitConfig = {};
EventPrototype$$1.call(this);
if (!type) {
throw new TypeError(((ERROR_PREFIX.EVENT_ERROR) + " 1 argument required, but only 0 present."));
}
if (typeof eventInitConfig !== 'object') {
throw new TypeError(((ERROR_PREFIX.EVENT_ERROR) + " parameter 2 ('eventInitDict') is not an object."));
}
var bubbles = eventInitConfig.bubbles;
var cancelable = eventInitConfig.cancelable;
this.type = "" + type;
this.timeStamp = Date.now();
this.target = null;
this.srcElement = null;
this.returnValue = true;
this.isTrusted = false;
this.eventPhase = 0;
this.defaultPrevented = false;
this.currentTarget = null;
this.cancelable = cancelable ? Boolean(cancelable) : false;
this.cancelBubble = false;
this.bubbles = bubbles ? Boolean(bubbles) : false;
}
if ( EventPrototype$$1 ) Event.__proto__ = EventPrototype$$1;
Event.prototype = Object.create( EventPrototype$$1 && EventPrototype$$1.prototype );
Event.prototype.constructor = Event;
return Event;
}(EventPrototype));
var MessageEvent = (function (EventPrototype$$1) {
function MessageEvent(type, eventInitConfig) {
if ( eventInitConfig === void 0 ) eventInitConfig = {};
EventPrototype$$1.call(this);
if (!type) {
throw new TypeError(((ERROR_PREFIX.EVENT.MESSAGE) + " 1 argument required, but only 0 present."));
}
if (typeof eventInitConfig !== 'object') {
throw new TypeError(((ERROR_PREFIX.EVENT.MESSAGE) + " parameter 2 ('eventInitDict') is not an object"));
}
var bubbles = eventInitConfig.bubbles;
var cancelable = eventInitConfig.cancelable;
var data = eventInitConfig.data;
var origin = eventInitConfig.origin;
var lastEventId = eventInitConfig.lastEventId;
var ports = eventInitConfig.ports;
this.type = "" + type;
this.timeStamp = Date.now();
this.target = null;
this.srcElement = null;
this.returnValue = true;
this.isTrusted = false;
this.eventPhase = 0;
this.defaultPrevented = false;
this.currentTarget = null;
this.cancelable = cancelable ? Boolean(cancelable) : false;
this.canncelBubble = false;
this.bubbles = bubbles ? Boolean(bubbles) : false;
this.origin = "" + origin;
this.ports = typeof ports === 'undefined' ? null : ports;
this.data = typeof data === 'undefined' ? null : data;
this.lastEventId = "" + (lastEventId || '');
}
if ( EventPrototype$$1 ) MessageEvent.__proto__ = EventPrototype$$1;
MessageEvent.prototype = Object.create( EventPrototype$$1 && EventPrototype$$1.prototype );
MessageEvent.prototype.constructor = MessageEvent;
return MessageEvent;
}(EventPrototype));
var CloseEvent = (function (EventPrototype$$1) {
function CloseEvent(type, eventInitConfig) {
if ( eventInitConfig === void 0 ) eventInitConfig = {};
EventPrototype$$1.call(this);
if (!type) {
throw new TypeError(((ERROR_PREFIX.EVENT.CLOSE) + " 1 argument required, but only 0 present."));
}
if (typeof eventInitConfig !== 'object') {
throw new TypeError(((ERROR_PREFIX.EVENT.CLOSE) + " parameter 2 ('eventInitDict') is not an object"));
}
var bubbles = eventInitConfig.bubbles;
var cancelable = eventInitConfig.cancelable;
var code = eventInitConfig.code;
var reason = eventInitConfig.reason;
var wasClean = eventInitConfig.wasClean;
this.type = "" + type;
this.timeStamp = Date.now();
this.target = null;
this.srcElement = null;
this.returnValue = true;
this.isTrusted = false;
this.eventPhase = 0;
this.defaultPrevented = false;
this.currentTarget = null;
this.cancelable = cancelable ? Boolean(cancelable) : false;
this.cancelBubble = false;
this.bubbles = bubbles ? Boolean(bubbles) : false;
this.code = typeof code === 'number' ? parseInt(code, 10) : 0;
this.reason = "" + (reason || '');
this.wasClean = wasClean ? Boolean(wasClean) : false;
}
if ( EventPrototype$$1 ) CloseEvent.__proto__ = EventPrototype$$1;
CloseEvent.prototype = Object.create( EventPrototype$$1 && EventPrototype$$1.prototype );
CloseEvent.prototype.constructor = CloseEvent;
return CloseEvent;
}(EventPrototype));
/*
* Creates an Event object and extends it to allow full modification of
* its properties.
*
* @param {object} config - within config you will need to pass type and optionally target
*/
function createEvent(config) {
var type = config.type;
var target = config.target;
var eventObject = new Event(type);
if (target) {
eventObject.target = target;
eventObject.srcElement = target;
eventObject.currentTarget = target;
}
return eventObject;
}
/*
* Creates a MessageEvent object and extends it to allow full modification of
* its properties.
*
* @param {object} config - within config: type, origin, data and optionally target
*/
function createMessageEvent(config) {
var type = config.type;
var origin = config.origin;
var data = config.data;
var target = config.target;
var messageEvent = new MessageEvent(type, {
data: data,
origin: origin
});
if (target) {
messageEvent.target = target;
messageEvent.srcElement = target;
messageEvent.currentTarget = target;
}
return messageEvent;
}
/*
* Creates a CloseEvent object and extends it to allow full modification of
* its properties.
*
* @param {object} config - within config: type and optionally target, code, and reason
*/
function createCloseEvent(config) {
var code = config.code;
var reason = config.reason;
var type = config.type;
var target = config.target;
var wasClean = config.wasClean;
if (!wasClean) {
wasClean = code === CLOSE_CODES.CLOSE_NORMAL || code === CLOSE_CODES.CLOSE_NO_STATUS;
}
var closeEvent = new CloseEvent(type, {
code: code,
reason: reason,
wasClean: wasClean
});
if (target) {
closeEvent.target = target;
closeEvent.srcElement = target;
closeEvent.currentTarget = target;
}
return closeEvent;
}
function closeWebSocketConnection(context, code, reason) {
context.readyState = WebSocket$1.CLOSING;
var server = networkBridge.serverLookup(context.url);
var closeEvent = createCloseEvent({
type: 'close',
target: context.target,
code: code,
reason: reason
});
delay(function () {
networkBridge.removeWebSocket(context, context.url);
context.readyState = WebSocket$1.CLOSED;
context.dispatchEvent(closeEvent);
if (server) {
server.dispatchEvent(closeEvent, server);
}
}, context);
}
function failWebSocketConnection(context, code, reason) {
context.readyState = WebSocket$1.CLOSING;
var server = networkBridge.serverLookup(context.url);
var closeEvent = createCloseEvent({
type: 'close',
target: context.target,
code: code,
reason: reason,
wasClean: false
});
var errorEvent = createEvent({
type: 'error',
target: context.target
});
delay(function () {
networkBridge.removeWebSocket(context, context.url);
context.readyState = WebSocket$1.CLOSED;
context.dispatchEvent(errorEvent);
context.dispatchEvent(closeEvent);
if (server) {
server.dispatchEvent(closeEvent, server);
}
}, context);
}
function normalizeSendData(data) {
if (Object.prototype.toString.call(data) !== '[object Blob]' && !(data instanceof ArrayBuffer)) {
data = String(data);
}
return data;
}
var proxies = new WeakMap();
function proxyFactory(target) {
if (proxies.has(target)) {
return proxies.get(target);
}
var proxy = new Proxy(target, {
get: function get(obj, prop) {
if (prop === 'close') {
return function close(options) {
if ( options === void 0 ) options = {};
var code = options.code || CLOSE_CODES.CLOSE_NORMAL;
var reason = options.reason || '';
closeWebSocketConnection(proxy, code, reason);
};
}
if (prop === 'send') {
return function send(data) {
data = normalizeSendData(data);
target.dispatchEvent(
createMessageEvent({
type: 'message',
data: data,
origin: this.url,
target: target
})
);
};
}
var toSocketName = function (type) { return (type === 'message' ? ("server::" + type) : type); };
if (prop === 'on') {
return function onWrapper(type, cb) {
target.addEventListener(toSocketName(type), cb);
};
}
if (prop === 'off') {
return function offWrapper(type, cb) {
target.removeEventListener(toSocketName(type), cb);
};
}
if (prop === 'target') {
return target;
}
return obj[prop];
}
});
proxies.set(target, proxy);
return proxy;
}
function lengthInUtf8Bytes(str) {
// Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
var m = encodeURIComponent(str).match(/%[89ABab]/g);
return str.length + (m ? m.length : 0);
}
function urlVerification(url) {
var urlRecord = new urlParse(url);
var pathname = urlRecord.pathname;
var protocol = urlRecord.protocol;
var hash = urlRecord.hash;
if (!url) {
throw new TypeError(((ERROR_PREFIX.CONSTRUCTOR_ERROR) + " 1 argument required, but only 0 present."));
}
if (!pathname) {
urlRecord.pathname = '/';
}
if (protocol === '') {
throw new SyntaxError(((ERROR_PREFIX.CONSTRUCTOR_ERROR) + " The URL '" + (urlRecord.toString()) + "' is invalid."));
}
if (protocol !== 'ws:' && protocol !== 'wss:') {
throw new SyntaxError(
((ERROR_PREFIX.CONSTRUCTOR_ERROR) + " The URL's scheme must be either 'ws' or 'wss'. '" + protocol + "' is not allowed.")
);
}
if (hash !== '') {
/* eslint-disable max-len */
throw new SyntaxError(
((ERROR_PREFIX.CONSTRUCTOR_ERROR) + " The URL contains a fragment identifier ('" + hash + "'). Fragment identifiers are not allowed in WebSocket URLs.")
);
/* eslint-enable max-len */
}
return urlRecord.toString();
}
function protocolVerification(protocols) {
if ( protocols === void 0 ) protocols = [];
if (!Array.isArray(protocols) && typeof protocols !== 'string') {
throw new SyntaxError(((ERROR_PREFIX.CONSTRUCTOR_ERROR) + " The subprotocol '" + (protocols.toString()) + "' is invalid."));
}
if (typeof protocols === 'string') {
protocols = [protocols];
}
var uniq = protocols
.map(function (p) { return ({ count: 1, protocol: p }); })
.reduce(function (a, b) {
a[b.protocol] = (a[b.protocol] || 0) + b.count;
return a;
}, {});
var duplicates = Object.keys(uniq).filter(function (a) { return uniq[a] > 1; });
if (duplicates.length > 0) {
throw new SyntaxError(((ERROR_PREFIX.CONSTRUCTOR_ERROR) + " The subprotocol '" + (duplicates[0]) + "' is duplicated."));
}
return protocols;
}
/*
* The main websocket class which is designed to mimick the native WebSocket class as close
* as possible.
*
* https://html.spec.whatwg.org/multipage/web-sockets.html
*/
var WebSocket$1 = (function (EventTarget$$1) {
function WebSocket(url, protocols) {
EventTarget$$1.call(this);
this._onopen = null;
this._onmessage = null;
this._onerror = null;
this._onclose = null;
this.url = urlVerification(url);
protocols = protocolVerification(protocols);
this.protocol = protocols[0] || '';
this.binaryType = 'blob';
this.readyState = WebSocket.CONNECTING;
var client = proxyFactory(this);
var server = networkBridge.attachWebSocket(client, this.url);
/*
* This delay is needed so that we dont trigger an event before the callbacks have been
* setup. For example:
*
* var socket = new WebSocket('ws://localhost');
*
* If we dont have the delay then the event would be triggered right here and this is
* before the onopen had a chance to register itself.
*
* socket.onopen = () => { // this would never be called };
*
* and with the delay the event gets triggered here after all of the callbacks have been
* registered :-)
*/
delay(function delayCallback() {
if (server) {
if (
server.options.verifyClient &&
typeof server.options.verifyClient === 'function' &&
!server.options.verifyClient()
) {
this.readyState = WebSocket.CLOSED;
log(
'error',
("WebSocket connection to '" + (this.url) + "' failed: HTTP Authentication failed; no valid credentials available")
);
networkBridge.removeWebSocket(client, this.url);
this.dispatchEvent(createEvent({ type: 'error', target: this }));
this.dispatchEvent(createCloseEvent({ type: 'close', target: this, code: CLOSE_CODES.CLOSE_NORMAL }));
} else {
if (server.options.selectProtocol && typeof server.options.selectProtocol === 'function') {
var selectedProtocol = server.options.selectProtocol(protocols);
var isFilled = selectedProtocol !== '';
var isRequested = protocols.indexOf(selectedProtocol) !== -1;
if (isFilled && !isRequested) {
this.readyState = WebSocket.CLOSED;
log('error', ("WebSocket connection to '" + (this.url) + "' failed: Invalid Sub-Protocol"));
networkBridge.removeWebSocket(client, this.url);
this.dispatchEvent(createEvent({ type: 'error', target: this }));
this.dispatchEvent(createCloseEvent({ type: 'close', target: this, code: CLOSE_CODES.CLOSE_NORMAL }));
return;
}
this.protocol = selectedProtocol;
}
this.readyState = WebSocket.OPEN;
this.dispatchEvent(createEvent({ type: 'open', target: this }));
server.dispatchEvent(createEvent({ type: 'connection' }), client);
}
} else {
this.readyState = WebSocket.CLOSED;
this.dispatchEvent(createEvent({ type: 'error', target: this }));
this.dispatchEvent(createCloseEvent({ type: 'close', target: this, code: CLOSE_CODES.CLOSE_NORMAL }));
log('error', ("WebSocket connection to '" + (this.url) + "' failed"));
}
}, this);
}
if ( EventTarget$$1 ) WebSocket.__proto__ = EventTarget$$1;
WebSocket.prototype = Object.create( EventTarget$$1 && EventTarget$$1.prototype );
WebSocket.prototype.constructor = WebSocket;
var prototypeAccessors = { onopen: {},onmessage: {},onclose: {},onerror: {} };
prototypeAccessors.onopen.get = function () {
return this._onopen;
};
prototypeAccessors.onmessage.get = function () {
return this._onmessage;
};
prototypeAccessors.onclose.get = function () {
return this._onclose;
};
prototypeAccessors.onerror.get = function () {
return this._onerror;
};
prototypeAccessors.onopen.set = function (listener) {
this.removeEventListener('open', this._onopen);
this._onopen = listener;
this.addEventListener('open', listener);
};
prototypeAccessors.onmessage.set = function (listener) {
this.removeEventListener('message', this._onmessage);
this._onmessage = listener;
this.addEventListener('message', listener);
};
prototypeAccessors.onclose.set = function (listener) {
this.removeEventListener('close', this._onclose);
this._onclose = listener;
this.addEventListener('close', listener);
};
prototypeAccessors.onerror.set = function (listener) {
this.removeEventListener('error', this._onerror);
this._onerror = listener;
this.addEventListener('error', listener);
};
WebSocket.prototype.send = function send (data) {
var this$1 = this;
if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) {
throw new Error('WebSocket is already in CLOSING or CLOSED state');
}
// TODO: handle bufferedAmount
var messageEvent = createMessageEvent({
type: 'server::message',
origin: this.url,
data: normalizeSendData(data)
});
var server = networkBridge.serverLookup(this.url);
if (server) {
delay(function () {
this$1.dispatchEvent(messageEvent, data);
}, server);
}
};
WebSocket.prototype.close = function close (code, reason) {
if (code !== undefined) {
if (typeof code !== 'number' || (code !== 1000 && (code < 3000 || code > 4999))) {
throw new TypeError(
((ERROR_PREFIX.CLOSE_ERROR) + " The code must be either 1000, or between 3000 and 4999. " + code + " is neither.")
);
}
}
if (reason !== undefined) {
var length = lengthInUtf8Bytes(reason);
if (length > 123) {
throw new SyntaxError(((ERROR_PREFIX.CLOSE_ERROR) + " The message must not be greater than 123 bytes."));
}
}
if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) {
return;
}
var client = proxyFactory(this);
if (this.readyState === WebSocket.CONNECTING) {
failWebSocketConnection(client, code || CLOSE_CODES.CLOSE_ABNORMAL, reason);
} else {
closeWebSocketConnection(client, code || CLOSE_CODES.CLOSE_NO_STATUS, reason);
}
};
Object.defineProperties( WebSocket.prototype, prototypeAccessors );
return WebSocket;
}(EventTarget));
WebSocket$1.CONNECTING = 0;
WebSocket$1.prototype.CONNECTING = WebSocket$1.CONNECTING;
WebSocket$1.OPEN = 1;
WebSocket$1.prototype.OPEN = WebSocket$1.OPEN;
WebSocket$1.CLOSING = 2;
WebSocket$1.prototype.CLOSING = WebSocket$1.CLOSING;
WebSocket$1.CLOSED = 3;
WebSocket$1.prototype.CLOSED = WebSocket$1.CLOSED;
/*
* The socket-io class is designed to mimick the real API as closely as possible.
*
* http://socket.io/docs/
*/
var SocketIO$1 = (function (EventTarget$$1) {
function SocketIO(url, protocol) {
var this$1 = this;
if ( url === void 0 ) url = 'socket.io';
if ( protocol === void 0 ) protocol = '';
EventTarget$$1.call(this);
this.binaryType = 'blob';
var urlRecord = new urlParse(url);
if (!urlRecord.pathname) {
urlRecord.pathname = '/';
}
this.url = urlRecord.toString();
this.readyState = SocketIO.CONNECTING;
this.protocol = '';
this.target = this;
if (typeof protocol === 'string' || (typeof protocol === 'object' && protocol !== null)) {
this.protocol = protocol;
} else if (Array.isArray(protocol) && protocol.length > 0) {
this.protocol = protocol[0];
}
var server = networkBridge.attachWebSocket(this, this.url);
/*
* Delay triggering the connection events so they can be defined in time.
*/
delay(function delayCallback() {
if (server) {
this.readyState = SocketIO.OPEN;
server.dispatchEvent(createEvent({ type: 'connection' }), server, this);
server.dispatchEvent(createEvent({ type: 'connect' }), server, this); // alias
this.dispatchEvent(createEvent({ type: 'connect', target: this }));
} else {
this.readyState = SocketIO.CLOSED;
this.dispatchEvent(createEvent({ type: 'error', target: this }));
this.dispatchEvent(
createCloseEvent({
type: 'close',
target: this,
code: CLOSE_CODES.CLOSE_NORMAL
})
);
log('error', ("Socket.io connection to '" + (this.url) + "' failed"));
}
}, this);
/**
Add an aliased event listener for close / disconnect
*/
this.addEventListener('close', function (event) {
this$1.dispatchEvent(
createCloseEvent({
type: 'disconnect',
target: event.target,
code: event.code
})
);
});
}
if ( EventTarget$$1 ) SocketIO.__proto__ = EventTarget$$1;
SocketIO.prototype = Object.create( EventTarget$$1 && EventTarget$$1.prototype );
SocketIO.prototype.constructor = SocketIO;
var prototypeAccessors = { broadcast: {} };
/*
* Closes the SocketIO connection or connection attempt, if any.
* If the connection is already CLOSED, this method does nothing.
*/
SocketIO.prototype.close = function close () {
if (this.readyState !== SocketIO.OPEN) {
return undefined;
}
var server = networkBridge.serverLookup(this.url);
networkBridge.removeWebSocket(this, this.url);
this.readyState = SocketIO.CLOSED;
this.dispatchEvent(
createCloseEvent({
type: 'close',
target: this,
code: CLOSE_CODES.CLOSE_NORMAL
})
);
if (server) {
server.dispatchEvent(
createCloseEvent({
type: 'disconnect',
target: this,
code: CLOSE_CODES.CLOSE_NORMAL
}),
server
);
}
return this;
};
/*
* Alias for Socket#close
*
* https://github.com/socketio/socket.io-client/blob/master/lib/socket.js#L383
*/
SocketIO.prototype.disconnect = function disconnect () {
return this.close();
};
/*
* Submits an event to the server with a payload
*/
SocketIO.prototype.emit = function emit (event) {
var data = [], len = arguments.length - 1;
while ( len-- > 0 ) data[ len ] = arguments[ len + 1 ];
if (this.readyState !== SocketIO.OPEN) {
throw new Error('SocketIO is already in CLOSING or CLOSED state');
}
var messageEvent = createMessageEvent({
type: event,
origin: this.url,
data: data
});
var server = networkBridge.serverLookup(this.url);
if (server) {
server.dispatchEvent.apply(server, [ messageEvent ].concat( data ));
}
return this;
};
/*
* Submits a 'message' event to the server.
*
* Should behave exactly like WebSocket#send
*
* https://github.com/socketio/socket.io-client/blob/master/lib/socket.js#L113
*/
SocketIO.prototype.send = function send (data) {
this.emit('message', data);
return this;
};
/*
* For broadcasting events to other connected sockets.
*
* e.g. socket.broadcast.emit('hi!');
* e.g. socket.broadcast.to('my-room').emit('hi!');
*/
prototypeAccessors.broadcast.get = function () {
if (this.readyState !== SocketIO.OPEN) {
throw new Error('SocketIO is already in CLOSING or CLOSED state');
}
var self = this;
var server = networkBridge.serverLookup(this.url);
if (!server) {
throw new Error(("SocketIO can not find a server at the specified URL (" + (this.url) + ")"));
}
return {
emit: function emit(event, data) {
server.emit(event, data, { websockets: networkBridge.websocketsLookup(self.url, null, self) });
return self;
},
to: function to(room) {
return server.to(room, self);
},
in: function in$1(room) {
return server.in(room, self);
}
};
};
/*
* For registering events to be received from the server
*/
SocketIO.prototype.on = function on (type, callback) {
this.addEventListener(type, callback);
return this;
};
/*
* Remove event listener
*
* https://github.com/component/emitter#emitteroffevent-fn
*/
SocketIO.prototype.off = function off (type, callback) {
this.removeEventListener(type, callback);
};
/*
* Check if listeners have already been added for an event
*
* https://github.com/component/emitter#emitterhaslistenersevent
*/
SocketIO.prototype.hasListeners = function hasListeners (type) {
var listeners = this.listeners[type];
if (!Array.isArray(listeners)) {
return false;
}
return !!listeners.length;
};
/*
* Join a room on a server
*
* http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving
*/
SocketIO.prototype.join = function join (room) {
networkBridge.addMembershipToRoom(this, room);
};
/*
* Get the websocket to leave the room
*
* http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving
*/
SocketIO.prototype.leave = function leave (room) {
networkBridge.removeMembershipFromRoom(this, room);
};
SocketIO.prototype.to = function to (room) {
return this.broadcast.to(room);
};
SocketIO.prototype.in = function in$1 () {
return this.to.apply(null, arguments);
};
/*
* Invokes all listener functions that are listening to the given event.type property. Each
* listener will be passed the event as the first argument.
*
* @param {object} event - event object which will be passed to all listeners of the event.type property
*/
SocketIO.prototype.dispatchEvent = function dispatchEvent (event) {
var this$1 = this;
var customArguments = [], len = arguments.length - 1;
while ( len-- > 0 ) customArguments[ len ] = arguments[ len + 1 ];
var eventName = event.type;
var listeners = this.listeners[eventName];
if (!Array.isArray(listeners)) {
return false;
}
listeners.forEach(function (listener) {
if (customArguments.length > 0) {
listener.apply(this$1, customArguments);
} else {
// Regular WebSockets expect a MessageEvent but Socketio.io just wants raw data
// payload instanceof MessageEvent works, but you can't isntance of NodeEvent
// for now we detect if the output has data defined on it
listener.call(this$1, event.data ? event.data : event);
}
});
};
Object.defineProperties( SocketIO.prototype, prototypeAccessors );
return SocketIO;
}(EventTarget));
SocketIO$1.CONNECTING = 0;
SocketIO$1.OPEN = 1;
SocketIO$1.CLOSING = 2;
SocketIO$1.CLOSED = 3;
/*
* Static constructor methods for the IO Socket
*/
var IO = function ioConstructor(url, protocol) {
return new SocketIO$1(url, protocol);
};
/*
* Alias the raw IO() constructor
*/
IO.connect = function ioConnect(url, protocol) {
/* eslint-disable new-cap */
return IO(url, protocol);
/* eslint-enable new-cap */
};
var dedupe = function (arr) { return arr.reduce(function (deduped, b) {
if (deduped.indexOf(b) > -1) { return deduped; }
return deduped.concat(b);
}, []); };
function retrieveGlobalObject() {
if (typeof window !== 'undefined') {
return window;
}
return typeof process === 'object' && typeof require === 'function' && typeof global === 'object' ? global : this;
}
var defaultOptions = {
mock: true,
verifyClient: null,
selectProtocol: null
};
var Server$1 = (function (EventTarget$$1) {
function Server(url, options) {
if ( options === void 0 ) options = defaultOptions;
EventTarget$$1.call(this);
var urlRecord = new urlParse(url);
if (!urlRecord.pathname) {
urlRecord.pathname = '/';
}
this.url = urlRecord.toString();
this.originalWebSocket = null;
var server = networkBridge.attachServer(this, this.url);
if (!server) {
this.dispatchEvent(createEvent({ type: 'error' }));
throw new Error('A mock server is already listening on this url');
}
this.options = Object.assign({}, defaultOptions, options);
if (this.options.mock) {
this.mockWebsocket();
}
}
if ( EventTarget$$1 ) Server.__proto__ = EventTarget$$1;
Server.prototype = Object.create( EventTarget$$1 && EventTarget$$1.prototype );
Server.prototype.constructor = Server;
/*
* Attaches the mock websocket object to the global object
*/
Server.prototype.mockWebsocket = function mockWebsocket () {
var globalObj = retrieveGlobalObject();
this.originalWebSocket = globalObj.WebSocket;
globalObj.WebSocket = WebSocket$1;
};
/*
* Removes the mock websocket object from the global object
*/
Server.prototype.restoreWebsocket = function restoreWebsocket () {
var globalObj = retrieveGlobalObject();
if (this.originalWebSocket !== null) {
globalObj.WebSocket = this.originalWebSocket;
}
this.originalWebSocket = null;
};
/**
* Removes itself from the urlMap so another server could add itself to the url.
* @param {function} callback - The callback is called when the server is stopped
*/
Server.prototype.stop = function stop (callback) {
if ( callback === void 0 ) callback = function () {};
if (this.options.mock) {
this.restoreWebsocket();
}
networkBridge.removeServer(this.url);
if (typeof callback === 'function') {
callback();
}
};
/*
* This is the main function for the mock server to subscribe to the on events.
*
* ie: mockServer.on('connection', function() { console.log('a mock client connected'); });
*
* @param {string} type - The event key to subscribe to. Valid keys are: connection, message, and close.
* @param {function} callback - The callback which should be called when a certain event is fired.
*/
Server.prototype.on = function on (type, callback) {
this.addEventListener(type, callback);
};
/*
* Remove event listener
*/
Server.prototype.off = function off (type, callback) {
this.removeEventListener(type, callback);
};
/*
* Closes the connection and triggers the onclose method of all listening
* websockets. After that it removes itself from the urlMap so another server
* could add itself to the url.
*
* @param {object} options
*/
Server.prototype.close = function close (options) {
if ( options === void 0 ) options = {};
var code = options.code;
var reason = options.reason;
var wasClean = options.wasClean;
var listeners = networkBridge.websocketsLookup(this.url);
// Remove server before notifications to prevent immediate reconnects from
// socket onclose handlers
networkBridge.removeServer(this.url);
listeners.forEach(function (socket) {
socket.readyState = WebSocket$1.CLOSED;
socket.dispatchEvent(
createCloseEvent({
type: 'close',
target: socket.target,
code: code || CLOSE_CODES.CLOSE_NORMAL,
reason: reason || '',
wasClean: wasClean
})
);
});
this.dispatchEvent(createCloseEvent({ type: 'close' }), this);
};
/*
* Sends a generic message event to all mock clients.
*/
Server.prototype.emit = function emit (event, data, options) {
var this$1 = this;
if ( options === void 0 ) options = {};
var websockets = options.websockets;
if (!websockets) {
websockets = networkBridge.websocketsLookup(this.url);
}
var normalizedData;
if (typeof options !== 'object' || arguments.length > 3) {
data = Array.prototype.slice.call(arguments, 1, arguments.length);
normalizedData = data.map(function (item) { return normalizeSendData(item); });
} else {
normalizedData = normalizeSendData(data);
}
websockets.forEach(function (socket) {
var messageData = socket instanceof SocketIO$1 ? data : normalizedData;
if (Array.isArray(messageData)) {
socket.dispatchEvent.apply(
socket, [ createMessageEvent({
type: event,
data: messageData,
origin: this$1.url,
target: socket.target
}) ].concat( messageData )
);
} else {
socket.dispatchEvent(
createMessageEvent({
type: event,
data: messageData,
origin: this$1.url,
target: socket.target
})
);
}
});
};
/*
* Returns an array of websockets which are listening to this server
* TOOD: this should return a set and not be a method
*/
Server.prototype.clients = function clients () {
return networkBridge.websocketsLookup(this.url);
};
/*
* Prepares a method to submit an event to members of the room
*
* e.g. server.to('my-room').emit('hi!');
*/
Server.prototype.to = function to (room, broadcaster, broadcastList) {
var this$1 = this;
if ( broadcastList === void 0 ) broadcastList = [];
var self = this;
var websockets = dedupe(broadcastList.concat(networkBridge.websocketsLookup(this.url, room, broadcaster)));
return {
to: function (chainedRoom, chainedBroadcaster) { return this$1.to.call(this$1, chainedRoom, chainedBroadcaster, websockets); },
emit: function emit(event, data) {
self.emit(event, data, { websockets: websockets });
}
};
};
/*
* Alias for Server.to
*/
Server.prototype.in = function in$1 () {
var args = [], len = arguments.length;
while ( len-- ) args[ len ] = arguments[ len ];
return this.to.apply(null, args);
};
/*
* Simulate an event from the server to the clients. Useful for
* simulating errors.
*/
Server.prototype.simulate = function simulate (event) {
var listeners = networkBridge.websocketsLookup(this.url);
if (event === 'error') {
listeners.forEach(function (socket) {
socket.readyState = WebSocket$1.CLOSED;
socket.dispatchEvent(createEvent({ type: 'error', target: socket.target }));
});
}
};
return Server;
}(EventTarget));
/*
* Alternative constructor to support namespaces in socket.io
*
* http://socket.io/docs/rooms-and-namespaces/#custom-namespaces
*/
Server$1.of = function of(url) {
return new Server$1(url);
};
var Server = Server$1;
var WebSocket = WebSocket$1;
var SocketIO$$1 = IO;
exports.Server = Server;
exports.WebSocket = WebSocket;
exports.SocketIO = SocketIO$$1;
Object.defineProperty(exports, '__esModule', { value: true });
})));