Spaces:
Runtime error
Runtime error
import require$$0 from "stream"; | |
import require$$0$1 from "zlib"; | |
import require$$0$2 from "buffer"; | |
import require$$3 from "net"; | |
import require$$4 from "tls"; | |
import require$$5 from "crypto"; | |
import require$$0$3 from "events"; | |
import require$$1$1 from "https"; | |
import require$$2 from "http"; | |
import require$$7 from "url"; | |
function getDefaultExportFromCjs(x) { | |
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x; | |
} | |
function getAugmentedNamespace(n) { | |
if (n.__esModule) | |
return n; | |
var f = n.default; | |
if (typeof f == "function") { | |
var a = function a2() { | |
if (this instanceof a2) { | |
var args = [null]; | |
args.push.apply(args, arguments); | |
var Ctor = Function.bind.apply(f, args); | |
return new Ctor(); | |
} | |
return f.apply(this, arguments); | |
}; | |
a.prototype = f.prototype; | |
} else | |
a = {}; | |
Object.defineProperty(a, "__esModule", { value: true }); | |
Object.keys(n).forEach(function(k) { | |
var d = Object.getOwnPropertyDescriptor(n, k); | |
Object.defineProperty(a, k, d.get ? d : { | |
enumerable: true, | |
get: function() { | |
return n[k]; | |
} | |
}); | |
}); | |
return a; | |
} | |
const { Duplex } = require$$0; | |
function emitClose$1(stream2) { | |
stream2.emit("close"); | |
} | |
function duplexOnEnd() { | |
if (!this.destroyed && this._writableState.finished) { | |
this.destroy(); | |
} | |
} | |
function duplexOnError(err) { | |
this.removeListener("error", duplexOnError); | |
this.destroy(); | |
if (this.listenerCount("error") === 0) { | |
this.emit("error", err); | |
} | |
} | |
function createWebSocketStream(ws, options) { | |
let terminateOnDestroy = true; | |
const duplex = new Duplex({ | |
...options, | |
autoDestroy: false, | |
emitClose: false, | |
objectMode: false, | |
writableObjectMode: false | |
}); | |
ws.on("message", function message(msg, isBinary) { | |
const data = !isBinary && duplex._readableState.objectMode ? msg.toString() : msg; | |
if (!duplex.push(data)) | |
ws.pause(); | |
}); | |
ws.once("error", function error2(err) { | |
if (duplex.destroyed) | |
return; | |
terminateOnDestroy = false; | |
duplex.destroy(err); | |
}); | |
ws.once("close", function close() { | |
if (duplex.destroyed) | |
return; | |
duplex.push(null); | |
}); | |
duplex._destroy = function(err, callback) { | |
if (ws.readyState === ws.CLOSED) { | |
callback(err); | |
process.nextTick(emitClose$1, duplex); | |
return; | |
} | |
let called = false; | |
ws.once("error", function error2(err2) { | |
called = true; | |
callback(err2); | |
}); | |
ws.once("close", function close() { | |
if (!called) | |
callback(err); | |
process.nextTick(emitClose$1, duplex); | |
}); | |
if (terminateOnDestroy) | |
ws.terminate(); | |
}; | |
duplex._final = function(callback) { | |
if (ws.readyState === ws.CONNECTING) { | |
ws.once("open", function open() { | |
duplex._final(callback); | |
}); | |
return; | |
} | |
if (ws._socket === null) | |
return; | |
if (ws._socket._writableState.finished) { | |
callback(); | |
if (duplex._readableState.endEmitted) | |
duplex.destroy(); | |
} else { | |
ws._socket.once("finish", function finish() { | |
callback(); | |
}); | |
ws.close(); | |
} | |
}; | |
duplex._read = function() { | |
if (ws.isPaused) | |
ws.resume(); | |
}; | |
duplex._write = function(chunk, encoding, callback) { | |
if (ws.readyState === ws.CONNECTING) { | |
ws.once("open", function open() { | |
duplex._write(chunk, encoding, callback); | |
}); | |
return; | |
} | |
ws.send(chunk, callback); | |
}; | |
duplex.on("end", duplexOnEnd); | |
duplex.on("error", duplexOnError); | |
return duplex; | |
} | |
var stream = createWebSocketStream; | |
const stream$1 = /* @__PURE__ */ getDefaultExportFromCjs(stream); | |
var bufferUtil$1 = { exports: {} }; | |
var constants = { | |
BINARY_TYPES: ["nodebuffer", "arraybuffer", "fragments"], | |
EMPTY_BUFFER: Buffer.alloc(0), | |
GUID: "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", | |
kForOnEventAttribute: Symbol("kIsForOnEventAttribute"), | |
kListener: Symbol("kListener"), | |
kStatusCode: Symbol("status-code"), | |
kWebSocket: Symbol("websocket"), | |
NOOP: () => { | |
} | |
}; | |
var unmask$1; | |
var mask; | |
const { EMPTY_BUFFER: EMPTY_BUFFER$3 } = constants; | |
const FastBuffer$2 = Buffer[Symbol.species]; | |
function concat$1(list, totalLength) { | |
if (list.length === 0) | |
return EMPTY_BUFFER$3; | |
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]; | |
target.set(buf, offset); | |
offset += buf.length; | |
} | |
if (offset < totalLength) { | |
return new FastBuffer$2(target.buffer, target.byteOffset, offset); | |
} | |
return target; | |
} | |
function _mask(source, mask2, output, offset, length) { | |
for (let i = 0; i < length; i++) { | |
output[offset + i] = source[i] ^ mask2[i & 3]; | |
} | |
} | |
function _unmask(buffer, mask2) { | |
for (let i = 0; i < buffer.length; i++) { | |
buffer[i] ^= mask2[i & 3]; | |
} | |
} | |
function toArrayBuffer$1(buf) { | |
if (buf.length === buf.buffer.byteLength) { | |
return buf.buffer; | |
} | |
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length); | |
} | |
function toBuffer$2(data) { | |
toBuffer$2.readOnly = true; | |
if (Buffer.isBuffer(data)) | |
return data; | |
let buf; | |
if (data instanceof ArrayBuffer) { | |
buf = new FastBuffer$2(data); | |
} else if (ArrayBuffer.isView(data)) { | |
buf = new FastBuffer$2(data.buffer, data.byteOffset, data.byteLength); | |
} else { | |
buf = Buffer.from(data); | |
toBuffer$2.readOnly = false; | |
} | |
return buf; | |
} | |
bufferUtil$1.exports = { | |
concat: concat$1, | |
mask: _mask, | |
toArrayBuffer: toArrayBuffer$1, | |
toBuffer: toBuffer$2, | |
unmask: _unmask | |
}; | |
if (!process.env.WS_NO_BUFFER_UTIL) { | |
try { | |
const bufferUtil2 = require("bufferutil"); | |
mask = bufferUtil$1.exports.mask = function(source, mask2, output, offset, length) { | |
if (length < 48) | |
_mask(source, mask2, output, offset, length); | |
else | |
bufferUtil2.mask(source, mask2, output, offset, length); | |
}; | |
unmask$1 = bufferUtil$1.exports.unmask = function(buffer, mask2) { | |
if (buffer.length < 32) | |
_unmask(buffer, mask2); | |
else | |
bufferUtil2.unmask(buffer, mask2); | |
}; | |
} catch (e) { | |
} | |
} | |
var bufferUtilExports = bufferUtil$1.exports; | |
const kDone = Symbol("kDone"); | |
const kRun = Symbol("kRun"); | |
let Limiter$1 = class Limiter { | |
/** | |
* Creates a new `Limiter`. | |
* | |
* @param {Number} [concurrency=Infinity] The maximum number of jobs allowed | |
* to run concurrently | |
*/ | |
constructor(concurrency) { | |
this[kDone] = () => { | |
this.pending--; | |
this[kRun](); | |
}; | |
this.concurrency = concurrency || Infinity; | |
this.jobs = []; | |
this.pending = 0; | |
} | |
/** | |
* Adds a job to the queue. | |
* | |
* @param {Function} job The job to run | |
* @public | |
*/ | |
add(job) { | |
this.jobs.push(job); | |
this[kRun](); | |
} | |
/** | |
* Removes a job from the queue and runs it if possible. | |
* | |
* @private | |
*/ | |
[kRun]() { | |
if (this.pending === this.concurrency) | |
return; | |
if (this.jobs.length) { | |
const job = this.jobs.shift(); | |
this.pending++; | |
job(this[kDone]); | |
} | |
} | |
}; | |
var limiter = Limiter$1; | |
const zlib = require$$0$1; | |
const bufferUtil = bufferUtilExports; | |
const Limiter2 = limiter; | |
const { kStatusCode: kStatusCode$2 } = constants; | |
const FastBuffer$1 = Buffer[Symbol.species]; | |
const TRAILER = Buffer.from([0, 0, 255, 255]); | |
const kPerMessageDeflate = Symbol("permessage-deflate"); | |
const kTotalLength = Symbol("total-length"); | |
const kCallback = Symbol("callback"); | |
const kBuffers = Symbol("buffers"); | |
const kError$1 = Symbol("error"); | |
let zlibLimiter; | |
let PerMessageDeflate$4 = class PerMessageDeflate { | |
/** | |
* Creates a PerMessageDeflate instance. | |
* | |
* @param {Object} [options] Configuration options | |
* @param {(Boolean|Number)} [options.clientMaxWindowBits] Advertise support | |
* for, or request, a custom client window size | |
* @param {Boolean} [options.clientNoContextTakeover=false] Advertise/ | |
* acknowledge disabling of client context takeover | |
* @param {Number} [options.concurrencyLimit=10] The number of concurrent | |
* calls to zlib | |
* @param {(Boolean|Number)} [options.serverMaxWindowBits] Request/confirm the | |
* use of a custom server window size | |
* @param {Boolean} [options.serverNoContextTakeover=false] Request/accept | |
* disabling of server context takeover | |
* @param {Number} [options.threshold=1024] Size (in bytes) below which | |
* messages should not be compressed if context takeover is disabled | |
* @param {Object} [options.zlibDeflateOptions] Options to pass to zlib on | |
* deflate | |
* @param {Object} [options.zlibInflateOptions] Options to pass to zlib on | |
* inflate | |
* @param {Boolean} [isServer=false] Create the instance in either server or | |
* client mode | |
* @param {Number} [maxPayload=0] The maximum allowed message length | |
*/ | |
constructor(options, isServer, maxPayload) { | |
this._maxPayload = maxPayload | 0; | |
this._options = options || {}; | |
this._threshold = this._options.threshold !== void 0 ? this._options.threshold : 1024; | |
this._isServer = !!isServer; | |
this._deflate = null; | |
this._inflate = null; | |
this.params = null; | |
if (!zlibLimiter) { | |
const concurrency = this._options.concurrencyLimit !== void 0 ? this._options.concurrencyLimit : 10; | |
zlibLimiter = new Limiter2(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) { | |
const callback = this._deflate[kCallback]; | |
this._deflate.close(); | |
this._deflate = null; | |
if (callback) { | |
callback( | |
new Error( | |
"The deflate stream was closed while data was being processed" | |
) | |
); | |
} | |
} | |
} | |
/** | |
* 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. | |
* | |
* @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.add((done) => { | |
this._decompress(data, fin, (err, result) => { | |
done(); | |
callback(err, result); | |
}); | |
}); | |
} | |
/** | |
* Compress data. Concurrency limited. | |
* | |
* @param {(Buffer|String)} 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.add((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$1]; | |
if (err) { | |
this._inflate.close(); | |
this._inflate = null; | |
callback(err); | |
return; | |
} | |
const data2 = bufferUtil.concat( | |
this._inflate[kBuffers], | |
this._inflate[kTotalLength] | |
); | |
if (this._inflate._readableState.endEmitted) { | |
this._inflate.close(); | |
this._inflate = null; | |
} else { | |
this._inflate[kTotalLength] = 0; | |
this._inflate[kBuffers] = []; | |
if (fin && this.params[`${endpoint}_no_context_takeover`]) { | |
this._inflate.reset(); | |
} | |
} | |
callback(null, data2); | |
}); | |
} | |
/** | |
* Compress data. | |
* | |
* @param {(Buffer|String)} 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) { | |
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] = []; | |
this._deflate.on("data", deflateOnData); | |
} | |
this._deflate[kCallback] = callback; | |
this._deflate.write(data); | |
this._deflate.flush(zlib.Z_SYNC_FLUSH, () => { | |
if (!this._deflate) { | |
return; | |
} | |
let data2 = bufferUtil.concat( | |
this._deflate[kBuffers], | |
this._deflate[kTotalLength] | |
); | |
if (fin) { | |
data2 = new FastBuffer$1(data2.buffer, data2.byteOffset, data2.length - 4); | |
} | |
this._deflate[kCallback] = null; | |
this._deflate[kTotalLength] = 0; | |
this._deflate[kBuffers] = []; | |
if (fin && this.params[`${endpoint}_no_context_takeover`]) { | |
this._deflate.reset(); | |
} | |
callback(null, data2); | |
}); | |
} | |
}; | |
var permessageDeflate = PerMessageDeflate$4; | |
function deflateOnData(chunk) { | |
this[kBuffers].push(chunk); | |
this[kTotalLength] += chunk.length; | |
} | |
function inflateOnData(chunk) { | |
this[kTotalLength] += chunk.length; | |
if (this[kPerMessageDeflate]._maxPayload < 1 || this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload) { | |
this[kBuffers].push(chunk); | |
return; | |
} | |
this[kError$1] = new RangeError("Max payload size exceeded"); | |
this[kError$1].code = "WS_ERR_UNSUPPORTED_MESSAGE_LENGTH"; | |
this[kError$1][kStatusCode$2] = 1009; | |
this.removeListener("data", inflateOnData); | |
this.reset(); | |
} | |
function inflateOnError(err) { | |
this[kPerMessageDeflate]._inflate = null; | |
err[kStatusCode$2] = 1007; | |
this[kCallback](err); | |
} | |
var validation = { exports: {} }; | |
const __viteOptionalPeerDep_utf8Validate_ws = {}; | |
const __viteOptionalPeerDep_utf8Validate_ws$1 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ | |
__proto__: null, | |
default: __viteOptionalPeerDep_utf8Validate_ws | |
}, Symbol.toStringTag, { value: "Module" })); | |
const require$$1 = /* @__PURE__ */ getAugmentedNamespace(__viteOptionalPeerDep_utf8Validate_ws$1); | |
var isValidUTF8_1; | |
const { isUtf8 } = require$$0$2; | |
const tokenChars$2 = [ | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
// 0 - 15 | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
// 16 - 31 | |
0, | |
1, | |
0, | |
1, | |
1, | |
1, | |
1, | |
1, | |
0, | |
0, | |
1, | |
1, | |
0, | |
1, | |
1, | |
0, | |
// 32 - 47 | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
0, | |
0, | |
0, | |
0, | |
0, | |
0, | |
// 48 - 63 | |
0, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
// 64 - 79 | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
0, | |
0, | |
0, | |
1, | |
1, | |
// 80 - 95 | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
// 96 - 111 | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
1, | |
0, | |
1, | |
0, | |
1, | |
0 | |
// 112 - 127 | |
]; | |
function isValidStatusCode$2(code) { | |
return code >= 1e3 && code <= 1014 && code !== 1004 && code !== 1005 && code !== 1006 || code >= 3e3 && code <= 4999; | |
} | |
function _isValidUTF8(buf) { | |
const len = buf.length; | |
let i = 0; | |
while (i < len) { | |
if ((buf[i] & 128) === 0) { | |
i++; | |
} else if ((buf[i] & 224) === 192) { | |
if (i + 1 === len || (buf[i + 1] & 192) !== 128 || (buf[i] & 254) === 192) { | |
return false; | |
} | |
i += 2; | |
} else if ((buf[i] & 240) === 224) { | |
if (i + 2 >= len || (buf[i + 1] & 192) !== 128 || (buf[i + 2] & 192) !== 128 || buf[i] === 224 && (buf[i + 1] & 224) === 128 || // Overlong | |
buf[i] === 237 && (buf[i + 1] & 224) === 160) { | |
return false; | |
} | |
i += 3; | |
} else if ((buf[i] & 248) === 240) { | |
if (i + 3 >= len || (buf[i + 1] & 192) !== 128 || (buf[i + 2] & 192) !== 128 || (buf[i + 3] & 192) !== 128 || buf[i] === 240 && (buf[i + 1] & 240) === 128 || // Overlong | |
buf[i] === 244 && buf[i + 1] > 143 || buf[i] > 244) { | |
return false; | |
} | |
i += 4; | |
} else { | |
return false; | |
} | |
} | |
return true; | |
} | |
validation.exports = { | |
isValidStatusCode: isValidStatusCode$2, | |
isValidUTF8: _isValidUTF8, | |
tokenChars: tokenChars$2 | |
}; | |
if (isUtf8) { | |
isValidUTF8_1 = validation.exports.isValidUTF8 = function(buf) { | |
return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf); | |
}; | |
} else if (!process.env.WS_NO_UTF_8_VALIDATE) { | |
try { | |
const isValidUTF82 = require$$1; | |
isValidUTF8_1 = validation.exports.isValidUTF8 = function(buf) { | |
return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF82(buf); | |
}; | |
} catch (e) { | |
} | |
} | |
var validationExports = validation.exports; | |
const { Writable } = require$$0; | |
const PerMessageDeflate$3 = permessageDeflate; | |
const { | |
BINARY_TYPES: BINARY_TYPES$1, | |
EMPTY_BUFFER: EMPTY_BUFFER$2, | |
kStatusCode: kStatusCode$1, | |
kWebSocket: kWebSocket$2 | |
} = constants; | |
const { concat, toArrayBuffer, unmask } = bufferUtilExports; | |
const { isValidStatusCode: isValidStatusCode$1, isValidUTF8 } = validationExports; | |
const FastBuffer = Buffer[Symbol.species]; | |
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; | |
let Receiver$1 = class Receiver extends Writable { | |
/** | |
* Creates a Receiver instance. | |
* | |
* @param {Object} [options] Options object | |
* @param {String} [options.binaryType=nodebuffer] The type for binary data | |
* @param {Object} [options.extensions] An object containing the negotiated | |
* extensions | |
* @param {Boolean} [options.isServer=false] Specifies whether to operate in | |
* client or server mode | |
* @param {Number} [options.maxPayload=0] The maximum allowed message length | |
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or | |
* not to skip UTF-8 validation for text and close messages | |
*/ | |
constructor(options = {}) { | |
super(); | |
this._binaryType = options.binaryType || BINARY_TYPES$1[0]; | |
this._extensions = options.extensions || {}; | |
this._isServer = !!options.isServer; | |
this._maxPayload = options.maxPayload | 0; | |
this._skipUTF8Validation = !!options.skipUTF8Validation; | |
this[kWebSocket$2] = void 0; | |
this._bufferedBytes = 0; | |
this._buffers = []; | |
this._compressed = false; | |
this._payloadLength = 0; | |
this._mask = void 0; | |
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 | |
* @private | |
*/ | |
_write(chunk, encoding, cb) { | |
if (this._opcode === 8 && 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] = new FastBuffer( | |
buf.buffer, | |
buf.byteOffset + n, | |
buf.length - n | |
); | |
return new FastBuffer(buf.buffer, buf.byteOffset, n); | |
} | |
const dst = Buffer.allocUnsafe(n); | |
do { | |
const buf = this._buffers[0]; | |
const offset = dst.length - n; | |
if (n >= buf.length) { | |
dst.set(this._buffers.shift(), offset); | |
} else { | |
dst.set(new Uint8Array(buf.buffer, buf.byteOffset, n), offset); | |
this._buffers[0] = new FastBuffer( | |
buf.buffer, | |
buf.byteOffset + n, | |
buf.length - 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: | |
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() { | |
if (this._bufferedBytes < 2) { | |
this._loop = false; | |
return; | |
} | |
const buf = this.consume(2); | |
if ((buf[0] & 48) !== 0) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"RSV2 and RSV3 must be clear", | |
true, | |
1002, | |
"WS_ERR_UNEXPECTED_RSV_2_3" | |
); | |
} | |
const compressed = (buf[0] & 64) === 64; | |
if (compressed && !this._extensions[PerMessageDeflate$3.extensionName]) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"RSV1 must be clear", | |
true, | |
1002, | |
"WS_ERR_UNEXPECTED_RSV_1" | |
); | |
} | |
this._fin = (buf[0] & 128) === 128; | |
this._opcode = buf[0] & 15; | |
this._payloadLength = buf[1] & 127; | |
if (this._opcode === 0) { | |
if (compressed) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"RSV1 must be clear", | |
true, | |
1002, | |
"WS_ERR_UNEXPECTED_RSV_1" | |
); | |
} | |
if (!this._fragmented) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"invalid opcode 0", | |
true, | |
1002, | |
"WS_ERR_INVALID_OPCODE" | |
); | |
} | |
this._opcode = this._fragmented; | |
} else if (this._opcode === 1 || this._opcode === 2) { | |
if (this._fragmented) { | |
this._loop = false; | |
return error( | |
RangeError, | |
`invalid opcode ${this._opcode}`, | |
true, | |
1002, | |
"WS_ERR_INVALID_OPCODE" | |
); | |
} | |
this._compressed = compressed; | |
} else if (this._opcode > 7 && this._opcode < 11) { | |
if (!this._fin) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"FIN must be set", | |
true, | |
1002, | |
"WS_ERR_EXPECTED_FIN" | |
); | |
} | |
if (compressed) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"RSV1 must be clear", | |
true, | |
1002, | |
"WS_ERR_UNEXPECTED_RSV_1" | |
); | |
} | |
if (this._payloadLength > 125 || this._opcode === 8 && this._payloadLength === 1) { | |
this._loop = false; | |
return error( | |
RangeError, | |
`invalid payload length ${this._payloadLength}`, | |
true, | |
1002, | |
"WS_ERR_INVALID_CONTROL_PAYLOAD_LENGTH" | |
); | |
} | |
} else { | |
this._loop = false; | |
return error( | |
RangeError, | |
`invalid opcode ${this._opcode}`, | |
true, | |
1002, | |
"WS_ERR_INVALID_OPCODE" | |
); | |
} | |
if (!this._fin && !this._fragmented) | |
this._fragmented = this._opcode; | |
this._masked = (buf[1] & 128) === 128; | |
if (this._isServer) { | |
if (!this._masked) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"MASK must be set", | |
true, | |
1002, | |
"WS_ERR_EXPECTED_MASK" | |
); | |
} | |
} else if (this._masked) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"MASK must be clear", | |
true, | |
1002, | |
"WS_ERR_UNEXPECTED_MASK" | |
); | |
} | |
if (this._payloadLength === 126) | |
this._state = GET_PAYLOAD_LENGTH_16; | |
else if (this._payloadLength === 127) | |
this._state = GET_PAYLOAD_LENGTH_64; | |
else | |
return this.haveLength(); | |
} | |
/** | |
* Gets extended payload length (7+16). | |
* | |
* @return {(RangeError|undefined)} A possible error | |
* @private | |
*/ | |
getPayloadLength16() { | |
if (this._bufferedBytes < 2) { | |
this._loop = false; | |
return; | |
} | |
this._payloadLength = this.consume(2).readUInt16BE(0); | |
return this.haveLength(); | |
} | |
/** | |
* Gets extended payload length (7+64). | |
* | |
* @return {(RangeError|undefined)} A possible error | |
* @private | |
*/ | |
getPayloadLength64() { | |
if (this._bufferedBytes < 8) { | |
this._loop = false; | |
return; | |
} | |
const buf = this.consume(8); | |
const num = buf.readUInt32BE(0); | |
if (num > Math.pow(2, 53 - 32) - 1) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"Unsupported WebSocket frame: payload length > 2^53 - 1", | |
false, | |
1009, | |
"WS_ERR_UNSUPPORTED_DATA_PAYLOAD_LENGTH" | |
); | |
} | |
this._payloadLength = num * Math.pow(2, 32) + buf.readUInt32BE(4); | |
return this.haveLength(); | |
} | |
/** | |
* Payload length has been read. | |
* | |
* @return {(RangeError|undefined)} A possible error | |
* @private | |
*/ | |
haveLength() { | |
if (this._payloadLength && this._opcode < 8) { | |
this._totalPayloadLength += this._payloadLength; | |
if (this._totalPayloadLength > this._maxPayload && this._maxPayload > 0) { | |
this._loop = false; | |
return error( | |
RangeError, | |
"Max payload size exceeded", | |
false, | |
1009, | |
"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH" | |
); | |
} | |
} | |
if (this._masked) | |
this._state = GET_MASK; | |
else | |
this._state = GET_DATA; | |
} | |
/** | |
* Reads mask bytes. | |
* | |
* @private | |
*/ | |
getMask() { | |
if (this._bufferedBytes < 4) { | |
this._loop = false; | |
return; | |
} | |
this._mask = this.consume(4); | |
this._state = GET_DATA; | |
} | |
/** | |
* Reads data bytes. | |
* | |
* @param {Function} cb Callback | |
* @return {(Error|RangeError|undefined)} A possible error | |
* @private | |
*/ | |
getData(cb) { | |
let data = EMPTY_BUFFER$2; | |
if (this._payloadLength) { | |
if (this._bufferedBytes < this._payloadLength) { | |
this._loop = false; | |
return; | |
} | |
data = this.consume(this._payloadLength); | |
if (this._masked && (this._mask[0] | this._mask[1] | this._mask[2] | this._mask[3]) !== 0) { | |
unmask(data, this._mask); | |
} | |
} | |
if (this._opcode > 7) | |
return this.controlMessage(data); | |
if (this._compressed) { | |
this._state = INFLATING; | |
this.decompress(data, cb); | |
return; | |
} | |
if (data.length) { | |
this._messageLength = this._totalPayloadLength; | |
this._fragments.push(data); | |
} | |
return this.dataMessage(); | |
} | |
/** | |
* Decompresses data. | |
* | |
* @param {Buffer} data Compressed data | |
* @param {Function} cb Callback | |
* @private | |
*/ | |
decompress(data, cb) { | |
const perMessageDeflate = this._extensions[PerMessageDeflate$3.extensionName]; | |
perMessageDeflate.decompress(data, this._fin, (err, buf) => { | |
if (err) | |
return cb(err); | |
if (buf.length) { | |
this._messageLength += buf.length; | |
if (this._messageLength > this._maxPayload && this._maxPayload > 0) { | |
return cb( | |
error( | |
RangeError, | |
"Max payload size exceeded", | |
false, | |
1009, | |
"WS_ERR_UNSUPPORTED_MESSAGE_LENGTH" | |
) | |
); | |
} | |
this._fragments.push(buf); | |
} | |
const er = this.dataMessage(); | |
if (er) | |
return cb(er); | |
this.startLoop(cb); | |
}); | |
} | |
/** | |
* Handles a data message. | |
* | |
* @return {(Error|undefined)} A possible error | |
* @private | |
*/ | |
dataMessage() { | |
if (this._fin) { | |
const messageLength = this._messageLength; | |
const fragments = this._fragments; | |
this._totalPayloadLength = 0; | |
this._messageLength = 0; | |
this._fragmented = 0; | |
this._fragments = []; | |
if (this._opcode === 2) { | |
let data; | |
if (this._binaryType === "nodebuffer") { | |
data = concat(fragments, messageLength); | |
} else if (this._binaryType === "arraybuffer") { | |
data = toArrayBuffer(concat(fragments, messageLength)); | |
} else { | |
data = fragments; | |
} | |
this.emit("message", data, true); | |
} else { | |
const buf = concat(fragments, messageLength); | |
if (!this._skipUTF8Validation && !isValidUTF8(buf)) { | |
this._loop = false; | |
return error( | |
Error, | |
"invalid UTF-8 sequence", | |
true, | |
1007, | |
"WS_ERR_INVALID_UTF8" | |
); | |
} | |
this.emit("message", buf, false); | |
} | |
} | |
this._state = GET_INFO; | |
} | |
/** | |
* Handles a control message. | |
* | |
* @param {Buffer} data Data to handle | |
* @return {(Error|RangeError|undefined)} A possible error | |
* @private | |
*/ | |
controlMessage(data) { | |
if (this._opcode === 8) { | |
this._loop = false; | |
if (data.length === 0) { | |
this.emit("conclude", 1005, EMPTY_BUFFER$2); | |
this.end(); | |
} else { | |
const code = data.readUInt16BE(0); | |
if (!isValidStatusCode$1(code)) { | |
return error( | |
RangeError, | |
`invalid status code ${code}`, | |
true, | |
1002, | |
"WS_ERR_INVALID_CLOSE_CODE" | |
); | |
} | |
const buf = new FastBuffer( | |
data.buffer, | |
data.byteOffset + 2, | |
data.length - 2 | |
); | |
if (!this._skipUTF8Validation && !isValidUTF8(buf)) { | |
return error( | |
Error, | |
"invalid UTF-8 sequence", | |
true, | |
1007, | |
"WS_ERR_INVALID_UTF8" | |
); | |
} | |
this.emit("conclude", code, buf); | |
this.end(); | |
} | |
} else if (this._opcode === 9) { | |
this.emit("ping", data); | |
} else { | |
this.emit("pong", data); | |
} | |
this._state = GET_INFO; | |
} | |
}; | |
var receiver = Receiver$1; | |
function error(ErrorCtor, message, prefix, statusCode, errorCode) { | |
const err = new ErrorCtor( | |
prefix ? `Invalid WebSocket frame: ${message}` : message | |
); | |
Error.captureStackTrace(err, error); | |
err.code = errorCode; | |
err[kStatusCode$1] = statusCode; | |
return err; | |
} | |
const receiver$1 = /* @__PURE__ */ getDefaultExportFromCjs(receiver); | |
const { randomFillSync } = require$$5; | |
const PerMessageDeflate$2 = permessageDeflate; | |
const { EMPTY_BUFFER: EMPTY_BUFFER$1 } = constants; | |
const { isValidStatusCode } = validationExports; | |
const { mask: applyMask, toBuffer: toBuffer$1 } = bufferUtilExports; | |
const kByteLength = Symbol("kByteLength"); | |
const maskBuffer = Buffer.alloc(4); | |
let Sender$1 = class Sender { | |
/** | |
* Creates a Sender instance. | |
* | |
* @param {(net.Socket|tls.Socket)} socket The connection socket | |
* @param {Object} [extensions] An object containing the negotiated extensions | |
* @param {Function} [generateMask] The function used to generate the masking | |
* key | |
*/ | |
constructor(socket, extensions, generateMask) { | |
this._extensions = extensions || {}; | |
if (generateMask) { | |
this._generateMask = generateMask; | |
this._maskBuffer = Buffer.alloc(4); | |
} | |
this._socket = socket; | |
this._firstFragment = true; | |
this._compress = false; | |
this._bufferedBytes = 0; | |
this._deflating = false; | |
this._queue = []; | |
} | |
/** | |
* Frames a piece of data according to the HyBi WebSocket protocol. | |
* | |
* @param {(Buffer|String)} data The data to frame | |
* @param {Object} options Options object | |
* @param {Boolean} [options.fin=false] Specifies whether or not to set the | |
* FIN bit | |
* @param {Function} [options.generateMask] The function used to generate the | |
* masking key | |
* @param {Boolean} [options.mask=false] Specifies whether or not to mask | |
* `data` | |
* @param {Buffer} [options.maskBuffer] The buffer used to store the masking | |
* key | |
* @param {Number} options.opcode The opcode | |
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be | |
* modified | |
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the | |
* RSV1 bit | |
* @return {(Buffer|String)[]} The framed data | |
* @public | |
*/ | |
static frame(data, options) { | |
let mask2; | |
let merge = false; | |
let offset = 2; | |
let skipMasking = false; | |
if (options.mask) { | |
mask2 = options.maskBuffer || maskBuffer; | |
if (options.generateMask) { | |
options.generateMask(mask2); | |
} else { | |
randomFillSync(mask2, 0, 4); | |
} | |
skipMasking = (mask2[0] | mask2[1] | mask2[2] | mask2[3]) === 0; | |
offset = 6; | |
} | |
let dataLength; | |
if (typeof data === "string") { | |
if ((!options.mask || skipMasking) && options[kByteLength] !== void 0) { | |
dataLength = options[kByteLength]; | |
} else { | |
data = Buffer.from(data); | |
dataLength = data.length; | |
} | |
} else { | |
dataLength = data.length; | |
merge = options.mask && options.readOnly && !skipMasking; | |
} | |
let payloadLength = dataLength; | |
if (dataLength >= 65536) { | |
offset += 8; | |
payloadLength = 127; | |
} else if (dataLength > 125) { | |
offset += 2; | |
payloadLength = 126; | |
} | |
const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset); | |
target[0] = options.fin ? options.opcode | 128 : options.opcode; | |
if (options.rsv1) | |
target[0] |= 64; | |
target[1] = payloadLength; | |
if (payloadLength === 126) { | |
target.writeUInt16BE(dataLength, 2); | |
} else if (payloadLength === 127) { | |
target[2] = target[3] = 0; | |
target.writeUIntBE(dataLength, 4, 6); | |
} | |
if (!options.mask) | |
return [target, data]; | |
target[1] |= 128; | |
target[offset - 4] = mask2[0]; | |
target[offset - 3] = mask2[1]; | |
target[offset - 2] = mask2[2]; | |
target[offset - 1] = mask2[3]; | |
if (skipMasking) | |
return [target, data]; | |
if (merge) { | |
applyMask(data, mask2, target, offset, dataLength); | |
return [target]; | |
} | |
applyMask(data, mask2, data, 0, dataLength); | |
return [target, data]; | |
} | |
/** | |
* Sends a close message to the other peer. | |
* | |
* @param {Number} [code] The status code component of the body | |
* @param {(String|Buffer)} [data] The message component of the body | |
* @param {Boolean} [mask=false] Specifies whether or not to mask the message | |
* @param {Function} [cb] Callback | |
* @public | |
*/ | |
close(code, data, mask2, cb) { | |
let buf; | |
if (code === void 0) { | |
buf = EMPTY_BUFFER$1; | |
} else if (typeof code !== "number" || !isValidStatusCode(code)) { | |
throw new TypeError("First argument must be a valid error code number"); | |
} else if (data === void 0 || !data.length) { | |
buf = Buffer.allocUnsafe(2); | |
buf.writeUInt16BE(code, 0); | |
} else { | |
const length = Buffer.byteLength(data); | |
if (length > 123) { | |
throw new RangeError("The message must not be greater than 123 bytes"); | |
} | |
buf = Buffer.allocUnsafe(2 + length); | |
buf.writeUInt16BE(code, 0); | |
if (typeof data === "string") { | |
buf.write(data, 2); | |
} else { | |
buf.set(data, 2); | |
} | |
} | |
const options = { | |
[kByteLength]: buf.length, | |
fin: true, | |
generateMask: this._generateMask, | |
mask: mask2, | |
maskBuffer: this._maskBuffer, | |
opcode: 8, | |
readOnly: false, | |
rsv1: false | |
}; | |
if (this._deflating) { | |
this.enqueue([this.dispatch, buf, false, options, cb]); | |
} else { | |
this.sendFrame(Sender.frame(buf, options), cb); | |
} | |
} | |
/** | |
* Sends a ping message to the other peer. | |
* | |
* @param {*} data The message to send | |
* @param {Boolean} [mask=false] Specifies whether or not to mask `data` | |
* @param {Function} [cb] Callback | |
* @public | |
*/ | |
ping(data, mask2, cb) { | |
let byteLength; | |
let readOnly; | |
if (typeof data === "string") { | |
byteLength = Buffer.byteLength(data); | |
readOnly = false; | |
} else { | |
data = toBuffer$1(data); | |
byteLength = data.length; | |
readOnly = toBuffer$1.readOnly; | |
} | |
if (byteLength > 125) { | |
throw new RangeError("The data size must not be greater than 125 bytes"); | |
} | |
const options = { | |
[kByteLength]: byteLength, | |
fin: true, | |
generateMask: this._generateMask, | |
mask: mask2, | |
maskBuffer: this._maskBuffer, | |
opcode: 9, | |
readOnly, | |
rsv1: false | |
}; | |
if (this._deflating) { | |
this.enqueue([this.dispatch, data, false, options, cb]); | |
} else { | |
this.sendFrame(Sender.frame(data, options), cb); | |
} | |
} | |
/** | |
* Sends a pong message to the other peer. | |
* | |
* @param {*} data The message to send | |
* @param {Boolean} [mask=false] Specifies whether or not to mask `data` | |
* @param {Function} [cb] Callback | |
* @public | |
*/ | |
pong(data, mask2, cb) { | |
let byteLength; | |
let readOnly; | |
if (typeof data === "string") { | |
byteLength = Buffer.byteLength(data); | |
readOnly = false; | |
} else { | |
data = toBuffer$1(data); | |
byteLength = data.length; | |
readOnly = toBuffer$1.readOnly; | |
} | |
if (byteLength > 125) { | |
throw new RangeError("The data size must not be greater than 125 bytes"); | |
} | |
const options = { | |
[kByteLength]: byteLength, | |
fin: true, | |
generateMask: this._generateMask, | |
mask: mask2, | |
maskBuffer: this._maskBuffer, | |
opcode: 10, | |
readOnly, | |
rsv1: false | |
}; | |
if (this._deflating) { | |
this.enqueue([this.dispatch, data, false, options, cb]); | |
} else { | |
this.sendFrame(Sender.frame(data, options), cb); | |
} | |
} | |
/** | |
* Sends a data message to the other peer. | |
* | |
* @param {*} data The message to send | |
* @param {Object} options Options object | |
* @param {Boolean} [options.binary=false] Specifies whether `data` is binary | |
* or text | |
* @param {Boolean} [options.compress=false] Specifies whether or not to | |
* compress `data` | |
* @param {Boolean} [options.fin=false] Specifies whether the fragment is the | |
* last one | |
* @param {Boolean} [options.mask=false] Specifies whether or not to mask | |
* `data` | |
* @param {Function} [cb] Callback | |
* @public | |
*/ | |
send(data, options, cb) { | |
const perMessageDeflate = this._extensions[PerMessageDeflate$2.extensionName]; | |
let opcode = options.binary ? 2 : 1; | |
let rsv1 = options.compress; | |
let byteLength; | |
let readOnly; | |
if (typeof data === "string") { | |
byteLength = Buffer.byteLength(data); | |
readOnly = false; | |
} else { | |
data = toBuffer$1(data); | |
byteLength = data.length; | |
readOnly = toBuffer$1.readOnly; | |
} | |
if (this._firstFragment) { | |
this._firstFragment = false; | |
if (rsv1 && perMessageDeflate && perMessageDeflate.params[perMessageDeflate._isServer ? "server_no_context_takeover" : "client_no_context_takeover"]) { | |
rsv1 = byteLength >= perMessageDeflate._threshold; | |
} | |
this._compress = rsv1; | |
} else { | |
rsv1 = false; | |
opcode = 0; | |
} | |
if (options.fin) | |
this._firstFragment = true; | |
if (perMessageDeflate) { | |
const opts = { | |
[kByteLength]: byteLength, | |
fin: options.fin, | |
generateMask: this._generateMask, | |
mask: options.mask, | |
maskBuffer: this._maskBuffer, | |
opcode, | |
readOnly, | |
rsv1 | |
}; | |
if (this._deflating) { | |
this.enqueue([this.dispatch, data, this._compress, opts, cb]); | |
} else { | |
this.dispatch(data, this._compress, opts, cb); | |
} | |
} else { | |
this.sendFrame( | |
Sender.frame(data, { | |
[kByteLength]: byteLength, | |
fin: options.fin, | |
generateMask: this._generateMask, | |
mask: options.mask, | |
maskBuffer: this._maskBuffer, | |
opcode, | |
readOnly, | |
rsv1: false | |
}), | |
cb | |
); | |
} | |
} | |
/** | |
* Dispatches a message. | |
* | |
* @param {(Buffer|String)} data The message to send | |
* @param {Boolean} [compress=false] Specifies whether or not to compress | |
* `data` | |
* @param {Object} options Options object | |
* @param {Boolean} [options.fin=false] Specifies whether or not to set the | |
* FIN bit | |
* @param {Function} [options.generateMask] The function used to generate the | |
* masking key | |
* @param {Boolean} [options.mask=false] Specifies whether or not to mask | |
* `data` | |
* @param {Buffer} [options.maskBuffer] The buffer used to store the masking | |
* key | |
* @param {Number} options.opcode The opcode | |
* @param {Boolean} [options.readOnly=false] Specifies whether `data` can be | |
* modified | |
* @param {Boolean} [options.rsv1=false] Specifies whether or not to set the | |
* RSV1 bit | |
* @param {Function} [cb] Callback | |
* @private | |
*/ | |
dispatch(data, compress, options, cb) { | |
if (!compress) { | |
this.sendFrame(Sender.frame(data, options), cb); | |
return; | |
} | |
const perMessageDeflate = this._extensions[PerMessageDeflate$2.extensionName]; | |
this._bufferedBytes += options[kByteLength]; | |
this._deflating = true; | |
perMessageDeflate.compress(data, options.fin, (_, buf) => { | |
if (this._socket.destroyed) { | |
const err = new Error( | |
"The socket was closed while data was being compressed" | |
); | |
if (typeof cb === "function") | |
cb(err); | |
for (let i = 0; i < this._queue.length; i++) { | |
const params = this._queue[i]; | |
const callback = params[params.length - 1]; | |
if (typeof callback === "function") | |
callback(err); | |
} | |
return; | |
} | |
this._bufferedBytes -= options[kByteLength]; | |
this._deflating = false; | |
options.readOnly = false; | |
this.sendFrame(Sender.frame(buf, options), cb); | |
this.dequeue(); | |
}); | |
} | |
/** | |
* Executes queued send operations. | |
* | |
* @private | |
*/ | |
dequeue() { | |
while (!this._deflating && this._queue.length) { | |
const params = this._queue.shift(); | |
this._bufferedBytes -= params[3][kByteLength]; | |
Reflect.apply(params[0], this, params.slice(1)); | |
} | |
} | |
/** | |
* Enqueues a send operation. | |
* | |
* @param {Array} params Send operation parameters. | |
* @private | |
*/ | |
enqueue(params) { | |
this._bufferedBytes += params[3][kByteLength]; | |
this._queue.push(params); | |
} | |
/** | |
* Sends a frame. | |
* | |
* @param {Buffer[]} list The frame to send | |
* @param {Function} [cb] Callback | |
* @private | |
*/ | |
sendFrame(list, cb) { | |
if (list.length === 2) { | |
this._socket.cork(); | |
this._socket.write(list[0]); | |
this._socket.write(list[1], cb); | |
this._socket.uncork(); | |
} else { | |
this._socket.write(list[0], cb); | |
} | |
} | |
}; | |
var sender = Sender$1; | |
const sender$1 = /* @__PURE__ */ getDefaultExportFromCjs(sender); | |
const { kForOnEventAttribute: kForOnEventAttribute$1, kListener: kListener$1 } = constants; | |
const kCode = Symbol("kCode"); | |
const kData = Symbol("kData"); | |
const kError = Symbol("kError"); | |
const kMessage = Symbol("kMessage"); | |
const kReason = Symbol("kReason"); | |
const kTarget = Symbol("kTarget"); | |
const kType = Symbol("kType"); | |
const kWasClean = Symbol("kWasClean"); | |
class Event { | |
/** | |
* Create a new `Event`. | |
* | |
* @param {String} type The name of the event | |
* @throws {TypeError} If the `type` argument is not specified | |
*/ | |
constructor(type) { | |
this[kTarget] = null; | |
this[kType] = type; | |
} | |
/** | |
* @type {*} | |
*/ | |
get target() { | |
return this[kTarget]; | |
} | |
/** | |
* @type {String} | |
*/ | |
get type() { | |
return this[kType]; | |
} | |
} | |
Object.defineProperty(Event.prototype, "target", { enumerable: true }); | |
Object.defineProperty(Event.prototype, "type", { enumerable: true }); | |
class CloseEvent extends Event { | |
/** | |
* Create a new `CloseEvent`. | |
* | |
* @param {String} type The name of the event | |
* @param {Object} [options] A dictionary object that allows for setting | |
* attributes via object members of the same name | |
* @param {Number} [options.code=0] The status code explaining why the | |
* connection was closed | |
* @param {String} [options.reason=''] A human-readable string explaining why | |
* the connection was closed | |
* @param {Boolean} [options.wasClean=false] Indicates whether or not the | |
* connection was cleanly closed | |
*/ | |
constructor(type, options = {}) { | |
super(type); | |
this[kCode] = options.code === void 0 ? 0 : options.code; | |
this[kReason] = options.reason === void 0 ? "" : options.reason; | |
this[kWasClean] = options.wasClean === void 0 ? false : options.wasClean; | |
} | |
/** | |
* @type {Number} | |
*/ | |
get code() { | |
return this[kCode]; | |
} | |
/** | |
* @type {String} | |
*/ | |
get reason() { | |
return this[kReason]; | |
} | |
/** | |
* @type {Boolean} | |
*/ | |
get wasClean() { | |
return this[kWasClean]; | |
} | |
} | |
Object.defineProperty(CloseEvent.prototype, "code", { enumerable: true }); | |
Object.defineProperty(CloseEvent.prototype, "reason", { enumerable: true }); | |
Object.defineProperty(CloseEvent.prototype, "wasClean", { enumerable: true }); | |
class ErrorEvent extends Event { | |
/** | |
* Create a new `ErrorEvent`. | |
* | |
* @param {String} type The name of the event | |
* @param {Object} [options] A dictionary object that allows for setting | |
* attributes via object members of the same name | |
* @param {*} [options.error=null] The error that generated this event | |
* @param {String} [options.message=''] The error message | |
*/ | |
constructor(type, options = {}) { | |
super(type); | |
this[kError] = options.error === void 0 ? null : options.error; | |
this[kMessage] = options.message === void 0 ? "" : options.message; | |
} | |
/** | |
* @type {*} | |
*/ | |
get error() { | |
return this[kError]; | |
} | |
/** | |
* @type {String} | |
*/ | |
get message() { | |
return this[kMessage]; | |
} | |
} | |
Object.defineProperty(ErrorEvent.prototype, "error", { enumerable: true }); | |
Object.defineProperty(ErrorEvent.prototype, "message", { enumerable: true }); | |
class MessageEvent extends Event { | |
/** | |
* Create a new `MessageEvent`. | |
* | |
* @param {String} type The name of the event | |
* @param {Object} [options] A dictionary object that allows for setting | |
* attributes via object members of the same name | |
* @param {*} [options.data=null] The message content | |
*/ | |
constructor(type, options = {}) { | |
super(type); | |
this[kData] = options.data === void 0 ? null : options.data; | |
} | |
/** | |
* @type {*} | |
*/ | |
get data() { | |
return this[kData]; | |
} | |
} | |
Object.defineProperty(MessageEvent.prototype, "data", { enumerable: true }); | |
const EventTarget = { | |
/** | |
* Register an event listener. | |
* | |
* @param {String} type A string representing the event type to listen for | |
* @param {(Function|Object)} handler The listener to add | |
* @param {Object} [options] An options object specifies characteristics about | |
* the event listener | |
* @param {Boolean} [options.once=false] A `Boolean` indicating that the | |
* listener should be invoked at most once after being added. If `true`, | |
* the listener would be automatically removed when invoked. | |
* @public | |
*/ | |
addEventListener(type, handler, options = {}) { | |
for (const listener of this.listeners(type)) { | |
if (!options[kForOnEventAttribute$1] && listener[kListener$1] === handler && !listener[kForOnEventAttribute$1]) { | |
return; | |
} | |
} | |
let wrapper; | |
if (type === "message") { | |
wrapper = function onMessage(data, isBinary) { | |
const event = new MessageEvent("message", { | |
data: isBinary ? data : data.toString() | |
}); | |
event[kTarget] = this; | |
callListener(handler, this, event); | |
}; | |
} else if (type === "close") { | |
wrapper = function onClose(code, message) { | |
const event = new CloseEvent("close", { | |
code, | |
reason: message.toString(), | |
wasClean: this._closeFrameReceived && this._closeFrameSent | |
}); | |
event[kTarget] = this; | |
callListener(handler, this, event); | |
}; | |
} else if (type === "error") { | |
wrapper = function onError(error2) { | |
const event = new ErrorEvent("error", { | |
error: error2, | |
message: error2.message | |
}); | |
event[kTarget] = this; | |
callListener(handler, this, event); | |
}; | |
} else if (type === "open") { | |
wrapper = function onOpen() { | |
const event = new Event("open"); | |
event[kTarget] = this; | |
callListener(handler, this, event); | |
}; | |
} else { | |
return; | |
} | |
wrapper[kForOnEventAttribute$1] = !!options[kForOnEventAttribute$1]; | |
wrapper[kListener$1] = handler; | |
if (options.once) { | |
this.once(type, wrapper); | |
} else { | |
this.on(type, wrapper); | |
} | |
}, | |
/** | |
* Remove an event listener. | |
* | |
* @param {String} type A string representing the event type to remove | |
* @param {(Function|Object)} handler The listener to remove | |
* @public | |
*/ | |
removeEventListener(type, handler) { | |
for (const listener of this.listeners(type)) { | |
if (listener[kListener$1] === handler && !listener[kForOnEventAttribute$1]) { | |
this.removeListener(type, listener); | |
break; | |
} | |
} | |
} | |
}; | |
var eventTarget = { | |
CloseEvent, | |
ErrorEvent, | |
Event, | |
EventTarget, | |
MessageEvent | |
}; | |
function callListener(listener, thisArg, event) { | |
if (typeof listener === "object" && listener.handleEvent) { | |
listener.handleEvent.call(listener, event); | |
} else { | |
listener.call(thisArg, event); | |
} | |
} | |
const { tokenChars: tokenChars$1 } = validationExports; | |
function push(dest, name, elem) { | |
if (dest[name] === void 0) | |
dest[name] = [elem]; | |
else | |
dest[name].push(elem); | |
} | |
function parse$2(header) { | |
const offers = /* @__PURE__ */ Object.create(null); | |
let params = /* @__PURE__ */ Object.create(null); | |
let mustUnescape = false; | |
let isEscaping = false; | |
let inQuotes = false; | |
let extensionName; | |
let paramName; | |
let start = -1; | |
let code = -1; | |
let end = -1; | |
let i = 0; | |
for (; i < header.length; i++) { | |
code = header.charCodeAt(i); | |
if (extensionName === void 0) { | |
if (end === -1 && tokenChars$1[code] === 1) { | |
if (start === -1) | |
start = i; | |
} else if (i !== 0 && (code === 32 || code === 9)) { | |
if (end === -1 && start !== -1) | |
end = i; | |
} else if (code === 59 || code === 44) { | |
if (start === -1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (end === -1) | |
end = i; | |
const name = header.slice(start, end); | |
if (code === 44) { | |
push(offers, name, params); | |
params = /* @__PURE__ */ Object.create(null); | |
} else { | |
extensionName = name; | |
} | |
start = end = -1; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} else if (paramName === void 0) { | |
if (end === -1 && tokenChars$1[code] === 1) { | |
if (start === -1) | |
start = i; | |
} else if (code === 32 || code === 9) { | |
if (end === -1 && start !== -1) | |
end = i; | |
} else if (code === 59 || code === 44) { | |
if (start === -1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (end === -1) | |
end = i; | |
push(params, header.slice(start, end), true); | |
if (code === 44) { | |
push(offers, extensionName, params); | |
params = /* @__PURE__ */ Object.create(null); | |
extensionName = void 0; | |
} | |
start = end = -1; | |
} else if (code === 61 && start !== -1 && end === -1) { | |
paramName = header.slice(start, i); | |
start = end = -1; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} else { | |
if (isEscaping) { | |
if (tokenChars$1[code] !== 1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (start === -1) | |
start = i; | |
else if (!mustUnescape) | |
mustUnescape = true; | |
isEscaping = false; | |
} else if (inQuotes) { | |
if (tokenChars$1[code] === 1) { | |
if (start === -1) | |
start = i; | |
} else if (code === 34 && start !== -1) { | |
inQuotes = false; | |
end = i; | |
} else if (code === 92) { | |
isEscaping = true; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} else if (code === 34 && header.charCodeAt(i - 1) === 61) { | |
inQuotes = true; | |
} else if (end === -1 && tokenChars$1[code] === 1) { | |
if (start === -1) | |
start = i; | |
} else if (start !== -1 && (code === 32 || code === 9)) { | |
if (end === -1) | |
end = i; | |
} else if (code === 59 || code === 44) { | |
if (start === -1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (end === -1) | |
end = i; | |
let value = header.slice(start, end); | |
if (mustUnescape) { | |
value = value.replace(/\\/g, ""); | |
mustUnescape = false; | |
} | |
push(params, paramName, value); | |
if (code === 44) { | |
push(offers, extensionName, params); | |
params = /* @__PURE__ */ Object.create(null); | |
extensionName = void 0; | |
} | |
paramName = void 0; | |
start = end = -1; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} | |
} | |
if (start === -1 || inQuotes || code === 32 || code === 9) { | |
throw new SyntaxError("Unexpected end of input"); | |
} | |
if (end === -1) | |
end = i; | |
const token = header.slice(start, end); | |
if (extensionName === void 0) { | |
push(offers, token, params); | |
} else { | |
if (paramName === void 0) { | |
push(params, token, true); | |
} else if (mustUnescape) { | |
push(params, paramName, token.replace(/\\/g, "")); | |
} else { | |
push(params, paramName, token); | |
} | |
push(offers, extensionName, params); | |
} | |
return offers; | |
} | |
function format$1(extensions) { | |
return Object.keys(extensions).map((extension2) => { | |
let configurations = extensions[extension2]; | |
if (!Array.isArray(configurations)) | |
configurations = [configurations]; | |
return configurations.map((params) => { | |
return [extension2].concat( | |
Object.keys(params).map((k) => { | |
let values = params[k]; | |
if (!Array.isArray(values)) | |
values = [values]; | |
return values.map((v) => v === true ? k : `${k}=${v}`).join("; "); | |
}) | |
).join("; "); | |
}).join(", "); | |
}).join(", "); | |
} | |
var extension$1 = { format: format$1, parse: parse$2 }; | |
const EventEmitter$1 = require$$0$3; | |
const https = require$$1$1; | |
const http$1 = require$$2; | |
const net = require$$3; | |
const tls = require$$4; | |
const { randomBytes, createHash: createHash$1 } = require$$5; | |
const { URL } = require$$7; | |
const PerMessageDeflate$1 = permessageDeflate; | |
const Receiver2 = receiver; | |
const Sender2 = sender; | |
const { | |
BINARY_TYPES, | |
EMPTY_BUFFER, | |
GUID: GUID$1, | |
kForOnEventAttribute, | |
kListener, | |
kStatusCode, | |
kWebSocket: kWebSocket$1, | |
NOOP | |
} = constants; | |
const { | |
EventTarget: { addEventListener, removeEventListener } | |
} = eventTarget; | |
const { format, parse: parse$1 } = extension$1; | |
const { toBuffer } = bufferUtilExports; | |
const closeTimeout = 30 * 1e3; | |
const kAborted = Symbol("kAborted"); | |
const protocolVersions = [8, 13]; | |
const readyStates = ["CONNECTING", "OPEN", "CLOSING", "CLOSED"]; | |
const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/; | |
let WebSocket$1 = class WebSocket extends EventEmitter$1 { | |
/** | |
* Create a new `WebSocket`. | |
* | |
* @param {(String|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._binaryType = BINARY_TYPES[0]; | |
this._closeCode = 1006; | |
this._closeFrameReceived = false; | |
this._closeFrameSent = false; | |
this._closeMessage = EMPTY_BUFFER; | |
this._closeTimer = null; | |
this._extensions = {}; | |
this._paused = false; | |
this._protocol = ""; | |
this._readyState = WebSocket.CONNECTING; | |
this._receiver = null; | |
this._sender = null; | |
this._socket = null; | |
if (address !== null) { | |
this._bufferedAmount = 0; | |
this._isServer = false; | |
this._redirects = 0; | |
if (protocols === void 0) { | |
protocols = []; | |
} else if (!Array.isArray(protocols)) { | |
if (typeof protocols === "object" && protocols !== null) { | |
options = protocols; | |
protocols = []; | |
} else { | |
protocols = [protocols]; | |
} | |
} | |
initAsClient(this, address, protocols, options); | |
} else { | |
this._isServer = true; | |
} | |
} | |
/** | |
* 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; | |
if (this._receiver) | |
this._receiver._binaryType = type; | |
} | |
/** | |
* @type {Number} | |
*/ | |
get bufferedAmount() { | |
if (!this._socket) | |
return this._bufferedAmount; | |
return this._socket._writableState.length + this._sender._bufferedBytes; | |
} | |
/** | |
* @type {String} | |
*/ | |
get extensions() { | |
return Object.keys(this._extensions).join(); | |
} | |
/** | |
* @type {Boolean} | |
*/ | |
get isPaused() { | |
return this._paused; | |
} | |
/** | |
* @type {Function} | |
*/ | |
/* istanbul ignore next */ | |
get onclose() { | |
return null; | |
} | |
/** | |
* @type {Function} | |
*/ | |
/* istanbul ignore next */ | |
get onerror() { | |
return null; | |
} | |
/** | |
* @type {Function} | |
*/ | |
/* istanbul ignore next */ | |
get onopen() { | |
return null; | |
} | |
/** | |
* @type {Function} | |
*/ | |
/* istanbul ignore next */ | |
get onmessage() { | |
return null; | |
} | |
/** | |
* @type {String} | |
*/ | |
get protocol() { | |
return this._protocol; | |
} | |
/** | |
* @type {Number} | |
*/ | |
get readyState() { | |
return this._readyState; | |
} | |
/** | |
* @type {String} | |
*/ | |
get url() { | |
return this._url; | |
} | |
/** | |
* Set up the socket and the internal resources. | |
* | |
* @param {(net.Socket|tls.Socket)} socket The network socket between the | |
* server and client | |
* @param {Buffer} head The first packet of the upgraded stream | |
* @param {Object} options Options object | |
* @param {Function} [options.generateMask] The function used to generate the | |
* masking key | |
* @param {Number} [options.maxPayload=0] The maximum allowed message size | |
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or | |
* not to skip UTF-8 validation for text and close messages | |
* @private | |
*/ | |
setSocket(socket, head, options) { | |
const receiver2 = new Receiver2({ | |
binaryType: this.binaryType, | |
extensions: this._extensions, | |
isServer: this._isServer, | |
maxPayload: options.maxPayload, | |
skipUTF8Validation: options.skipUTF8Validation | |
}); | |
this._sender = new Sender2(socket, this._extensions, options.generateMask); | |
this._receiver = receiver2; | |
this._socket = socket; | |
receiver2[kWebSocket$1] = this; | |
socket[kWebSocket$1] = this; | |
receiver2.on("conclude", receiverOnConclude); | |
receiver2.on("drain", receiverOnDrain); | |
receiver2.on("error", receiverOnError); | |
receiver2.on("message", receiverOnMessage); | |
receiver2.on("ping", receiverOnPing); | |
receiver2.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$1); | |
this._readyState = WebSocket.OPEN; | |
this.emit("open"); | |
} | |
/** | |
* Emit the `'close'` event. | |
* | |
* @private | |
*/ | |
emitClose() { | |
if (!this._socket) { | |
this._readyState = WebSocket.CLOSED; | |
this.emit("close", this._closeCode, this._closeMessage); | |
return; | |
} | |
if (this._extensions[PerMessageDeflate$1.extensionName]) { | |
this._extensions[PerMessageDeflate$1.extensionName].cleanup(); | |
} | |
this._receiver.removeAllListeners(); | |
this._readyState = WebSocket.CLOSED; | |
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|Buffer)} [data] The reason 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"; | |
abortHandshake$1(this, this._req, msg); | |
return; | |
} | |
if (this.readyState === WebSocket.CLOSING) { | |
if (this._closeFrameSent && (this._closeFrameReceived || this._receiver._writableState.errorEmitted)) { | |
this._socket.end(); | |
} | |
return; | |
} | |
this._readyState = WebSocket.CLOSING; | |
this._sender.close(code, data, !this._isServer, (err) => { | |
if (err) | |
return; | |
this._closeFrameSent = true; | |
if (this._closeFrameReceived || this._receiver._writableState.errorEmitted) { | |
this._socket.end(); | |
} | |
}); | |
this._closeTimer = setTimeout( | |
this._socket.destroy.bind(this._socket), | |
closeTimeout | |
); | |
} | |
/** | |
* Pause the socket. | |
* | |
* @public | |
*/ | |
pause() { | |
if (this.readyState === WebSocket.CONNECTING || this.readyState === WebSocket.CLOSED) { | |
return; | |
} | |
this._paused = true; | |
this._socket.pause(); | |
} | |
/** | |
* 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, mask2, cb) { | |
if (this.readyState === WebSocket.CONNECTING) { | |
throw new Error("WebSocket is not open: readyState 0 (CONNECTING)"); | |
} | |
if (typeof data === "function") { | |
cb = data; | |
data = mask2 = void 0; | |
} else if (typeof mask2 === "function") { | |
cb = mask2; | |
mask2 = void 0; | |
} | |
if (typeof data === "number") | |
data = data.toString(); | |
if (this.readyState !== WebSocket.OPEN) { | |
sendAfterClose(this, data, cb); | |
return; | |
} | |
if (mask2 === void 0) | |
mask2 = !this._isServer; | |
this._sender.ping(data || EMPTY_BUFFER, mask2, 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, mask2, cb) { | |
if (this.readyState === WebSocket.CONNECTING) { | |
throw new Error("WebSocket is not open: readyState 0 (CONNECTING)"); | |
} | |
if (typeof data === "function") { | |
cb = data; | |
data = mask2 = void 0; | |
} else if (typeof mask2 === "function") { | |
cb = mask2; | |
mask2 = void 0; | |
} | |
if (typeof data === "number") | |
data = data.toString(); | |
if (this.readyState !== WebSocket.OPEN) { | |
sendAfterClose(this, data, cb); | |
return; | |
} | |
if (mask2 === void 0) | |
mask2 = !this._isServer; | |
this._sender.pong(data || EMPTY_BUFFER, mask2, cb); | |
} | |
/** | |
* Resume the socket. | |
* | |
* @public | |
*/ | |
resume() { | |
if (this.readyState === WebSocket.CONNECTING || this.readyState === WebSocket.CLOSED) { | |
return; | |
} | |
this._paused = false; | |
if (!this._receiver._writableState.needDrain) | |
this._socket.resume(); | |
} | |
/** | |
* Send a data message. | |
* | |
* @param {*} data The message to send | |
* @param {Object} [options] Options object | |
* @param {Boolean} [options.binary] Specifies whether `data` is binary or | |
* text | |
* @param {Boolean} [options.compress] Specifies whether or not to compress | |
* `data` | |
* @param {Boolean} [options.fin=true] 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$1.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"; | |
abortHandshake$1(this, this._req, msg); | |
return; | |
} | |
if (this._socket) { | |
this._readyState = WebSocket.CLOSING; | |
this._socket.destroy(); | |
} | |
} | |
}; | |
Object.defineProperty(WebSocket$1, "CONNECTING", { | |
enumerable: true, | |
value: readyStates.indexOf("CONNECTING") | |
}); | |
Object.defineProperty(WebSocket$1.prototype, "CONNECTING", { | |
enumerable: true, | |
value: readyStates.indexOf("CONNECTING") | |
}); | |
Object.defineProperty(WebSocket$1, "OPEN", { | |
enumerable: true, | |
value: readyStates.indexOf("OPEN") | |
}); | |
Object.defineProperty(WebSocket$1.prototype, "OPEN", { | |
enumerable: true, | |
value: readyStates.indexOf("OPEN") | |
}); | |
Object.defineProperty(WebSocket$1, "CLOSING", { | |
enumerable: true, | |
value: readyStates.indexOf("CLOSING") | |
}); | |
Object.defineProperty(WebSocket$1.prototype, "CLOSING", { | |
enumerable: true, | |
value: readyStates.indexOf("CLOSING") | |
}); | |
Object.defineProperty(WebSocket$1, "CLOSED", { | |
enumerable: true, | |
value: readyStates.indexOf("CLOSED") | |
}); | |
Object.defineProperty(WebSocket$1.prototype, "CLOSED", { | |
enumerable: true, | |
value: readyStates.indexOf("CLOSED") | |
}); | |
[ | |
"binaryType", | |
"bufferedAmount", | |
"extensions", | |
"isPaused", | |
"protocol", | |
"readyState", | |
"url" | |
].forEach((property) => { | |
Object.defineProperty(WebSocket$1.prototype, property, { enumerable: true }); | |
}); | |
["open", "error", "close", "message"].forEach((method) => { | |
Object.defineProperty(WebSocket$1.prototype, `on${method}`, { | |
enumerable: true, | |
get() { | |
for (const listener of this.listeners(method)) { | |
if (listener[kForOnEventAttribute]) | |
return listener[kListener]; | |
} | |
return null; | |
}, | |
set(handler) { | |
for (const listener of this.listeners(method)) { | |
if (listener[kForOnEventAttribute]) { | |
this.removeListener(method, listener); | |
break; | |
} | |
} | |
if (typeof handler !== "function") | |
return; | |
this.addEventListener(method, handler, { | |
[kForOnEventAttribute]: true | |
}); | |
} | |
}); | |
}); | |
WebSocket$1.prototype.addEventListener = addEventListener; | |
WebSocket$1.prototype.removeEventListener = removeEventListener; | |
var websocket = WebSocket$1; | |
function initAsClient(websocket2, address, protocols, options) { | |
const opts = { | |
protocolVersion: protocolVersions[1], | |
maxPayload: 100 * 1024 * 1024, | |
skipUTF8Validation: false, | |
perMessageDeflate: true, | |
followRedirects: false, | |
maxRedirects: 10, | |
...options, | |
createConnection: void 0, | |
socketPath: void 0, | |
hostname: void 0, | |
protocol: void 0, | |
timeout: void 0, | |
method: "GET", | |
host: void 0, | |
path: void 0, | |
port: void 0 | |
}; | |
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; | |
websocket2._url = address.href; | |
} else { | |
try { | |
parsedUrl = new URL(address); | |
} catch (e) { | |
throw new SyntaxError(`Invalid URL: ${address}`); | |
} | |
websocket2._url = address; | |
} | |
const isSecure = parsedUrl.protocol === "wss:"; | |
const isIpcUrl = parsedUrl.protocol === "ws+unix:"; | |
let invalidUrlMessage; | |
if (parsedUrl.protocol !== "ws:" && !isSecure && !isIpcUrl) { | |
invalidUrlMessage = `The URL's protocol must be one of "ws:", "wss:", or "ws+unix:"`; | |
} else if (isIpcUrl && !parsedUrl.pathname) { | |
invalidUrlMessage = "The URL's pathname is empty"; | |
} else if (parsedUrl.hash) { | |
invalidUrlMessage = "The URL contains a fragment identifier"; | |
} | |
if (invalidUrlMessage) { | |
const err = new SyntaxError(invalidUrlMessage); | |
if (websocket2._redirects === 0) { | |
throw err; | |
} else { | |
emitErrorAndClose(websocket2, err); | |
return; | |
} | |
} | |
const defaultPort = isSecure ? 443 : 80; | |
const key = randomBytes(16).toString("base64"); | |
const request = isSecure ? https.request : http$1.request; | |
const protocolSet = /* @__PURE__ */ new Set(); | |
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 = { | |
...opts.headers, | |
"Sec-WebSocket-Version": opts.protocolVersion, | |
"Sec-WebSocket-Key": key, | |
Connection: "Upgrade", | |
Upgrade: "websocket" | |
}; | |
opts.path = parsedUrl.pathname + parsedUrl.search; | |
opts.timeout = opts.handshakeTimeout; | |
if (opts.perMessageDeflate) { | |
perMessageDeflate = new PerMessageDeflate$1( | |
opts.perMessageDeflate !== true ? opts.perMessageDeflate : {}, | |
false, | |
opts.maxPayload | |
); | |
opts.headers["Sec-WebSocket-Extensions"] = format({ | |
[PerMessageDeflate$1.extensionName]: perMessageDeflate.offer() | |
}); | |
} | |
if (protocols.length) { | |
for (const protocol of protocols) { | |
if (typeof protocol !== "string" || !subprotocolRegex.test(protocol) || protocolSet.has(protocol)) { | |
throw new SyntaxError( | |
"An invalid or duplicated subprotocol was specified" | |
); | |
} | |
protocolSet.add(protocol); | |
} | |
opts.headers["Sec-WebSocket-Protocol"] = protocols.join(","); | |
} | |
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 (isIpcUrl) { | |
const parts = opts.path.split(":"); | |
opts.socketPath = parts[0]; | |
opts.path = parts[1]; | |
} | |
let req; | |
if (opts.followRedirects) { | |
if (websocket2._redirects === 0) { | |
websocket2._originalIpc = isIpcUrl; | |
websocket2._originalSecure = isSecure; | |
websocket2._originalHostOrSocketPath = isIpcUrl ? opts.socketPath : parsedUrl.host; | |
const headers = options && options.headers; | |
options = { ...options, headers: {} }; | |
if (headers) { | |
for (const [key2, value] of Object.entries(headers)) { | |
options.headers[key2.toLowerCase()] = value; | |
} | |
} | |
} else if (websocket2.listenerCount("redirect") === 0) { | |
const isSameHost = isIpcUrl ? websocket2._originalIpc ? opts.socketPath === websocket2._originalHostOrSocketPath : false : websocket2._originalIpc ? false : parsedUrl.host === websocket2._originalHostOrSocketPath; | |
if (!isSameHost || websocket2._originalSecure && !isSecure) { | |
delete opts.headers.authorization; | |
delete opts.headers.cookie; | |
if (!isSameHost) | |
delete opts.headers.host; | |
opts.auth = void 0; | |
} | |
} | |
if (opts.auth && !options.headers.authorization) { | |
options.headers.authorization = "Basic " + Buffer.from(opts.auth).toString("base64"); | |
} | |
req = websocket2._req = request(opts); | |
if (websocket2._redirects) { | |
websocket2.emit("redirect", websocket2.url, req); | |
} | |
} else { | |
req = websocket2._req = request(opts); | |
} | |
if (opts.timeout) { | |
req.on("timeout", () => { | |
abortHandshake$1(websocket2, req, "Opening handshake has timed out"); | |
}); | |
} | |
req.on("error", (err) => { | |
if (req === null || req[kAborted]) | |
return; | |
req = websocket2._req = null; | |
emitErrorAndClose(websocket2, err); | |
}); | |
req.on("response", (res) => { | |
const location = res.headers.location; | |
const statusCode = res.statusCode; | |
if (location && opts.followRedirects && statusCode >= 300 && statusCode < 400) { | |
if (++websocket2._redirects > opts.maxRedirects) { | |
abortHandshake$1(websocket2, req, "Maximum redirects exceeded"); | |
return; | |
} | |
req.abort(); | |
let addr; | |
try { | |
addr = new URL(location, address); | |
} catch (e) { | |
const err = new SyntaxError(`Invalid URL: ${location}`); | |
emitErrorAndClose(websocket2, err); | |
return; | |
} | |
initAsClient(websocket2, addr, protocols, options); | |
} else if (!websocket2.emit("unexpected-response", req, res)) { | |
abortHandshake$1( | |
websocket2, | |
req, | |
`Unexpected server response: ${res.statusCode}` | |
); | |
} | |
}); | |
req.on("upgrade", (res, socket, head) => { | |
websocket2.emit("upgrade", res); | |
if (websocket2.readyState !== WebSocket$1.CONNECTING) | |
return; | |
req = websocket2._req = null; | |
if (res.headers.upgrade.toLowerCase() !== "websocket") { | |
abortHandshake$1(websocket2, socket, "Invalid Upgrade header"); | |
return; | |
} | |
const digest = createHash$1("sha1").update(key + GUID$1).digest("base64"); | |
if (res.headers["sec-websocket-accept"] !== digest) { | |
abortHandshake$1(websocket2, socket, "Invalid Sec-WebSocket-Accept header"); | |
return; | |
} | |
const serverProt = res.headers["sec-websocket-protocol"]; | |
let protError; | |
if (serverProt !== void 0) { | |
if (!protocolSet.size) { | |
protError = "Server sent a subprotocol but none was requested"; | |
} else if (!protocolSet.has(serverProt)) { | |
protError = "Server sent an invalid subprotocol"; | |
} | |
} else if (protocolSet.size) { | |
protError = "Server sent no subprotocol"; | |
} | |
if (protError) { | |
abortHandshake$1(websocket2, socket, protError); | |
return; | |
} | |
if (serverProt) | |
websocket2._protocol = serverProt; | |
const secWebSocketExtensions = res.headers["sec-websocket-extensions"]; | |
if (secWebSocketExtensions !== void 0) { | |
if (!perMessageDeflate) { | |
const message = "Server sent a Sec-WebSocket-Extensions header but no extension was requested"; | |
abortHandshake$1(websocket2, socket, message); | |
return; | |
} | |
let extensions; | |
try { | |
extensions = parse$1(secWebSocketExtensions); | |
} catch (err) { | |
const message = "Invalid Sec-WebSocket-Extensions header"; | |
abortHandshake$1(websocket2, socket, message); | |
return; | |
} | |
const extensionNames = Object.keys(extensions); | |
if (extensionNames.length !== 1 || extensionNames[0] !== PerMessageDeflate$1.extensionName) { | |
const message = "Server indicated an extension that was not requested"; | |
abortHandshake$1(websocket2, socket, message); | |
return; | |
} | |
try { | |
perMessageDeflate.accept(extensions[PerMessageDeflate$1.extensionName]); | |
} catch (err) { | |
const message = "Invalid Sec-WebSocket-Extensions header"; | |
abortHandshake$1(websocket2, socket, message); | |
return; | |
} | |
websocket2._extensions[PerMessageDeflate$1.extensionName] = perMessageDeflate; | |
} | |
websocket2.setSocket(socket, head, { | |
generateMask: opts.generateMask, | |
maxPayload: opts.maxPayload, | |
skipUTF8Validation: opts.skipUTF8Validation | |
}); | |
}); | |
if (opts.finishRequest) { | |
opts.finishRequest(req, websocket2); | |
} else { | |
req.end(); | |
} | |
} | |
function emitErrorAndClose(websocket2, err) { | |
websocket2._readyState = WebSocket$1.CLOSING; | |
websocket2.emit("error", err); | |
websocket2.emitClose(); | |
} | |
function netConnect(options) { | |
options.path = options.socketPath; | |
return net.connect(options); | |
} | |
function tlsConnect(options) { | |
options.path = void 0; | |
if (!options.servername && options.servername !== "") { | |
options.servername = net.isIP(options.host) ? "" : options.host; | |
} | |
return tls.connect(options); | |
} | |
function abortHandshake$1(websocket2, stream2, message) { | |
websocket2._readyState = WebSocket$1.CLOSING; | |
const err = new Error(message); | |
Error.captureStackTrace(err, abortHandshake$1); | |
if (stream2.setHeader) { | |
stream2[kAborted] = true; | |
stream2.abort(); | |
if (stream2.socket && !stream2.socket.destroyed) { | |
stream2.socket.destroy(); | |
} | |
process.nextTick(emitErrorAndClose, websocket2, err); | |
} else { | |
stream2.destroy(err); | |
stream2.once("error", websocket2.emit.bind(websocket2, "error")); | |
stream2.once("close", websocket2.emitClose.bind(websocket2)); | |
} | |
} | |
function sendAfterClose(websocket2, data, cb) { | |
if (data) { | |
const length = toBuffer(data).length; | |
if (websocket2._socket) | |
websocket2._sender._bufferedBytes += length; | |
else | |
websocket2._bufferedAmount += length; | |
} | |
if (cb) { | |
const err = new Error( | |
`WebSocket is not open: readyState ${websocket2.readyState} (${readyStates[websocket2.readyState]})` | |
); | |
process.nextTick(cb, err); | |
} | |
} | |
function receiverOnConclude(code, reason) { | |
const websocket2 = this[kWebSocket$1]; | |
websocket2._closeFrameReceived = true; | |
websocket2._closeMessage = reason; | |
websocket2._closeCode = code; | |
if (websocket2._socket[kWebSocket$1] === void 0) | |
return; | |
websocket2._socket.removeListener("data", socketOnData); | |
process.nextTick(resume, websocket2._socket); | |
if (code === 1005) | |
websocket2.close(); | |
else | |
websocket2.close(code, reason); | |
} | |
function receiverOnDrain() { | |
const websocket2 = this[kWebSocket$1]; | |
if (!websocket2.isPaused) | |
websocket2._socket.resume(); | |
} | |
function receiverOnError(err) { | |
const websocket2 = this[kWebSocket$1]; | |
if (websocket2._socket[kWebSocket$1] !== void 0) { | |
websocket2._socket.removeListener("data", socketOnData); | |
process.nextTick(resume, websocket2._socket); | |
websocket2.close(err[kStatusCode]); | |
} | |
websocket2.emit("error", err); | |
} | |
function receiverOnFinish() { | |
this[kWebSocket$1].emitClose(); | |
} | |
function receiverOnMessage(data, isBinary) { | |
this[kWebSocket$1].emit("message", data, isBinary); | |
} | |
function receiverOnPing(data) { | |
const websocket2 = this[kWebSocket$1]; | |
websocket2.pong(data, !websocket2._isServer, NOOP); | |
websocket2.emit("ping", data); | |
} | |
function receiverOnPong(data) { | |
this[kWebSocket$1].emit("pong", data); | |
} | |
function resume(stream2) { | |
stream2.resume(); | |
} | |
function socketOnClose() { | |
const websocket2 = this[kWebSocket$1]; | |
this.removeListener("close", socketOnClose); | |
this.removeListener("data", socketOnData); | |
this.removeListener("end", socketOnEnd); | |
websocket2._readyState = WebSocket$1.CLOSING; | |
let chunk; | |
if (!this._readableState.endEmitted && !websocket2._closeFrameReceived && !websocket2._receiver._writableState.errorEmitted && (chunk = websocket2._socket.read()) !== null) { | |
websocket2._receiver.write(chunk); | |
} | |
websocket2._receiver.end(); | |
this[kWebSocket$1] = void 0; | |
clearTimeout(websocket2._closeTimer); | |
if (websocket2._receiver._writableState.finished || websocket2._receiver._writableState.errorEmitted) { | |
websocket2.emitClose(); | |
} else { | |
websocket2._receiver.on("error", receiverOnFinish); | |
websocket2._receiver.on("finish", receiverOnFinish); | |
} | |
} | |
function socketOnData(chunk) { | |
if (!this[kWebSocket$1]._receiver.write(chunk)) { | |
this.pause(); | |
} | |
} | |
function socketOnEnd() { | |
const websocket2 = this[kWebSocket$1]; | |
websocket2._readyState = WebSocket$1.CLOSING; | |
websocket2._receiver.end(); | |
this.end(); | |
} | |
function socketOnError$1() { | |
const websocket2 = this[kWebSocket$1]; | |
this.removeListener("error", socketOnError$1); | |
this.on("error", NOOP); | |
if (websocket2) { | |
websocket2._readyState = WebSocket$1.CLOSING; | |
this.destroy(); | |
} | |
} | |
const WebSocket$2 = /* @__PURE__ */ getDefaultExportFromCjs(websocket); | |
const { tokenChars } = validationExports; | |
function parse(header) { | |
const protocols = /* @__PURE__ */ new Set(); | |
let start = -1; | |
let end = -1; | |
let i = 0; | |
for (i; i < header.length; i++) { | |
const code = header.charCodeAt(i); | |
if (end === -1 && tokenChars[code] === 1) { | |
if (start === -1) | |
start = i; | |
} else if (i !== 0 && (code === 32 || code === 9)) { | |
if (end === -1 && start !== -1) | |
end = i; | |
} else if (code === 44) { | |
if (start === -1) { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
if (end === -1) | |
end = i; | |
const protocol2 = header.slice(start, end); | |
if (protocols.has(protocol2)) { | |
throw new SyntaxError(`The "${protocol2}" subprotocol is duplicated`); | |
} | |
protocols.add(protocol2); | |
start = end = -1; | |
} else { | |
throw new SyntaxError(`Unexpected character at index ${i}`); | |
} | |
} | |
if (start === -1 || end !== -1) { | |
throw new SyntaxError("Unexpected end of input"); | |
} | |
const protocol = header.slice(start, i); | |
if (protocols.has(protocol)) { | |
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`); | |
} | |
protocols.add(protocol); | |
return protocols; | |
} | |
var subprotocol$1 = { parse }; | |
const EventEmitter = require$$0$3; | |
const http = require$$2; | |
const { createHash } = require$$5; | |
const extension = extension$1; | |
const PerMessageDeflate2 = permessageDeflate; | |
const subprotocol = subprotocol$1; | |
const WebSocket2 = websocket; | |
const { GUID, kWebSocket } = constants; | |
const keyRegex = /^[+/0-9A-Za-z]{22}==$/; | |
const RUNNING = 0; | |
const CLOSING = 1; | |
const CLOSED = 2; | |
class WebSocketServer extends EventEmitter { | |
/** | |
* Create a `WebSocketServer` instance. | |
* | |
* @param {Object} options Configuration options | |
* @param {Number} [options.backlog=511] The maximum length of the queue of | |
* pending connections | |
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to | |
* track clients | |
* @param {Function} [options.handleProtocols] A hook to handle protocols | |
* @param {String} [options.host] The hostname where to bind the server | |
* @param {Number} [options.maxPayload=104857600] The maximum allowed message | |
* size | |
* @param {Boolean} [options.noServer=false] Enable no server mode | |
* @param {String} [options.path] Accept only connections matching this path | |
* @param {(Boolean|Object)} [options.perMessageDeflate=false] Enable/disable | |
* permessage-deflate | |
* @param {Number} [options.port] The port where to bind the server | |
* @param {(http.Server|https.Server)} [options.server] A pre-created HTTP/S | |
* server to use | |
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or | |
* not to skip UTF-8 validation for text and close messages | |
* @param {Function} [options.verifyClient] A hook to reject connections | |
* @param {Function} [options.WebSocket=WebSocket] Specifies the `WebSocket` | |
* class to use. It must be the `WebSocket` class or class that extends it | |
* @param {Function} [callback] A listener for the `listening` event | |
*/ | |
constructor(options, callback) { | |
super(); | |
options = { | |
maxPayload: 100 * 1024 * 1024, | |
skipUTF8Validation: false, | |
perMessageDeflate: false, | |
handleProtocols: null, | |
clientTracking: true, | |
verifyClient: null, | |
noServer: false, | |
backlog: null, | |
// use default (511 as implemented in net.js) | |
server: null, | |
host: null, | |
path: null, | |
port: null, | |
WebSocket: WebSocket2, | |
...options | |
}; | |
if (options.port == null && !options.server && !options.noServer || options.port != null && (options.server || options.noServer) || options.server && options.noServer) { | |
throw new TypeError( | |
'One and only one of the "port", "server", or "noServer" options must be specified' | |
); | |
} | |
if (options.port != null) { | |
this._server = http.createServer((req, res) => { | |
const body = http.STATUS_CODES[426]; | |
res.writeHead(426, { | |
"Content-Length": body.length, | |
"Content-Type": "text/plain" | |
}); | |
res.end(body); | |
}); | |
this._server.listen( | |
options.port, | |
options.host, | |
options.backlog, | |
callback | |
); | |
} else if (options.server) { | |
this._server = options.server; | |
} | |
if (this._server) { | |
const emitConnection = this.emit.bind(this, "connection"); | |
this._removeListeners = addListeners(this._server, { | |
listening: this.emit.bind(this, "listening"), | |
error: this.emit.bind(this, "error"), | |
upgrade: (req, socket, head) => { | |
this.handleUpgrade(req, socket, head, emitConnection); | |
} | |
}); | |
} | |
if (options.perMessageDeflate === true) | |
options.perMessageDeflate = {}; | |
if (options.clientTracking) { | |
this.clients = /* @__PURE__ */ new Set(); | |
this._shouldEmitClose = false; | |
} | |
this.options = options; | |
this._state = RUNNING; | |
} | |
/** | |
* Returns the bound address, the address family name, and port of the server | |
* as reported by the operating system if listening on an IP socket. | |
* If the server is listening on a pipe or UNIX domain socket, the name is | |
* returned as a string. | |
* | |
* @return {(Object|String|null)} The address of the server | |
* @public | |
*/ | |
address() { | |
if (this.options.noServer) { | |
throw new Error('The server is operating in "noServer" mode'); | |
} | |
if (!this._server) | |
return null; | |
return this._server.address(); | |
} | |
/** | |
* Stop the server from accepting new connections and emit the `'close'` event | |
* when all existing connections are closed. | |
* | |
* @param {Function} [cb] A one-time listener for the `'close'` event | |
* @public | |
*/ | |
close(cb) { | |
if (this._state === CLOSED) { | |
if (cb) { | |
this.once("close", () => { | |
cb(new Error("The server is not running")); | |
}); | |
} | |
process.nextTick(emitClose, this); | |
return; | |
} | |
if (cb) | |
this.once("close", cb); | |
if (this._state === CLOSING) | |
return; | |
this._state = CLOSING; | |
if (this.options.noServer || this.options.server) { | |
if (this._server) { | |
this._removeListeners(); | |
this._removeListeners = this._server = null; | |
} | |
if (this.clients) { | |
if (!this.clients.size) { | |
process.nextTick(emitClose, this); | |
} else { | |
this._shouldEmitClose = true; | |
} | |
} else { | |
process.nextTick(emitClose, this); | |
} | |
} else { | |
const server = this._server; | |
this._removeListeners(); | |
this._removeListeners = this._server = null; | |
server.close(() => { | |
emitClose(this); | |
}); | |
} | |
} | |
/** | |
* See if a given request should be handled by this server instance. | |
* | |
* @param {http.IncomingMessage} req Request object to inspect | |
* @return {Boolean} `true` if the request is valid, else `false` | |
* @public | |
*/ | |
shouldHandle(req) { | |
if (this.options.path) { | |
const index = req.url.indexOf("?"); | |
const pathname = index !== -1 ? req.url.slice(0, index) : req.url; | |
if (pathname !== this.options.path) | |
return false; | |
} | |
return true; | |
} | |
/** | |
* Handle a HTTP Upgrade request. | |
* | |
* @param {http.IncomingMessage} req The request object | |
* @param {(net.Socket|tls.Socket)} socket The network socket between the | |
* server and client | |
* @param {Buffer} head The first packet of the upgraded stream | |
* @param {Function} cb Callback | |
* @public | |
*/ | |
handleUpgrade(req, socket, head, cb) { | |
socket.on("error", socketOnError); | |
const key = req.headers["sec-websocket-key"]; | |
const version = +req.headers["sec-websocket-version"]; | |
if (req.method !== "GET") { | |
const message = "Invalid HTTP method"; | |
abortHandshakeOrEmitwsClientError(this, req, socket, 405, message); | |
return; | |
} | |
if (req.headers.upgrade.toLowerCase() !== "websocket") { | |
const message = "Invalid Upgrade header"; | |
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); | |
return; | |
} | |
if (!key || !keyRegex.test(key)) { | |
const message = "Missing or invalid Sec-WebSocket-Key header"; | |
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); | |
return; | |
} | |
if (version !== 8 && version !== 13) { | |
const message = "Missing or invalid Sec-WebSocket-Version header"; | |
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); | |
return; | |
} | |
if (!this.shouldHandle(req)) { | |
abortHandshake(socket, 400); | |
return; | |
} | |
const secWebSocketProtocol = req.headers["sec-websocket-protocol"]; | |
let protocols = /* @__PURE__ */ new Set(); | |
if (secWebSocketProtocol !== void 0) { | |
try { | |
protocols = subprotocol.parse(secWebSocketProtocol); | |
} catch (err) { | |
const message = "Invalid Sec-WebSocket-Protocol header"; | |
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); | |
return; | |
} | |
} | |
const secWebSocketExtensions = req.headers["sec-websocket-extensions"]; | |
const extensions = {}; | |
if (this.options.perMessageDeflate && secWebSocketExtensions !== void 0) { | |
const perMessageDeflate = new PerMessageDeflate2( | |
this.options.perMessageDeflate, | |
true, | |
this.options.maxPayload | |
); | |
try { | |
const offers = extension.parse(secWebSocketExtensions); | |
if (offers[PerMessageDeflate2.extensionName]) { | |
perMessageDeflate.accept(offers[PerMessageDeflate2.extensionName]); | |
extensions[PerMessageDeflate2.extensionName] = perMessageDeflate; | |
} | |
} catch (err) { | |
const message = "Invalid or unacceptable Sec-WebSocket-Extensions header"; | |
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message); | |
return; | |
} | |
} | |
if (this.options.verifyClient) { | |
const info = { | |
origin: req.headers[`${version === 8 ? "sec-websocket-origin" : "origin"}`], | |
secure: !!(req.socket.authorized || req.socket.encrypted), | |
req | |
}; | |
if (this.options.verifyClient.length === 2) { | |
this.options.verifyClient(info, (verified, code, message, headers) => { | |
if (!verified) { | |
return abortHandshake(socket, code || 401, message, headers); | |
} | |
this.completeUpgrade( | |
extensions, | |
key, | |
protocols, | |
req, | |
socket, | |
head, | |
cb | |
); | |
}); | |
return; | |
} | |
if (!this.options.verifyClient(info)) | |
return abortHandshake(socket, 401); | |
} | |
this.completeUpgrade(extensions, key, protocols, req, socket, head, cb); | |
} | |
/** | |
* Upgrade the connection to WebSocket. | |
* | |
* @param {Object} extensions The accepted extensions | |
* @param {String} key The value of the `Sec-WebSocket-Key` header | |
* @param {Set} protocols The subprotocols | |
* @param {http.IncomingMessage} req The request object | |
* @param {(net.Socket|tls.Socket)} socket The network socket between the | |
* server and client | |
* @param {Buffer} head The first packet of the upgraded stream | |
* @param {Function} cb Callback | |
* @throws {Error} If called more than once with the same socket | |
* @private | |
*/ | |
completeUpgrade(extensions, key, protocols, req, socket, head, cb) { | |
if (!socket.readable || !socket.writable) | |
return socket.destroy(); | |
if (socket[kWebSocket]) { | |
throw new Error( | |
"server.handleUpgrade() was called more than once with the same socket, possibly due to a misconfiguration" | |
); | |
} | |
if (this._state > RUNNING) | |
return abortHandshake(socket, 503); | |
const digest = createHash("sha1").update(key + GUID).digest("base64"); | |
const headers = [ | |
"HTTP/1.1 101 Switching Protocols", | |
"Upgrade: websocket", | |
"Connection: Upgrade", | |
`Sec-WebSocket-Accept: ${digest}` | |
]; | |
const ws = new this.options.WebSocket(null); | |
if (protocols.size) { | |
const protocol = this.options.handleProtocols ? this.options.handleProtocols(protocols, req) : protocols.values().next().value; | |
if (protocol) { | |
headers.push(`Sec-WebSocket-Protocol: ${protocol}`); | |
ws._protocol = protocol; | |
} | |
} | |
if (extensions[PerMessageDeflate2.extensionName]) { | |
const params = extensions[PerMessageDeflate2.extensionName].params; | |
const value = extension.format({ | |
[PerMessageDeflate2.extensionName]: [params] | |
}); | |
headers.push(`Sec-WebSocket-Extensions: ${value}`); | |
ws._extensions = extensions; | |
} | |
this.emit("headers", headers, req); | |
socket.write(headers.concat("\r\n").join("\r\n")); | |
socket.removeListener("error", socketOnError); | |
ws.setSocket(socket, head, { | |
maxPayload: this.options.maxPayload, | |
skipUTF8Validation: this.options.skipUTF8Validation | |
}); | |
if (this.clients) { | |
this.clients.add(ws); | |
ws.on("close", () => { | |
this.clients.delete(ws); | |
if (this._shouldEmitClose && !this.clients.size) { | |
process.nextTick(emitClose, this); | |
} | |
}); | |
} | |
cb(ws, req); | |
} | |
} | |
var websocketServer = WebSocketServer; | |
function addListeners(server, map) { | |
for (const event of Object.keys(map)) | |
server.on(event, map[event]); | |
return function removeListeners() { | |
for (const event of Object.keys(map)) { | |
server.removeListener(event, map[event]); | |
} | |
}; | |
} | |
function emitClose(server) { | |
server._state = CLOSED; | |
server.emit("close"); | |
} | |
function socketOnError() { | |
this.destroy(); | |
} | |
function abortHandshake(socket, code, message, headers) { | |
message = message || http.STATUS_CODES[code]; | |
headers = { | |
Connection: "close", | |
"Content-Type": "text/html", | |
"Content-Length": Buffer.byteLength(message), | |
...headers | |
}; | |
socket.once("finish", socket.destroy); | |
socket.end( | |
`HTTP/1.1 ${code} ${http.STATUS_CODES[code]}\r | |
` + Object.keys(headers).map((h) => `${h}: ${headers[h]}`).join("\r\n") + "\r\n\r\n" + message | |
); | |
} | |
function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) { | |
if (server.listenerCount("wsClientError")) { | |
const err = new Error(message); | |
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError); | |
server.emit("wsClientError", err, socket, req); | |
} else { | |
abortHandshake(socket, code, message); | |
} | |
} | |
const websocketServer$1 = /* @__PURE__ */ getDefaultExportFromCjs(websocketServer); | |
export { | |
receiver$1 as Receiver, | |
sender$1 as Sender, | |
WebSocket$2 as WebSocket, | |
websocketServer$1 as WebSocketServer, | |
stream$1 as createWebSocketStream, | |
WebSocket$2 as default | |
}; | |