mirror of
https://github.com/bigskysoftware/htmx.git
synced 2025-09-29 22:11:22 +00:00
2158 lines
60 KiB
JavaScript
2158 lines
60 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 slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//;
|
|
var protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i;
|
|
var whitespace = '[\\x09\\x0A\\x0B\\x0C\\x0D\\x20\\xA0\\u1680\\u180E\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200A\\u202F\\u205F\\u3000\\u2028\\u2029\\uFEFF]';
|
|
var left = new RegExp('^'+ whitespace +'+');
|
|
|
|
/**
|
|
* Trim a given string.
|
|
*
|
|
* @param {String} str String to trim.
|
|
* @public
|
|
*/
|
|
function trimLeft(str) {
|
|
return (str ? str : '').toString().replace(left, '');
|
|
}
|
|
|
|
/**
|
|
* 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);
|
|
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);
|
|
|
|
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 (
|
|
url.protocol === 'file:' ||
|
|
(!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) {
|
|
if (~(index = address.indexOf(parse))) {
|
|
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) {
|
|
instruction = url.auth.split(':');
|
|
url.username = instruction[0] || '';
|
|
url.password = instruction[1] || '';
|
|
}
|
|
|
|
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;
|
|
|
|
default:
|
|
url[part] = value;
|
|
}
|
|
|
|
for (var i = 0; i < rules.length; i++) {
|
|
var ins = rules[i];
|
|
|
|
if (ins[4]) { url[ins[1]] = url[ins[1]].toLowerCase(); }
|
|
}
|
|
|
|
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
|
|
, protocol = url.protocol;
|
|
|
|
if (protocol && protocol.charAt(protocol.length - 1) !== ':') { protocol += ':'; }
|
|
|
|
var result = protocol + (url.slashes || isSpecial(url.protocol) ? '//' : '');
|
|
|
|
if (url.username) {
|
|
result += url.username;
|
|
if (url.password) { result += ':'+ url.password; }
|
|
result += '@';
|
|
}
|
|
|
|
result += url.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
|
|
});
|
|
var serverCloseEvent = createCloseEvent({
|
|
type: 'server::close',
|
|
target: context,
|
|
code: code,
|
|
reason: reason
|
|
});
|
|
|
|
delay(function () {
|
|
networkBridge.removeWebSocket(context, context.url);
|
|
|
|
context.readyState = WebSocket$1.CLOSED;
|
|
context.dispatchEvent(closeEvent);
|
|
context.dispatchEvent(serverCloseEvent);
|
|
|
|
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 serverCloseEvent = createCloseEvent({
|
|
type: 'server::close',
|
|
target: context,
|
|
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);
|
|
context.dispatchEvent(serverCloseEvent);
|
|
|
|
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
|
|
})
|
|
);
|
|
};
|
|
}
|
|
|
|
if (prop === 'on') {
|
|
return function onWrapper(type, cb) {
|
|
target.addEventListener(("server::" + 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;
|
|
|
|
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);
|
|
};
|
|
|
|
/*
|
|
* 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);
|
|
}
|
|
|
|
if (typeof options !== 'object' || arguments.length > 3) {
|
|
data = Array.prototype.slice.call(arguments, 1, arguments.length);
|
|
data = data.map(function (item) { return normalizeSendData(item); });
|
|
} else {
|
|
data = normalizeSendData(data);
|
|
}
|
|
|
|
websockets.forEach(function (socket) {
|
|
if (Array.isArray(data)) {
|
|
socket.dispatchEvent.apply(
|
|
socket, [ createMessageEvent({
|
|
type: event,
|
|
data: data,
|
|
origin: this$1.url,
|
|
target: socket.target
|
|
}) ].concat( data )
|
|
);
|
|
} else {
|
|
socket.dispatchEvent(
|
|
createMessageEvent({
|
|
type: event,
|
|
data: data,
|
|
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' }));
|
|
});
|
|
}
|
|
};
|
|
|
|
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);
|
|
};
|
|
|
|
/*
|
|
* 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 Server = Server$1;
|
|
var WebSocket = WebSocket$1;
|
|
var SocketIO = IO;
|
|
|
|
exports.Server = Server;
|
|
exports.WebSocket = WebSocket;
|
|
exports.SocketIO = SocketIO;
|
|
|
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
|
|
})));
|