unit tests, and some cleaning up
This commit is contained in:
57
node_modules/mock-socket/src/algorithms/close.js
generated
vendored
Normal file
57
node_modules/mock-socket/src/algorithms/close.js
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
import WebSocket from '../websocket';
|
||||
import delay from '../helpers/delay';
|
||||
import networkBridge from '../network-bridge';
|
||||
import { createCloseEvent, createEvent } from '../event/factory';
|
||||
|
||||
export function closeWebSocketConnection(context, code, reason) {
|
||||
context.readyState = WebSocket.CLOSING;
|
||||
|
||||
const server = networkBridge.serverLookup(context.url);
|
||||
const closeEvent = createCloseEvent({
|
||||
type: 'close',
|
||||
target: context,
|
||||
code,
|
||||
reason
|
||||
});
|
||||
|
||||
delay(() => {
|
||||
networkBridge.removeWebSocket(context, context.url);
|
||||
|
||||
context.readyState = WebSocket.CLOSED;
|
||||
context.dispatchEvent(closeEvent);
|
||||
|
||||
if (server) {
|
||||
server.dispatchEvent(closeEvent, server);
|
||||
}
|
||||
}, context);
|
||||
}
|
||||
|
||||
export function failWebSocketConnection(context, code, reason) {
|
||||
context.readyState = WebSocket.CLOSING;
|
||||
|
||||
const server = networkBridge.serverLookup(context.url);
|
||||
const closeEvent = createCloseEvent({
|
||||
type: 'close',
|
||||
target: context,
|
||||
code,
|
||||
reason,
|
||||
wasClean: false
|
||||
});
|
||||
|
||||
const errorEvent = createEvent({
|
||||
type: 'error',
|
||||
target: context
|
||||
});
|
||||
|
||||
delay(() => {
|
||||
networkBridge.removeWebSocket(context, context.url);
|
||||
|
||||
context.readyState = WebSocket.CLOSED;
|
||||
context.dispatchEvent(errorEvent);
|
||||
context.dispatchEvent(closeEvent);
|
||||
|
||||
if (server) {
|
||||
server.dispatchEvent(closeEvent, server);
|
||||
}
|
||||
}, context);
|
||||
}
|
29
node_modules/mock-socket/src/constants.js
generated
vendored
Normal file
29
node_modules/mock-socket/src/constants.js
generated
vendored
Normal file
@ -0,0 +1,29 @@
|
||||
/*
|
||||
* https://developer.mozilla.org/en-US/docs/Web/API/CloseEvent
|
||||
*/
|
||||
export const 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
|
||||
};
|
||||
|
||||
export const 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':"
|
||||
}
|
||||
};
|
34
node_modules/mock-socket/src/event/close.js
generated
vendored
Normal file
34
node_modules/mock-socket/src/event/close.js
generated
vendored
Normal file
@ -0,0 +1,34 @@
|
||||
import EventPrototype from './prototype';
|
||||
import { ERROR_PREFIX } from '../constants';
|
||||
|
||||
export default class CloseEvent extends EventPrototype {
|
||||
constructor(type, eventInitConfig = {}) {
|
||||
super();
|
||||
|
||||
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`);
|
||||
}
|
||||
|
||||
const { bubbles, cancelable, code, reason, wasClean } = eventInitConfig;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
31
node_modules/mock-socket/src/event/event.js
generated
vendored
Normal file
31
node_modules/mock-socket/src/event/event.js
generated
vendored
Normal file
@ -0,0 +1,31 @@
|
||||
import EventPrototype from './prototype';
|
||||
import { ERROR_PREFIX } from '../constants';
|
||||
|
||||
export default class Event extends EventPrototype {
|
||||
constructor(type, eventInitConfig = {}) {
|
||||
super();
|
||||
|
||||
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.`);
|
||||
}
|
||||
|
||||
const { bubbles, cancelable } = eventInitConfig;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
75
node_modules/mock-socket/src/event/factory.js
generated
vendored
Normal file
75
node_modules/mock-socket/src/event/factory.js
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
import Event from './event';
|
||||
import MessageEvent from './message';
|
||||
import CloseEvent from './close';
|
||||
|
||||
/*
|
||||
* 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) {
|
||||
const { type, target } = config;
|
||||
const 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) {
|
||||
const { type, origin, data, target } = config;
|
||||
const messageEvent = new MessageEvent(type, {
|
||||
data,
|
||||
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) {
|
||||
const { code, reason, type, target } = config;
|
||||
let { wasClean } = config;
|
||||
|
||||
if (!wasClean) {
|
||||
wasClean = code === 1000;
|
||||
}
|
||||
|
||||
const closeEvent = new CloseEvent(type, {
|
||||
code,
|
||||
reason,
|
||||
wasClean
|
||||
});
|
||||
|
||||
if (target) {
|
||||
closeEvent.target = target;
|
||||
closeEvent.srcElement = target;
|
||||
closeEvent.currentTarget = target;
|
||||
}
|
||||
|
||||
return closeEvent;
|
||||
}
|
||||
|
||||
export { createEvent, createMessageEvent, createCloseEvent };
|
35
node_modules/mock-socket/src/event/message.js
generated
vendored
Normal file
35
node_modules/mock-socket/src/event/message.js
generated
vendored
Normal file
@ -0,0 +1,35 @@
|
||||
import EventPrototype from './prototype';
|
||||
import { ERROR_PREFIX } from '../constants';
|
||||
|
||||
export default class MessageEvent extends EventPrototype {
|
||||
constructor(type, eventInitConfig = {}) {
|
||||
super();
|
||||
|
||||
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`);
|
||||
}
|
||||
|
||||
const { bubbles, cancelable, data, origin, lastEventId, ports } = eventInitConfig;
|
||||
|
||||
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 || ''}`;
|
||||
}
|
||||
}
|
13
node_modules/mock-socket/src/event/prototype.js
generated
vendored
Normal file
13
node_modules/mock-socket/src/event/prototype.js
generated
vendored
Normal file
@ -0,0 +1,13 @@
|
||||
export default class EventPrototype {
|
||||
// Noops
|
||||
stopPropagation() {}
|
||||
stopImmediatePropagation() {}
|
||||
|
||||
// if no arguments are passed then the type is set to "undefined" on
|
||||
// chrome and safari.
|
||||
initEvent(type = 'undefined', bubbles = false, cancelable = false) {
|
||||
this.type = `${type}`;
|
||||
this.bubbles = Boolean(bubbles);
|
||||
this.cancelable = Boolean(cancelable);
|
||||
}
|
||||
}
|
73
node_modules/mock-socket/src/event/target.js
generated
vendored
Normal file
73
node_modules/mock-socket/src/event/target.js
generated
vendored
Normal file
@ -0,0 +1,73 @@
|
||||
import { reject, filter } from '../helpers/array-helpers';
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
class EventTarget {
|
||||
constructor() {
|
||||
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 - the callback function to invoke whenever an event is dispatched matching the given type
|
||||
* @param {boolean} useCapture - N/A TODO: implement useCapture functionality
|
||||
*/
|
||||
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], item => 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 - the callback function to invoke whenever an event is dispatched matching the given type
|
||||
* @param {boolean} useCapture - N/A TODO: implement useCapture functionality
|
||||
*/
|
||||
removeEventListener(type, removingListener /* , useCapture */) {
|
||||
const arrayOfListeners = this.listeners[type];
|
||||
this.listeners[type] = reject(arrayOfListeners, listener => 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
|
||||
*/
|
||||
dispatchEvent(event, ...customArguments) {
|
||||
const eventName = event.type;
|
||||
const listeners = this.listeners[eventName];
|
||||
|
||||
if (!Array.isArray(listeners)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
listeners.forEach(listener => {
|
||||
if (customArguments.length > 0) {
|
||||
listener.apply(this, customArguments);
|
||||
} else {
|
||||
listener.call(this, event);
|
||||
}
|
||||
});
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
export default EventTarget;
|
21
node_modules/mock-socket/src/helpers/array-helpers.js
generated
vendored
Normal file
21
node_modules/mock-socket/src/helpers/array-helpers.js
generated
vendored
Normal file
@ -0,0 +1,21 @@
|
||||
export function reject(array, callback) {
|
||||
const results = [];
|
||||
array.forEach(itemInArray => {
|
||||
if (!callback(itemInArray)) {
|
||||
results.push(itemInArray);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
export function filter(array, callback) {
|
||||
const results = [];
|
||||
array.forEach(itemInArray => {
|
||||
if (callback(itemInArray)) {
|
||||
results.push(itemInArray);
|
||||
}
|
||||
});
|
||||
|
||||
return results;
|
||||
}
|
5
node_modules/mock-socket/src/helpers/byte-length.js
generated
vendored
Normal file
5
node_modules/mock-socket/src/helpers/byte-length.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export default function lengthInUtf8Bytes(str) {
|
||||
// Matches only the 10.. bytes that are non-initial characters in a multi-byte sequence.
|
||||
const m = encodeURIComponent(str).match(/%[89ABab]/g);
|
||||
return str.length + (m ? m.length : 0);
|
||||
}
|
5
node_modules/mock-socket/src/helpers/dedupe.js
generated
vendored
Normal file
5
node_modules/mock-socket/src/helpers/dedupe.js
generated
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
export default arr =>
|
||||
arr.reduce((deduped, b) => {
|
||||
if (deduped.indexOf(b) > -1) return deduped;
|
||||
return deduped.concat(b);
|
||||
}, []);
|
11
node_modules/mock-socket/src/helpers/delay.js
generated
vendored
Normal file
11
node_modules/mock-socket/src/helpers/delay.js
generated
vendored
Normal file
@ -0,0 +1,11 @@
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
export default function delay(callback, context) {
|
||||
setTimeout(timeoutContext => callback.call(timeoutContext), 4, context);
|
||||
}
|
7
node_modules/mock-socket/src/helpers/global-object.js
generated
vendored
Normal file
7
node_modules/mock-socket/src/helpers/global-object.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export default function retrieveGlobalObject() {
|
||||
if (typeof window !== 'undefined') {
|
||||
return window;
|
||||
}
|
||||
|
||||
return typeof process === 'object' && typeof require === 'function' && typeof global === 'object' ? global : this;
|
||||
}
|
7
node_modules/mock-socket/src/helpers/logger.js
generated
vendored
Normal file
7
node_modules/mock-socket/src/helpers/logger.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export default 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 */
|
||||
}
|
7
node_modules/mock-socket/src/helpers/normalize-send.js
generated
vendored
Normal file
7
node_modules/mock-socket/src/helpers/normalize-send.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
export default function normalizeSendData(data) {
|
||||
if (Object.prototype.toString.call(data) !== '[object Blob]' && !(data instanceof ArrayBuffer)) {
|
||||
data = String(data);
|
||||
}
|
||||
|
||||
return data;
|
||||
}
|
24
node_modules/mock-socket/src/helpers/protocol-verification.js
generated
vendored
Normal file
24
node_modules/mock-socket/src/helpers/protocol-verification.js
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
import { ERROR_PREFIX } from '../constants';
|
||||
|
||||
export default function protocolVerification(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];
|
||||
}
|
||||
|
||||
const uniq = protocols.map(p => ({ count: 1, protocol: p })).reduce((a, b) => {
|
||||
a[b.protocol] = (a[b.protocol] || 0) + b.count;
|
||||
return a;
|
||||
}, {});
|
||||
|
||||
const duplicates = Object.keys(uniq).filter(a => uniq[a] > 1);
|
||||
|
||||
if (duplicates.length > 0) {
|
||||
throw new SyntaxError(`${ERROR_PREFIX.CONSTRUCTOR_ERROR} The subprotocol '${duplicates[0]}' is duplicated.`);
|
||||
}
|
||||
|
||||
return protocols;
|
||||
}
|
45
node_modules/mock-socket/src/helpers/proxy-factory.js
generated
vendored
Normal file
45
node_modules/mock-socket/src/helpers/proxy-factory.js
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
import { CLOSE_CODES } from '../constants';
|
||||
import { closeWebSocketConnection } from '../algorithms/close';
|
||||
import normalizeSendData from './normalize-send';
|
||||
import { createMessageEvent } from '../event/factory';
|
||||
|
||||
export default function proxyFactory(target) {
|
||||
const handler = {
|
||||
get(obj, prop) {
|
||||
if (prop === 'close') {
|
||||
return function close(options = {}) {
|
||||
const code = options.code || CLOSE_CODES.CLOSE_NORMAL;
|
||||
const reason = options.reason || '';
|
||||
|
||||
closeWebSocketConnection(target, code, reason);
|
||||
};
|
||||
}
|
||||
|
||||
if (prop === 'send') {
|
||||
return function send(data) {
|
||||
data = normalizeSendData(data);
|
||||
|
||||
target.dispatchEvent(
|
||||
createMessageEvent({
|
||||
type: 'message',
|
||||
data,
|
||||
origin: this.url,
|
||||
target
|
||||
})
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
if (prop === 'on') {
|
||||
return function onWrapper(type, cb) {
|
||||
target.addEventListener(`server::${type}`, cb);
|
||||
};
|
||||
}
|
||||
|
||||
return obj[prop];
|
||||
}
|
||||
};
|
||||
|
||||
const proxy = new Proxy(target, handler);
|
||||
return proxy;
|
||||
}
|
37
node_modules/mock-socket/src/helpers/url-verification.js
generated
vendored
Normal file
37
node_modules/mock-socket/src/helpers/url-verification.js
generated
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
import URL from 'url-parse';
|
||||
import { ERROR_PREFIX } from '../constants';
|
||||
|
||||
export default function urlVerification(url) {
|
||||
const urlRecord = new URL(url);
|
||||
const { pathname, protocol, hash } = urlRecord;
|
||||
|
||||
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();
|
||||
}
|
7
node_modules/mock-socket/src/index.js
generated
vendored
Normal file
7
node_modules/mock-socket/src/index.js
generated
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
import MockServer from './server';
|
||||
import MockSocketIO from './socket-io';
|
||||
import MockWebSocket from './websocket';
|
||||
|
||||
export const Server = MockServer;
|
||||
export const WebSocket = MockWebSocket;
|
||||
export const SocketIO = MockSocketIO;
|
135
node_modules/mock-socket/src/network-bridge.js
generated
vendored
Normal file
135
node_modules/mock-socket/src/network-bridge.js
generated
vendored
Normal file
@ -0,0 +1,135 @@
|
||||
import { reject } from './helpers/array-helpers';
|
||||
|
||||
/*
|
||||
* 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.
|
||||
*/
|
||||
class NetworkBridge {
|
||||
constructor() {
|
||||
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
|
||||
*/
|
||||
attachWebSocket(websocket, url) {
|
||||
const connectionLookup = this.urlMap[url];
|
||||
|
||||
if (connectionLookup && connectionLookup.server && connectionLookup.websockets.indexOf(websocket) === -1) {
|
||||
connectionLookup.websockets.push(websocket);
|
||||
return connectionLookup.server;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Attaches a websocket to a room
|
||||
*/
|
||||
addMembershipToRoom(websocket, room) {
|
||||
const connectionLookup = this.urlMap[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
|
||||
*/
|
||||
attachServer(server, url) {
|
||||
const connectionLookup = this.urlMap[url];
|
||||
|
||||
if (!connectionLookup) {
|
||||
this.urlMap[url] = {
|
||||
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
|
||||
*/
|
||||
serverLookup(url) {
|
||||
const connectionLookup = this.urlMap[url];
|
||||
|
||||
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
|
||||
*/
|
||||
websocketsLookup(url, room, broadcaster) {
|
||||
let websockets;
|
||||
const connectionLookup = this.urlMap[url];
|
||||
|
||||
websockets = connectionLookup ? connectionLookup.websockets : [];
|
||||
|
||||
if (room) {
|
||||
const members = connectionLookup.roomMemberships[room];
|
||||
websockets = members || [];
|
||||
}
|
||||
|
||||
return broadcaster ? websockets.filter(websocket => websocket !== broadcaster) : websockets;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the entry associated with the url.
|
||||
*
|
||||
* @param {string} url
|
||||
*/
|
||||
removeServer(url) {
|
||||
delete this.urlMap[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
|
||||
*/
|
||||
removeWebSocket(websocket, url) {
|
||||
const connectionLookup = this.urlMap[url];
|
||||
|
||||
if (connectionLookup) {
|
||||
connectionLookup.websockets = reject(connectionLookup.websockets, socket => socket === websocket);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes a websocket from a room
|
||||
*/
|
||||
removeMembershipFromRoom(websocket, room) {
|
||||
const connectionLookup = this.urlMap[websocket.url];
|
||||
const memberships = connectionLookup.roomMemberships[room];
|
||||
|
||||
if (connectionLookup && memberships !== null) {
|
||||
connectionLookup.roomMemberships[room] = reject(memberships, socket => socket === websocket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new NetworkBridge(); // Note: this is a singleton
|
217
node_modules/mock-socket/src/server.js
generated
vendored
Normal file
217
node_modules/mock-socket/src/server.js
generated
vendored
Normal file
@ -0,0 +1,217 @@
|
||||
import URL from 'url-parse';
|
||||
import WebSocket from './websocket';
|
||||
import dedupe from './helpers/dedupe';
|
||||
import EventTarget from './event/target';
|
||||
import { CLOSE_CODES } from './constants';
|
||||
import networkBridge from './network-bridge';
|
||||
import globalObject from './helpers/global-object';
|
||||
import normalizeSendData from './helpers/normalize-send';
|
||||
import { createEvent, createMessageEvent, createCloseEvent } from './event/factory';
|
||||
|
||||
class Server extends EventTarget {
|
||||
constructor(url, options = {}) {
|
||||
super();
|
||||
const urlRecord = new URL(url);
|
||||
|
||||
if (!urlRecord.pathname) {
|
||||
urlRecord.pathname = '/';
|
||||
}
|
||||
|
||||
this.url = urlRecord.toString();
|
||||
|
||||
this.originalWebSocket = null;
|
||||
const 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');
|
||||
}
|
||||
|
||||
if (typeof options.verifyClient === 'undefined') {
|
||||
options.verifyClient = null;
|
||||
}
|
||||
|
||||
if (typeof options.selectProtocol === 'undefined') {
|
||||
options.selectProtocol = null;
|
||||
}
|
||||
|
||||
this.options = options;
|
||||
this.start();
|
||||
}
|
||||
|
||||
/*
|
||||
* Attaches the mock websocket object to the global object
|
||||
*/
|
||||
start() {
|
||||
const globalObj = globalObject();
|
||||
|
||||
if (globalObj.WebSocket) {
|
||||
this.originalWebSocket = globalObj.WebSocket;
|
||||
}
|
||||
|
||||
globalObj.WebSocket = WebSocket;
|
||||
}
|
||||
|
||||
/*
|
||||
* Removes the mock websocket object from the global object
|
||||
*/
|
||||
stop(callback = () => {}) {
|
||||
const globalObj = globalObject();
|
||||
|
||||
if (this.originalWebSocket) {
|
||||
globalObj.WebSocket = this.originalWebSocket;
|
||||
} else {
|
||||
delete globalObj.WebSocket;
|
||||
}
|
||||
|
||||
this.originalWebSocket = null;
|
||||
|
||||
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.
|
||||
*/
|
||||
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
|
||||
*/
|
||||
close(options = {}) {
|
||||
const { code, reason, wasClean } = options;
|
||||
const listeners = networkBridge.websocketsLookup(this.url);
|
||||
|
||||
// Remove server before notifications to prevent immediate reconnects from
|
||||
// socket onclose handlers
|
||||
networkBridge.removeServer(this.url);
|
||||
|
||||
listeners.forEach(socket => {
|
||||
socket.readyState = WebSocket.CLOSE;
|
||||
socket.dispatchEvent(
|
||||
createCloseEvent({
|
||||
type: 'close',
|
||||
target: socket,
|
||||
code: code || CLOSE_CODES.CLOSE_NORMAL,
|
||||
reason: reason || '',
|
||||
wasClean
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
this.dispatchEvent(createCloseEvent({ type: 'close' }), this);
|
||||
}
|
||||
|
||||
/*
|
||||
* Sends a generic message event to all mock clients.
|
||||
*/
|
||||
emit(event, data, options = {}) {
|
||||
let { websockets } = options;
|
||||
|
||||
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(item => normalizeSendData(item));
|
||||
} else {
|
||||
data = normalizeSendData(data);
|
||||
}
|
||||
|
||||
websockets.forEach(socket => {
|
||||
if (Array.isArray(data)) {
|
||||
socket.dispatchEvent(
|
||||
createMessageEvent({
|
||||
type: event,
|
||||
data,
|
||||
origin: this.url,
|
||||
target: socket
|
||||
}),
|
||||
...data
|
||||
);
|
||||
} else {
|
||||
socket.dispatchEvent(
|
||||
createMessageEvent({
|
||||
type: event,
|
||||
data,
|
||||
origin: this.url,
|
||||
target: socket
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns an array of websockets which are listening to this server
|
||||
* TOOD: this should return a set and not be a method
|
||||
*/
|
||||
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!');
|
||||
*/
|
||||
to(room, broadcaster, broadcastList = []) {
|
||||
const self = this;
|
||||
const websockets = dedupe(broadcastList.concat(networkBridge.websocketsLookup(this.url, room, broadcaster)));
|
||||
|
||||
return {
|
||||
to: (chainedRoom, chainedBroadcaster) => this.to.call(this, chainedRoom, chainedBroadcaster, websockets),
|
||||
emit(event, data) {
|
||||
self.emit(event, data, { websockets });
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* Alias for Server.to
|
||||
*/
|
||||
in(...args) {
|
||||
return this.to.apply(null, args);
|
||||
}
|
||||
|
||||
/*
|
||||
* Simulate an event from the server to the clients. Useful for
|
||||
* simulating errors.
|
||||
*/
|
||||
simulate(event) {
|
||||
const listeners = networkBridge.websocketsLookup(this.url);
|
||||
|
||||
if (event === 'error') {
|
||||
listeners.forEach(socket => {
|
||||
socket.readyState = WebSocket.CLOSE;
|
||||
socket.dispatchEvent(createEvent({ type: 'error' }));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Alternative constructor to support namespaces in socket.io
|
||||
*
|
||||
* http://socket.io/docs/rooms-and-namespaces/#custom-namespaces
|
||||
*/
|
||||
Server.of = function of(url) {
|
||||
return new Server(url);
|
||||
};
|
||||
|
||||
export default Server;
|
279
node_modules/mock-socket/src/socket-io.js
generated
vendored
Normal file
279
node_modules/mock-socket/src/socket-io.js
generated
vendored
Normal file
@ -0,0 +1,279 @@
|
||||
import URL from 'url-parse';
|
||||
import delay from './helpers/delay';
|
||||
import EventTarget from './event/target';
|
||||
import networkBridge from './network-bridge';
|
||||
import { CLOSE_CODES } from './constants';
|
||||
import logger from './helpers/logger';
|
||||
import { createEvent, createMessageEvent, createCloseEvent } from './event/factory';
|
||||
|
||||
/*
|
||||
* The socket-io class is designed to mimick the real API as closely as possible.
|
||||
*
|
||||
* http://socket.io/docs/
|
||||
*/
|
||||
class SocketIO extends EventTarget {
|
||||
/*
|
||||
* @param {string} url
|
||||
*/
|
||||
constructor(url = 'socket.io', protocol = '') {
|
||||
super();
|
||||
|
||||
this.binaryType = 'blob';
|
||||
const urlRecord = new URL(url);
|
||||
|
||||
if (!urlRecord.pathname) {
|
||||
urlRecord.pathname = '/';
|
||||
}
|
||||
|
||||
this.url = urlRecord.toString();
|
||||
this.readyState = SocketIO.CONNECTING;
|
||||
this.protocol = '';
|
||||
|
||||
if (typeof protocol === 'string' || (typeof protocol === 'object' && protocol !== null)) {
|
||||
this.protocol = protocol;
|
||||
} else if (Array.isArray(protocol) && protocol.length > 0) {
|
||||
this.protocol = protocol[0];
|
||||
}
|
||||
|
||||
const 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
|
||||
})
|
||||
);
|
||||
|
||||
logger('error', `Socket.io connection to '${this.url}' failed`);
|
||||
}
|
||||
}, this);
|
||||
|
||||
/**
|
||||
Add an aliased event listener for close / disconnect
|
||||
*/
|
||||
this.addEventListener('close', event => {
|
||||
this.dispatchEvent(
|
||||
createCloseEvent({
|
||||
type: 'disconnect',
|
||||
target: event.target,
|
||||
code: event.code
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Closes the SocketIO connection or connection attempt, if any.
|
||||
* If the connection is already CLOSED, this method does nothing.
|
||||
*/
|
||||
close() {
|
||||
if (this.readyState !== SocketIO.OPEN) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const 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
|
||||
*/
|
||||
disconnect() {
|
||||
return this.close();
|
||||
}
|
||||
|
||||
/*
|
||||
* Submits an event to the server with a payload
|
||||
*/
|
||||
emit(event, ...data) {
|
||||
if (this.readyState !== SocketIO.OPEN) {
|
||||
throw new Error('SocketIO is already in CLOSING or CLOSED state');
|
||||
}
|
||||
|
||||
const messageEvent = createMessageEvent({
|
||||
type: event,
|
||||
origin: this.url,
|
||||
data
|
||||
});
|
||||
|
||||
const server = networkBridge.serverLookup(this.url);
|
||||
|
||||
if (server) {
|
||||
server.dispatchEvent(messageEvent, ...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
|
||||
*/
|
||||
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!');
|
||||
*/
|
||||
get broadcast() {
|
||||
if (this.readyState !== SocketIO.OPEN) {
|
||||
throw new Error('SocketIO is already in CLOSING or CLOSED state');
|
||||
}
|
||||
|
||||
const self = this;
|
||||
const server = networkBridge.serverLookup(this.url);
|
||||
if (!server) {
|
||||
throw new Error(`SocketIO can not find a server at the specified URL (${this.url})`);
|
||||
}
|
||||
|
||||
return {
|
||||
emit(event, data) {
|
||||
server.emit(event, data, { websockets: networkBridge.websocketsLookup(self.url, null, self) });
|
||||
return self;
|
||||
},
|
||||
to(room) {
|
||||
return server.to(room, self);
|
||||
},
|
||||
in(room) {
|
||||
return server.in(room, self);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/*
|
||||
* For registering events to be received from the server
|
||||
*/
|
||||
on(type, callback) {
|
||||
this.addEventListener(type, callback);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* Remove event listener
|
||||
*
|
||||
* https://socket.io/docs/client-api/#socket-on-eventname-callback
|
||||
*/
|
||||
off(type) {
|
||||
this.removeEventListener(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* Join a room on a server
|
||||
*
|
||||
* http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving
|
||||
*/
|
||||
join(room) {
|
||||
networkBridge.addMembershipToRoom(this, room);
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the websocket to leave the room
|
||||
*
|
||||
* http://socket.io/docs/rooms-and-namespaces/#joining-and-leaving
|
||||
*/
|
||||
leave(room) {
|
||||
networkBridge.removeMembershipFromRoom(this, room);
|
||||
}
|
||||
|
||||
to(room) {
|
||||
return this.broadcast.to(room);
|
||||
}
|
||||
|
||||
in() {
|
||||
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
|
||||
*/
|
||||
dispatchEvent(event, ...customArguments) {
|
||||
const eventName = event.type;
|
||||
const listeners = this.listeners[eventName];
|
||||
|
||||
if (!Array.isArray(listeners)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
listeners.forEach(listener => {
|
||||
if (customArguments.length > 0) {
|
||||
listener.apply(this, 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, event.data ? event.data : event);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
SocketIO.CONNECTING = 0;
|
||||
SocketIO.OPEN = 1;
|
||||
SocketIO.CLOSING = 2;
|
||||
SocketIO.CLOSED = 3;
|
||||
|
||||
/*
|
||||
* Static constructor methods for the IO Socket
|
||||
*/
|
||||
const IO = function ioConstructor(url, protocol) {
|
||||
return new SocketIO(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 */
|
||||
};
|
||||
|
||||
export default IO;
|
191
node_modules/mock-socket/src/websocket.js
generated
vendored
Normal file
191
node_modules/mock-socket/src/websocket.js
generated
vendored
Normal file
@ -0,0 +1,191 @@
|
||||
import delay from './helpers/delay';
|
||||
import logger from './helpers/logger';
|
||||
import EventTarget from './event/target';
|
||||
import networkBridge from './network-bridge';
|
||||
import proxyFactory from './helpers/proxy-factory';
|
||||
import lengthInUtf8Bytes from './helpers/byte-length';
|
||||
import { CLOSE_CODES, ERROR_PREFIX } from './constants';
|
||||
import urlVerification from './helpers/url-verification';
|
||||
import normalizeSendData from './helpers/normalize-send';
|
||||
import protocolVerification from './helpers/protocol-verification';
|
||||
import { createEvent, createMessageEvent, createCloseEvent } from './event/factory';
|
||||
import { closeWebSocketConnection, failWebSocketConnection } from './algorithms/close';
|
||||
|
||||
/*
|
||||
* 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
|
||||
*/
|
||||
class WebSocket extends EventTarget {
|
||||
constructor(url, protocols) {
|
||||
super();
|
||||
|
||||
this.url = urlVerification(url);
|
||||
protocols = protocolVerification(protocols);
|
||||
this.protocol = protocols[0] || '';
|
||||
|
||||
this.binaryType = 'blob';
|
||||
this.readyState = WebSocket.CONNECTING;
|
||||
|
||||
const server = networkBridge.attachWebSocket(this, 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;
|
||||
|
||||
logger(
|
||||
'error',
|
||||
`WebSocket connection to '${this.url}' failed: HTTP Authentication failed; no valid credentials available`
|
||||
);
|
||||
|
||||
networkBridge.removeWebSocket(this, 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') {
|
||||
const selectedProtocol = server.options.selectProtocol(protocols);
|
||||
const isFilled = selectedProtocol !== '';
|
||||
const isRequested = protocols.indexOf(selectedProtocol) !== -1;
|
||||
if (isFilled && !isRequested) {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
|
||||
logger('error', `WebSocket connection to '${this.url}' failed: Invalid Sub-Protocol`);
|
||||
|
||||
networkBridge.removeWebSocket(this, 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' }), proxyFactory(this));
|
||||
}
|
||||
} else {
|
||||
this.readyState = WebSocket.CLOSED;
|
||||
this.dispatchEvent(createEvent({ type: 'error', target: this }));
|
||||
this.dispatchEvent(createCloseEvent({ type: 'close', target: this, code: CLOSE_CODES.CLOSE_NORMAL }));
|
||||
|
||||
logger('error', `WebSocket connection to '${this.url}' failed`);
|
||||
}
|
||||
}, this);
|
||||
}
|
||||
|
||||
get onopen() {
|
||||
return this.listeners.open;
|
||||
}
|
||||
|
||||
get onmessage() {
|
||||
return this.listeners.message;
|
||||
}
|
||||
|
||||
get onclose() {
|
||||
return this.listeners.close;
|
||||
}
|
||||
|
||||
get onerror() {
|
||||
return this.listeners.error;
|
||||
}
|
||||
|
||||
set onopen(listener) {
|
||||
delete this.listeners.open;
|
||||
this.addEventListener('open', listener);
|
||||
}
|
||||
|
||||
set onmessage(listener) {
|
||||
delete this.listeners.message;
|
||||
this.addEventListener('message', listener);
|
||||
}
|
||||
|
||||
set onclose(listener) {
|
||||
delete this.listeners.close;
|
||||
this.addEventListener('close', listener);
|
||||
}
|
||||
|
||||
set onerror(listener) {
|
||||
delete this.listeners.error;
|
||||
this.addEventListener('error', listener);
|
||||
}
|
||||
|
||||
send(data) {
|
||||
if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) {
|
||||
throw new Error('WebSocket is already in CLOSING or CLOSED state');
|
||||
}
|
||||
|
||||
// TODO: handle bufferedAmount
|
||||
|
||||
const messageEvent = createMessageEvent({
|
||||
type: 'server::message',
|
||||
origin: this.url,
|
||||
data: normalizeSendData(data)
|
||||
});
|
||||
|
||||
const server = networkBridge.serverLookup(this.url);
|
||||
|
||||
if (server) {
|
||||
delay(() => {
|
||||
this.dispatchEvent(messageEvent, data);
|
||||
}, server);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
const 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;
|
||||
}
|
||||
|
||||
if (this.readyState === WebSocket.CONNECTING) {
|
||||
failWebSocketConnection(this, code, reason);
|
||||
} else {
|
||||
closeWebSocketConnection(this, code, reason);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
WebSocket.CONNECTING = 0;
|
||||
WebSocket.prototype.CONNECTING = WebSocket.CONNECTING;
|
||||
WebSocket.OPEN = 1;
|
||||
WebSocket.prototype.OPEN = WebSocket.OPEN;
|
||||
WebSocket.CLOSING = 2;
|
||||
WebSocket.prototype.CLOSING = WebSocket.CLOSING;
|
||||
WebSocket.CLOSED = 3;
|
||||
WebSocket.prototype.CLOSED = WebSocket.CLOSED;
|
||||
|
||||
export default WebSocket;
|
Reference in New Issue
Block a user