@stencil/core
Version:
A Compiler for Web Components and Progressive Web Apps
1,858 lines (1,603 loc) • 93.3 kB
JavaScript
(function(e, a) { for(var i in a) e[i] = a[i]; }(exports, /******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = 11);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
module.exports = {
BINARY_TYPES: ['nodebuffer', 'arraybuffer', 'fragments'],
GUID: '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',
kStatusCode: Symbol('status-code'),
kWebSocket: Symbol('websocket'),
EMPTY_BUFFER: Buffer.alloc(0),
NOOP: () => {}
};
/***/ }),
/* 1 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const Limiter = __webpack_require__(17);
const zlib = __webpack_require__(18);
const bufferUtil = __webpack_require__(2);
const { kStatusCode, NOOP } = __webpack_require__(0);
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
const EMPTY_BLOCK = Buffer.from([0x00]);
const kPerMessageDeflate = Symbol('permessage-deflate');
const kTotalLength = Symbol('total-length');
const kCallback = Symbol('callback');
const kBuffers = Symbol('buffers');
const kError = Symbol('error');
//
// We limit zlib concurrency, which prevents severe memory fragmentation
// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
// and https://github.com/websockets/ws/issues/1202
//
// Intentionally global; it's the global thread pool that's an issue.
//
let zlibLimiter;
/**
* permessage-deflate implementation.
*/
class PerMessageDeflate {
/**
* Creates a PerMessageDeflate instance.
*
* @param {Object} options Configuration options
* @param {Boolean} options.serverNoContextTakeover Request/accept disabling
* of server context takeover
* @param {Boolean} options.clientNoContextTakeover Advertise/acknowledge
* disabling of client context takeover
* @param {(Boolean|Number)} options.serverMaxWindowBits Request/confirm the
* use of a custom server window size
* @param {(Boolean|Number)} options.clientMaxWindowBits Advertise support
* for, or request, a custom client window size
* @param {Object} options.zlibDeflateOptions Options to pass to zlib on deflate
* @param {Object} options.zlibInflateOptions Options to pass to zlib on inflate
* @param {Number} options.threshold Size (in bytes) below which messages
* should not be compressed
* @param {Number} options.concurrencyLimit The number of concurrent calls to
* zlib
* @param {Boolean} isServer Create the instance in either server or client
* mode
* @param {Number} maxPayload The maximum allowed message length
*/
constructor(options, isServer, maxPayload) {
this._maxPayload = maxPayload | 0;
this._options = options || {};
this._threshold =
this._options.threshold !== undefined ? this._options.threshold : 1024;
this._isServer = !!isServer;
this._deflate = null;
this._inflate = null;
this.params = null;
if (!zlibLimiter) {
const concurrency =
this._options.concurrencyLimit !== undefined
? this._options.concurrencyLimit
: 10;
zlibLimiter = new Limiter({ concurrency });
}
}
/**
* @type {String}
*/
static get extensionName() {
return 'permessage-deflate';
}
/**
* Create an extension negotiation offer.
*
* @return {Object} Extension parameters
* @public
*/
offer() {
const params = {};
if (this._options.serverNoContextTakeover) {
params.server_no_context_takeover = true;
}
if (this._options.clientNoContextTakeover) {
params.client_no_context_takeover = true;
}
if (this._options.serverMaxWindowBits) {
params.server_max_window_bits = this._options.serverMaxWindowBits;
}
if (this._options.clientMaxWindowBits) {
params.client_max_window_bits = this._options.clientMaxWindowBits;
} else if (this._options.clientMaxWindowBits == null) {
params.client_max_window_bits = true;
}
return params;
}
/**
* Accept an extension negotiation offer/response.
*
* @param {Array} configurations The extension negotiation offers/reponse
* @return {Object} Accepted configuration
* @public
*/
accept(configurations) {
configurations = this.normalizeParams(configurations);
this.params = this._isServer
? this.acceptAsServer(configurations)
: this.acceptAsClient(configurations);
return this.params;
}
/**
* Releases all resources used by the extension.
*
* @public
*/
cleanup() {
if (this._inflate) {
this._inflate.close();
this._inflate = null;
}
if (this._deflate) {
this._deflate.close();
this._deflate = null;
}
}
/**
* Accept an extension negotiation offer.
*
* @param {Array} offers The extension negotiation offers
* @return {Object} Accepted configuration
* @private
*/
acceptAsServer(offers) {
const opts = this._options;
const accepted = offers.find((params) => {
if (
(opts.serverNoContextTakeover === false &&
params.server_no_context_takeover) ||
(params.server_max_window_bits &&
(opts.serverMaxWindowBits === false ||
(typeof opts.serverMaxWindowBits === 'number' &&
opts.serverMaxWindowBits > params.server_max_window_bits))) ||
(typeof opts.clientMaxWindowBits === 'number' &&
!params.client_max_window_bits)
) {
return false;
}
return true;
});
if (!accepted) {
throw new Error('None of the extension offers can be accepted');
}
if (opts.serverNoContextTakeover) {
accepted.server_no_context_takeover = true;
}
if (opts.clientNoContextTakeover) {
accepted.client_no_context_takeover = true;
}
if (typeof opts.serverMaxWindowBits === 'number') {
accepted.server_max_window_bits = opts.serverMaxWindowBits;
}
if (typeof opts.clientMaxWindowBits === 'number') {
accepted.client_max_window_bits = opts.clientMaxWindowBits;
} else if (
accepted.client_max_window_bits === true ||
opts.clientMaxWindowBits === false
) {
delete accepted.client_max_window_bits;
}
return accepted;
}
/**
* Accept the extension negotiation response.
*
* @param {Array} response The extension negotiation response
* @return {Object} Accepted configuration
* @private
*/
acceptAsClient(response) {
const params = response[0];
if (
this._options.clientNoContextTakeover === false &&
params.client_no_context_takeover
) {
throw new Error('Unexpected parameter "client_no_context_takeover"');
}
if (!params.client_max_window_bits) {
if (typeof this._options.clientMaxWindowBits === 'number') {
params.client_max_window_bits = this._options.clientMaxWindowBits;
}
} else if (
this._options.clientMaxWindowBits === false ||
(typeof this._options.clientMaxWindowBits === 'number' &&
params.client_max_window_bits > this._options.clientMaxWindowBits)
) {
throw new Error(
'Unexpected or invalid parameter "client_max_window_bits"'
);
}
return params;
}
/**
* Normalize parameters.
*
* @param {Array} configurations The extension negotiation offers/reponse
* @return {Array} The offers/response with normalized parameters
* @private
*/
normalizeParams(configurations) {
configurations.forEach((params) => {
Object.keys(params).forEach((key) => {
let value = params[key];
if (value.length > 1) {
throw new Error(`Parameter "${key}" must have only a single value`);
}
value = value[0];
if (key === 'client_max_window_bits') {
if (value !== true) {
const num = +value;
if (!Number.isInteger(num) || num < 8 || num > 15) {
throw new TypeError(
`Invalid value for parameter "${key}": ${value}`
);
}
value = num;
} else if (!this._isServer) {
throw new TypeError(
`Invalid value for parameter "${key}": ${value}`
);
}
} else if (key === 'server_max_window_bits') {
const num = +value;
if (!Number.isInteger(num) || num < 8 || num > 15) {
throw new TypeError(
`Invalid value for parameter "${key}": ${value}`
);
}
value = num;
} else if (
key === 'client_no_context_takeover' ||
key === 'server_no_context_takeover'
) {
if (value !== true) {
throw new TypeError(
`Invalid value for parameter "${key}": ${value}`
);
}
} else {
throw new Error(`Unknown parameter "${key}"`);
}
params[key] = value;
});
});
return configurations;
}
/**
* Decompress data. Concurrency limited by async-limiter.
*
* @param {Buffer} data Compressed data
* @param {Boolean} fin Specifies whether or not this is the last fragment
* @param {Function} callback Callback
* @public
*/
decompress(data, fin, callback) {
zlibLimiter.push((done) => {
this._decompress(data, fin, (err, result) => {
done();
callback(err, result);
});
});
}
/**
* Compress data. Concurrency limited by async-limiter.
*
* @param {Buffer} data Data to compress
* @param {Boolean} fin Specifies whether or not this is the last fragment
* @param {Function} callback Callback
* @public
*/
compress(data, fin, callback) {
zlibLimiter.push((done) => {
this._compress(data, fin, (err, result) => {
done();
callback(err, result);
});
});
}
/**
* Decompress data.
*
* @param {Buffer} data Compressed data
* @param {Boolean} fin Specifies whether or not this is the last fragment
* @param {Function} callback Callback
* @private
*/
_decompress(data, fin, callback) {
const endpoint = this._isServer ? 'client' : 'server';
if (!this._inflate) {
const key = `${endpoint}_max_window_bits`;
const windowBits =
typeof this.params[key] !== 'number'
? zlib.Z_DEFAULT_WINDOWBITS
: this.params[key];
this._inflate = zlib.createInflateRaw({
...this._options.zlibInflateOptions,
windowBits
});
this._inflate[kPerMessageDeflate] = this;
this._inflate[kTotalLength] = 0;
this._inflate[kBuffers] = [];
this._inflate.on('error', inflateOnError);
this._inflate.on('data', inflateOnData);
}
this._inflate[kCallback] = callback;
this._inflate.write(data);
if (fin) this._inflate.write(TRAILER);
this._inflate.flush(() => {
const err = this._inflate[kError];
if (err) {
this._inflate.close();
this._inflate = null;
callback(err);
return;
}
const data = bufferUtil.concat(
this._inflate[kBuffers],
this._inflate[kTotalLength]
);
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
this._inflate.close();
this._inflate = null;
} else {
this._inflate[kTotalLength] = 0;
this._inflate[kBuffers] = [];
}
callback(null, data);
});
}
/**
* Compress data.
*
* @param {Buffer} data Data to compress
* @param {Boolean} fin Specifies whether or not this is the last fragment
* @param {Function} callback Callback
* @private
*/
_compress(data, fin, callback) {
if (!data || data.length === 0) {
process.nextTick(callback, null, EMPTY_BLOCK);
return;
}
const endpoint = this._isServer ? 'server' : 'client';
if (!this._deflate) {
const key = `${endpoint}_max_window_bits`;
const windowBits =
typeof this.params[key] !== 'number'
? zlib.Z_DEFAULT_WINDOWBITS
: this.params[key];
this._deflate = zlib.createDeflateRaw({
...this._options.zlibDeflateOptions,
windowBits
});
this._deflate[kTotalLength] = 0;
this._deflate[kBuffers] = [];
//
// An `'error'` event is emitted, only on Node.js < 10.0.0, if the
// `zlib.DeflateRaw` instance is closed while data is being processed.
// This can happen if `PerMessageDeflate#cleanup()` is called at the wrong
// time due to an abnormal WebSocket closure.
//
this._deflate.on('error', NOOP);
this._deflate.on('data', deflateOnData);
}
this._deflate.write(data);
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => {
if (!this._deflate) {
//
// This `if` statement is only needed for Node.js < 10.0.0 because as of
// commit https://github.com/nodejs/node/commit/5e3f5164, the flush
// callback is no longer called if the deflate stream is closed while
// data is being processed.
//
return;
}
let data = bufferUtil.concat(
this._deflate[kBuffers],
this._deflate[kTotalLength]
);
if (fin) data = data.slice(0, data.length - 4);
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
this._deflate.close();
this._deflate = null;
} else {
this._deflate[kTotalLength] = 0;
this._deflate[kBuffers] = [];
}
callback(null, data);
});
}
}
module.exports = PerMessageDeflate;
/**
* The listener of the `zlib.DeflateRaw` stream `'data'` event.
*
* @param {Buffer} chunk A chunk of data
* @private
*/
function deflateOnData(chunk) {
this[kBuffers].push(chunk);
this[kTotalLength] += chunk.length;
}
/**
* The listener of the `zlib.InflateRaw` stream `'data'` event.
*
* @param {Buffer} chunk A chunk of data
* @private
*/
function inflateOnData(chunk) {
this[kTotalLength] += chunk.length;
if (
this[kPerMessageDeflate]._maxPayload < 1 ||
this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
) {
this[kBuffers].push(chunk);
return;
}
this[kError] = new RangeError('Max payload size exceeded');
this[kError][kStatusCode] = 1009;
this.removeListener('data', inflateOnData);
this.reset();
}
/**
* The listener of the `zlib.InflateRaw` stream `'error'` event.
*
* @param {Error} err The emitted error
* @private
*/
function inflateOnError(err) {
//
// There is no need to call `Zlib#close()` as the handle is automatically
// closed when an error is emitted.
//
this[kPerMessageDeflate]._inflate = null;
err[kStatusCode] = 1007;
this[kCallback](err);
}
/***/ }),
/* 2 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const { EMPTY_BUFFER } = __webpack_require__(0);
/**
* Merges an array of buffers into a new buffer.
*
* @param {Buffer[]} list The array of buffers to concat
* @param {Number} totalLength The total length of buffers in the list
* @return {Buffer} The resulting buffer
* @public
*/
function concat(list, totalLength) {
if (list.length === 0) return EMPTY_BUFFER;
if (list.length === 1) return list[0];
const target = Buffer.allocUnsafe(totalLength);
let offset = 0;
for (let i = 0; i < list.length; i++) {
const buf = list[i];
buf.copy(target, offset);
offset += buf.length;
}
return target;
}
/**
* Masks a buffer using the given mask.
*
* @param {Buffer} source The buffer to mask
* @param {Buffer} mask The mask to use
* @param {Buffer} output The buffer where to store the result
* @param {Number} offset The offset at which to start writing
* @param {Number} length The number of bytes to mask.
* @public
*/
function _mask(source, mask, output, offset, length) {
for (let i = 0; i < length; i++) {
output[offset + i] = source[i] ^ mask[i & 3];
}
}
/**
* Unmasks a buffer using the given mask.
*
* @param {Buffer} buffer The buffer to unmask
* @param {Buffer} mask The mask to use
* @public
*/
function _unmask(buffer, mask) {
// Required until https://github.com/nodejs/node/issues/9006 is resolved.
const length = buffer.length;
for (let i = 0; i < length; i++) {
buffer[i] ^= mask[i & 3];
}
}
/**
* Converts a buffer to an `ArrayBuffer`.
*
* @param {Buffer} buf The buffer to convert
* @return {ArrayBuffer} Converted buffer
* @public
*/
function toArrayBuffer(buf) {
if (buf.byteLength === buf.buffer.byteLength) {
return buf.buffer;
}
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength);
}
/**
* Converts `data` to a `Buffer`.
*
* @param {*} data The data to convert
* @return {Buffer} The buffer
* @throws {TypeError}
* @public
*/
function toBuffer(data) {
toBuffer.readOnly = true;
if (Buffer.isBuffer(data)) return data;
let buf;
if (data instanceof ArrayBuffer) {
buf = Buffer.from(data);
} else if (ArrayBuffer.isView(data)) {
buf = viewToBuffer(data);
} else {
buf = Buffer.from(data);
toBuffer.readOnly = false;
}
return buf;
}
/**
* Converts an `ArrayBuffer` view into a buffer.
*
* @param {(DataView|TypedArray)} view The view to convert
* @return {Buffer} Converted view
* @private
*/
function viewToBuffer(view) {
const buf = Buffer.from(view.buffer);
if (view.byteLength !== view.buffer.byteLength) {
return buf.slice(view.byteOffset, view.byteOffset + view.byteLength);
}
return buf;
}
try {
const bufferUtil = __webpack_require__(!(function webpackMissingModule() { var e = new Error("Cannot find module 'bufferutil'"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));
const bu = bufferUtil.BufferUtil || bufferUtil;
module.exports = {
concat,
mask(source, mask, output, offset, length) {
if (length < 48) _mask(source, mask, output, offset, length);
else bu.mask(source, mask, output, offset, length);
},
toArrayBuffer,
toBuffer,
unmask(buffer, mask) {
if (buffer.length < 32) _unmask(buffer, mask);
else bu.unmask(buffer, mask);
}
};
} catch (e) /* istanbul ignore next */ {
module.exports = {
concat,
mask: _mask,
toArrayBuffer,
toBuffer,
unmask: _unmask
};
}
/***/ }),
/* 3 */
/***/ (function(module, exports) {
module.exports = require("crypto");
/***/ }),
/* 4 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const EventEmitter = __webpack_require__(5);
const https = __webpack_require__(13);
const http = __webpack_require__(6);
const net = __webpack_require__(14);
const tls = __webpack_require__(15);
const { randomBytes, createHash } = __webpack_require__(3);
const { URL } = __webpack_require__(16);
const PerMessageDeflate = __webpack_require__(1);
const Receiver = __webpack_require__(7);
const Sender = __webpack_require__(9);
const {
BINARY_TYPES,
EMPTY_BUFFER,
GUID,
kStatusCode,
kWebSocket,
NOOP
} = __webpack_require__(0);
const { addEventListener, removeEventListener } = __webpack_require__(20);
const { format, parse } = __webpack_require__(10);
const { toBuffer } = __webpack_require__(2);
const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
const protocolVersions = [8, 13];
const closeTimeout = 30 * 1000;
/**
* Class representing a WebSocket.
*
* @extends EventEmitter
*/
class WebSocket extends EventEmitter {
/**
* Create a new `WebSocket`.
*
* @param {(String|url.URL)} address The URL to which to connect
* @param {(String|String[])} protocols The subprotocols
* @param {Object} options Connection options
*/
constructor(address, protocols, options) {
super();
this.readyState = WebSocket.CONNECTING;
this.protocol = '';
this._binaryType = BINARY_TYPES[0];
this._closeFrameReceived = false;
this._closeFrameSent = false;
this._closeMessage = '';
this._closeTimer = null;
this._closeCode = 1006;
this._extensions = {};
this._receiver = null;
this._sender = null;
this._socket = null;
if (address !== null) {
this._bufferedAmount = 0;
this._isServer = false;
this._redirects = 0;
if (Array.isArray(protocols)) {
protocols = protocols.join(', ');
} else if (typeof protocols === 'object' && protocols !== null) {
options = protocols;
protocols = undefined;
}
initAsClient(this, address, protocols, options);
} else {
this._isServer = true;
}
}
get CONNECTING() {
return WebSocket.CONNECTING;
}
get CLOSING() {
return WebSocket.CLOSING;
}
get CLOSED() {
return WebSocket.CLOSED;
}
get OPEN() {
return WebSocket.OPEN;
}
/**
* This deviates from the WHATWG interface since ws doesn't support the
* required default "blob" type (instead we define a custom "nodebuffer"
* type).
*
* @type {String}
*/
get binaryType() {
return this._binaryType;
}
set binaryType(type) {
if (!BINARY_TYPES.includes(type)) return;
this._binaryType = type;
//
// Allow to change `binaryType` on the fly.
//
if (this._receiver) this._receiver._binaryType = type;
}
/**
* @type {Number}
*/
get bufferedAmount() {
if (!this._socket) return this._bufferedAmount;
//
// `socket.bufferSize` is `undefined` if the socket is closed.
//
return (this._socket.bufferSize || 0) + this._sender._bufferedBytes;
}
/**
* @type {String}
*/
get extensions() {
return Object.keys(this._extensions).join();
}
/**
* Set up the socket and the internal resources.
*
* @param {net.Socket} socket The network socket between the server and client
* @param {Buffer} head The first packet of the upgraded stream
* @param {Number} maxPayload The maximum allowed message size
* @private
*/
setSocket(socket, head, maxPayload) {
const receiver = new Receiver(
this._binaryType,
this._extensions,
maxPayload
);
this._sender = new Sender(socket, this._extensions);
this._receiver = receiver;
this._socket = socket;
receiver[kWebSocket] = this;
socket[kWebSocket] = this;
receiver.on('conclude', receiverOnConclude);
receiver.on('drain', receiverOnDrain);
receiver.on('error', receiverOnError);
receiver.on('message', receiverOnMessage);
receiver.on('ping', receiverOnPing);
receiver.on('pong', receiverOnPong);
socket.setTimeout(0);
socket.setNoDelay();
if (head.length > 0) socket.unshift(head);
socket.on('close', socketOnClose);
socket.on('data', socketOnData);
socket.on('end', socketOnEnd);
socket.on('error', socketOnError);
this.readyState = WebSocket.OPEN;
this.emit('open');
}
/**
* Emit the `'close'` event.
*
* @private
*/
emitClose() {
this.readyState = WebSocket.CLOSED;
if (!this._socket) {
this.emit('close', this._closeCode, this._closeMessage);
return;
}
if (this._extensions[PerMessageDeflate.extensionName]) {
this._extensions[PerMessageDeflate.extensionName].cleanup();
}
this._receiver.removeAllListeners();
this.emit('close', this._closeCode, this._closeMessage);
}
/**
* Start a closing handshake.
*
* +----------+ +-----------+ +----------+
* - - -|ws.close()|-->|close frame|-->|ws.close()|- - -
* | +----------+ +-----------+ +----------+ |
* +----------+ +-----------+ |
* CLOSING |ws.close()|<--|close frame|<--+-----+ CLOSING
* +----------+ +-----------+ |
* | | | +---+ |
* +------------------------+-->|fin| - - - -
* | +---+ | +---+
* - - - - -|fin|<---------------------+
* +---+
*
* @param {Number} code Status code explaining why the connection is closing
* @param {String} data A string explaining why the connection is closing
* @public
*/
close(code, data) {
if (this.readyState === WebSocket.CLOSED) return;
if (this.readyState === WebSocket.CONNECTING) {
const msg = 'WebSocket was closed before the connection was established';
return abortHandshake(this, this._req, msg);
}
if (this.readyState === WebSocket.CLOSING) {
if (this._closeFrameSent && this._closeFrameReceived) this._socket.end();
return;
}
this.readyState = WebSocket.CLOSING;
this._sender.close(code, data, !this._isServer, (err) => {
//
// This error is handled by the `'error'` listener on the socket. We only
// want to know if the close frame has been sent here.
//
if (err) return;
this._closeFrameSent = true;
if (this._closeFrameReceived) this._socket.end();
});
//
// Specify a timeout for the closing handshake to complete.
//
this._closeTimer = setTimeout(
this._socket.destroy.bind(this._socket),
closeTimeout
);
}
/**
* Send a ping.
*
* @param {*} data The data to send
* @param {Boolean} mask Indicates whether or not to mask `data`
* @param {Function} cb Callback which is executed when the ping is sent
* @public
*/
ping(data, mask, cb) {
if (this.readyState === WebSocket.CONNECTING) {
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
}
if (typeof data === 'function') {
cb = data;
data = mask = undefined;
} else if (typeof mask === 'function') {
cb = mask;
mask = undefined;
}
if (typeof data === 'number') data = data.toString();
if (this.readyState !== WebSocket.OPEN) {
sendAfterClose(this, data, cb);
return;
}
if (mask === undefined) mask = !this._isServer;
this._sender.ping(data || EMPTY_BUFFER, mask, cb);
}
/**
* Send a pong.
*
* @param {*} data The data to send
* @param {Boolean} mask Indicates whether or not to mask `data`
* @param {Function} cb Callback which is executed when the pong is sent
* @public
*/
pong(data, mask, cb) {
if (this.readyState === WebSocket.CONNECTING) {
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
}
if (typeof data === 'function') {
cb = data;
data = mask = undefined;
} else if (typeof mask === 'function') {
cb = mask;
mask = undefined;
}
if (typeof data === 'number') data = data.toString();
if (this.readyState !== WebSocket.OPEN) {
sendAfterClose(this, data, cb);
return;
}
if (mask === undefined) mask = !this._isServer;
this._sender.pong(data || EMPTY_BUFFER, mask, cb);
}
/**
* Send a data message.
*
* @param {*} data The message to send
* @param {Object} options Options object
* @param {Boolean} options.compress Specifies whether or not to compress
* `data`
* @param {Boolean} options.binary Specifies whether `data` is binary or text
* @param {Boolean} options.fin Specifies whether the fragment is the last one
* @param {Boolean} options.mask Specifies whether or not to mask `data`
* @param {Function} cb Callback which is executed when data is written out
* @public
*/
send(data, options, cb) {
if (this.readyState === WebSocket.CONNECTING) {
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
}
if (typeof options === 'function') {
cb = options;
options = {};
}
if (typeof data === 'number') data = data.toString();
if (this.readyState !== WebSocket.OPEN) {
sendAfterClose(this, data, cb);
return;
}
const opts = {
binary: typeof data !== 'string',
mask: !this._isServer,
compress: true,
fin: true,
...options
};
if (!this._extensions[PerMessageDeflate.extensionName]) {
opts.compress = false;
}
this._sender.send(data || EMPTY_BUFFER, opts, cb);
}
/**
* Forcibly close the connection.
*
* @public
*/
terminate() {
if (this.readyState === WebSocket.CLOSED) return;
if (this.readyState === WebSocket.CONNECTING) {
const msg = 'WebSocket was closed before the connection was established';
return abortHandshake(this, this._req, msg);
}
if (this._socket) {
this.readyState = WebSocket.CLOSING;
this._socket.destroy();
}
}
}
readyStates.forEach((readyState, i) => {
WebSocket[readyState] = i;
});
//
// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
//
['open', 'error', 'close', 'message'].forEach((method) => {
Object.defineProperty(WebSocket.prototype, `on${method}`, {
/**
* Return the listener of the event.
*
* @return {(Function|undefined)} The event listener or `undefined`
* @public
*/
get() {
const listeners = this.listeners(method);
for (let i = 0; i < listeners.length; i++) {
if (listeners[i]._listener) return listeners[i]._listener;
}
return undefined;
},
/**
* Add a listener for the event.
*
* @param {Function} listener The listener to add
* @public
*/
set(listener) {
const listeners = this.listeners(method);
for (let i = 0; i < listeners.length; i++) {
//
// Remove only the listeners added via `addEventListener`.
//
if (listeners[i]._listener) this.removeListener(method, listeners[i]);
}
this.addEventListener(method, listener);
}
});
});
WebSocket.prototype.addEventListener = addEventListener;
WebSocket.prototype.removeEventListener = removeEventListener;
module.exports = WebSocket;
/**
* Initialize a WebSocket client.
*
* @param {WebSocket} websocket The client to initialize
* @param {(String|url.URL)} address The URL to which to connect
* @param {String} protocols The subprotocols
* @param {Object} options Connection options
* @param {(Boolean|Object)} options.perMessageDeflate Enable/disable
* permessage-deflate
* @param {Number} options.handshakeTimeout Timeout in milliseconds for the
* handshake request
* @param {Number} options.protocolVersion Value of the `Sec-WebSocket-Version`
* header
* @param {String} options.origin Value of the `Origin` or
* `Sec-WebSocket-Origin` header
* @param {Number} options.maxPayload The maximum allowed message size
* @param {Boolean} options.followRedirects Whether or not to follow redirects
* @param {Number} options.maxRedirects The maximum number of redirects allowed
* @private
*/
function initAsClient(websocket, address, protocols, options) {
const opts = {
protocolVersion: protocolVersions[1],
maxPayload: 100 * 1024 * 1024,
perMessageDeflate: true,
followRedirects: false,
maxRedirects: 10,
...options,
createConnection: undefined,
socketPath: undefined,
hostname: undefined,
protocol: undefined,
timeout: undefined,
method: undefined,
auth: undefined,
host: undefined,
path: undefined,
port: undefined
};
if (!protocolVersions.includes(opts.protocolVersion)) {
throw new RangeError(
`Unsupported protocol version: ${opts.protocolVersion} ` +
`(supported versions: ${protocolVersions.join(', ')})`
);
}
let parsedUrl;
if (address instanceof URL) {
parsedUrl = address;
websocket.url = address.href;
} else {
parsedUrl = new URL(address);
websocket.url = address;
}
const isUnixSocket = parsedUrl.protocol === 'ws+unix:';
if (!parsedUrl.host && (!isUnixSocket || !parsedUrl.pathname)) {
throw new Error(`Invalid URL: ${websocket.url}`);
}
const isSecure =
parsedUrl.protocol === 'wss:' || parsedUrl.protocol === 'https:';
const defaultPort = isSecure ? 443 : 80;
const key = randomBytes(16).toString('base64');
const get = isSecure ? https.get : http.get;
let perMessageDeflate;
opts.createConnection = isSecure ? tlsConnect : netConnect;
opts.defaultPort = opts.defaultPort || defaultPort;
opts.port = parsedUrl.port || defaultPort;
opts.host = parsedUrl.hostname.startsWith('[')
? parsedUrl.hostname.slice(1, -1)
: parsedUrl.hostname;
opts.headers = {
'Sec-WebSocket-Version': opts.protocolVersion,
'Sec-WebSocket-Key': key,
Connection: 'Upgrade',
Upgrade: 'websocket',
...opts.headers
};
opts.path = parsedUrl.pathname + parsedUrl.search;
opts.timeout = opts.handshakeTimeout;
if (opts.perMessageDeflate) {
perMessageDeflate = new PerMessageDeflate(
opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
false,
opts.maxPayload
);
opts.headers['Sec-WebSocket-Extensions'] = format({
[PerMessageDeflate.extensionName]: perMessageDeflate.offer()
});
}
if (protocols) {
opts.headers['Sec-WebSocket-Protocol'] = protocols;
}
if (opts.origin) {
if (opts.protocolVersion < 13) {
opts.headers['Sec-WebSocket-Origin'] = opts.origin;
} else {
opts.headers.Origin = opts.origin;
}
}
if (parsedUrl.username || parsedUrl.password) {
opts.auth = `${parsedUrl.username}:${parsedUrl.password}`;
}
if (isUnixSocket) {
const parts = opts.path.split(':');
opts.socketPath = parts[0];
opts.path = parts[1];
}
let req = (websocket._req = get(opts));
if (opts.timeout) {
req.on('timeout', () => {
abortHandshake(websocket, req, 'Opening handshake has timed out');
});
}
req.on('error', (err) => {
if (websocket._req.aborted) return;
req = websocket._req = null;
websocket.readyState = WebSocket.CLOSING;
websocket.emit('error', err);
websocket.emitClose();
});
req.on('response', (res) => {
const location = res.headers.location;
const statusCode = res.statusCode;
if (
location &&
opts.followRedirects &&
statusCode >= 300 &&
statusCode < 400
) {
if (++websocket._redirects > opts.maxRedirects) {
abortHandshake(websocket, req, 'Maximum redirects exceeded');
return;
}
req.abort();
const addr = new URL(location, address);
initAsClient(websocket, addr, protocols, options);
} else if (!websocket.emit('unexpected-response', req, res)) {
abortHandshake(
websocket,
req,
`Unexpected server response: ${res.statusCode}`
);
}
});
req.on('upgrade', (res, socket, head) => {
websocket.emit('upgrade', res);
//
// The user may have closed the connection from a listener of the `upgrade`
// event.
//
if (websocket.readyState !== WebSocket.CONNECTING) return;
req = websocket._req = null;
const digest = createHash('sha1')
.update(key + GUID)
.digest('base64');
if (res.headers['sec-websocket-accept'] !== digest) {
abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');
return;
}
const serverProt = res.headers['sec-websocket-protocol'];
const protList = (protocols || '').split(/, */);
let protError;
if (!protocols && serverProt) {
protError = 'Server sent a subprotocol but none was requested';
} else if (protocols && !serverProt) {
protError = 'Server sent no subprotocol';
} else if (serverProt && !protList.includes(serverProt)) {
protError = 'Server sent an invalid subprotocol';
}
if (protError) {
abortHandshake(websocket, socket, protError);
return;
}
if (serverProt) websocket.protocol = serverProt;
if (perMessageDeflate) {
try {
const extensions = parse(res.headers['sec-websocket-extensions']);
if (extensions[PerMessageDeflate.extensionName]) {
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
websocket._extensions[
PerMessageDeflate.extensionName
] = perMessageDeflate;
}
} catch (err) {
abortHandshake(
websocket,
socket,
'Invalid Sec-WebSocket-Extensions header'
);
return;
}
}
websocket.setSocket(socket, head, opts.maxPayload);
});
}
/**
* Create a `net.Socket` and initiate a connection.
*
* @param {Object} options Connection options
* @return {net.Socket} The newly created socket used to start the connection
* @private
*/
function netConnect(options) {
options.path = options.socketPath;
return net.connect(options);
}
/**
* Create a `tls.TLSSocket` and initiate a connection.
*
* @param {Object} options Connection options
* @return {tls.TLSSocket} The newly created socket used to start the connection
* @private
*/
function tlsConnect(options) {
options.path = undefined;
if (!options.servername && options.servername !== '') {
options.servername = options.host;
}
return tls.connect(options);
}
/**
* Abort the handshake and emit an error.
*
* @param {WebSocket} websocket The WebSocket instance
* @param {(http.ClientRequest|net.Socket)} stream The request to abort or the
* socket to destroy
* @param {String} message The error message
* @private
*/
function abortHandshake(websocket, stream, message) {
websocket.readyState = WebSocket.CLOSING;
const err = new Error(message);
Error.captureStackTrace(err, abortHandshake);
if (stream.setHeader) {
stream.abort();
stream.once('abort', websocket.emitClose.bind(websocket));
websocket.emit('error', err);
} else {
stream.destroy(err);
stream.once('error', websocket.emit.bind(websocket, 'error'));
stream.once('close', websocket.emitClose.bind(websocket));
}
}
/**
* Handle cases where the `ping()`, `pong()`, or `send()` methods are called
* when the `readyState` attribute is `CLOSING` or `CLOSED`.
*
* @param {WebSocket} websocket The WebSocket instance
* @param {*} data The data to send
* @param {Function} cb Callback
* @private
*/
function sendAfterClose(websocket, data, cb) {
if (data) {
const length = toBuffer(data).length;
//
// The `_bufferedAmount` property is used only when the peer is a client and
// the opening handshake fails. Under these circumstances, in fact, the
// `setSocket()` method is not called, so the `_socket` and `_sender`
// properties are set to `null`.
//
if (websocket._socket) websocket._sender._bufferedBytes += length;
else websocket._bufferedAmount += length;
}
if (cb) {
const err = new Error(
`WebSocket is not open: readyState ${websocket.readyState} ` +
`(${readyStates[websocket.readyState]})`
);
cb(err);
}
}
/**
* The listener of the `Receiver` `'conclude'` event.
*
* @param {Number} code The status code
* @param {String} reason The reason for closing
* @private
*/
function receiverOnConclude(code, reason) {
const websocket = this[kWebSocket];
websocket._socket.removeListener('data', socketOnData);
websocket._socket.resume();
websocket._closeFrameReceived = true;
websocket._closeMessage = reason;
websocket._closeCode = code;
if (code === 1005) websocket.close();
else websocket.close(code, reason);
}
/**
* The listener of the `Receiver` `'drain'` event.
*
* @private
*/
function receiverOnDrain() {
this[kWebSocket]._socket.resume();
}
/**
* The listener of the `Receiver` `'error'` event.
*
* @param {(RangeError|Error)} err The emitted error
* @private
*/
function receiverOnError(err) {
const websocket = this[kWebSocket];
websocket._socket.removeListener('data', socketOnData);
websocket.readyState = WebSocket.CLOSING;
websocket._closeCode = err[kStatusCode];
websocket.emit('error', err);
websocket._socket.destroy();
}
/**
* The listener of the `Receiver` `'finish'` event.
*
* @private
*/
function receiverOnFinish() {
this[kWebSocket].emitClose();
}
/**
* The listener of the `Receiver` `'message'` event.
*
* @param {(String|Buffer|ArrayBuffer|Buffer[])} data The message
* @private
*/
function receiverOnMessage(data) {
this[kWebSocket].emit('message', data);
}
/**
* The listener of the `Receiver` `'ping'` event.
*
* @param {Buffer} data The data included in the ping frame
* @private
*/
function receiverOnPing(data) {
const websocket = this[kWebSocket];
websocket.pong(data, !websocket._isServer, NOOP);
websocket.emit('ping', data);
}
/**
* The listener of the `Receiver` `'pong'` event.
*
* @param {Buffer} data The data included in the pong frame
* @private
*/
function receiverOnPong(data) {
this[kWebSocket].emit('pong', data);
}
/**
* The listener of the `net.Socket` `'close'` event.
*
* @private
*/
function socketOnClose() {
const websocket = this[kWebSocket];
this.removeListener('close', socketOnClose);
this.removeListener('end', socketOnEnd);
websocket.readyState = WebSocket.CLOSING;
//
// The close frame might not have been received or the `'end'` event emitted,
// for example, if the socket was destroyed due to an error. Ensure that the
// `receiver` stream is closed after writing any remaining buffered data to
// it. If the readable side of the socket is in flowing mode then there is no
// buffered data as everything has been already written and `readable.read()`
// will return `null`. If instead, the socket is paused, any possible buffered
// data will be read as a single chunk and emitted synchronously in a single
// `'data'` event.
//
websocket._socket.read();
websocket._receiver.end();
this.removeListener('data', socketOnData);
this[kWebSocket] = undefined;
clearTimeout(websocket._closeTimer);
if (
websocket._receiver._writableState.finished ||
websocket._receiver._writableState.errorEmitted
) {
websocket.emitClose();
} else {
websocket._receiver.on('error', receiverOnFinish);
websocket._receiver.on('finish', receiverOnFinish);
}
}
/**
* The listener of the `net.Socket` `'data'` event.
*
* @param {Buffer} chunk A chunk of data
* @private
*/
function socketOnData(chunk) {
if (!this[kWebSocket]._receiver.write(chunk)) {
this.pause();
}
}
/**
* The listener of the `net.Socket` `'end'` event.
*
* @private
*/
function socketOnEnd() {
const websocket = this[kWebSocket];
websocket.readyState = WebSocket.CLOSING;
websocket._receiver.end();
this.end();
}
/**
* The listener of the `net.Socket` `'error'` event.
*
* @private
*/
function socketOnError() {
const websocket = this[kWebSocket];
this.removeListener('error', socketOnError);
this.on('error', NOOP);
websocket.readyState = WebSocket.CLOSING;
this.destroy();
}
/***/ }),
/* 5 */
/***/ (function(module, exports) {
module.exports = require("events");
/***/ }),
/* 6 */
/***/ (function(module, exports) {
module.exports = require("http");
/***/ }),
/* 7 */
/***/ (function(module, exports, __webpack_require__) {
"use strict";
const { Writable } = __webpack_require__(19);
const PerMessageDeflate = __webpack_require__(1);
const {
BINARY_TYPES,
EMPTY_BUFFER,
kStatusCode,
kWebSocket
} = __webpack_require__(0);
const { concat, toArrayBuffer, unmask } = __webpack_require__(2);
const { isValidStatusCode, isValidUTF8 } = __webpack_require__(8);
const GET_INFO = 0;
const GET_PAYLOAD_LENGTH_16 = 1;
const GET_PAYLOAD_LENGTH_64 = 2;
const GET_MASK = 3;
const GET_DATA = 4;
const INFLATING = 5;
/**
* HyBi Receiver implementation.
*
* @extends stream.Writable
*/
class Receiver extends Writable {
/**
* Creates a Receiver instance.
*
* @param {String} binaryType The type for binary data
* @param {Object} extensions An object containing the negotiated extensions
* @param {Number} maxPayload The maximum allowed message length
*/
constructor(binaryType, extensions, maxPayload) {
super();
this._binaryType = binaryType || BINARY_TYPES[0];
this[kWebSocket] = undefined;
this._extensions = extensions || {};
this._maxPayload = maxPayload | 0;
this._bufferedBytes = 0;
this._buffers = [];
this._compressed = false;
this._payloadLength = 0;
this._mask = undefined;
this._fragmented = 0;
this._masked = false;
this._fin = false;
this._opcode = 0;
this._totalPayloadLength = 0;
this._messageLength = 0;
this._fragments = [];
this._state = GET_INFO;
this._loop = false;
}
/**
* Implements `Writable.prototype._write()`.
*
* @param {Buffer} chunk The chunk of data to write
* @param {String} encoding The character encoding of `chunk`
* @param {Function} cb Callback
*/
_write(chunk, encoding, cb) {
if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
this._bufferedBytes += chunk.length;
this._buffers.push(chunk);
this.startLoop(cb);
}
/**
* Consumes `n` bytes from the buffered data.
*
* @param {Number} n The number of bytes to consume
* @return {Buffer} The consumed bytes
* @private
*/
consume(n) {
this._bufferedBytes -= n;
if (n === this._buffers[0].length) return this._buffers.shift();
if (n < this._buffers[0].length) {
const buf = this._buffers[0];
this._buffers[0] = buf.slice(n);
return buf.slice(0, n);
}
const dst = Buffer.allocUnsafe(n);
do {
const buf = this._buffers[0];
if (n >= buf.length) {
this._buffers.shift().copy(dst, dst.length - n);
} else {
buf.copy(dst, dst.length - n, 0, n);
this._buffers[0] = buf.slice(n);
}
n -= buf.length;
} while (n > 0);
return dst;
}
/**
* Starts the parsing loop.
*
* @param {Function} cb Callback
* @private
*/
startLoop(cb) {
let err;
this._loop = true;
do {
switch (this._state) {
case GET_INFO:
err = this.getInfo();
break;
case GET_PAYLOAD_LENGTH_16:
err = this.getPayloadLength16();
break;
case GET_PAYLOAD_LENGTH_64:
err = this.getPayloadLength64();
break;
case GET_MASK:
this.getMask();
break;
case GET_DATA:
err = this.getData(cb);
break;
default:
// `INFLATING`
this._loop = false;
return;
}
} while (this._loop);
cb(err);
}
/**
* Reads the first two bytes of a frame.
*
* @return {(RangeError|undefined)} A possible error
* @private
*/
getInfo() {