mirror of
https://github.com/lush2020/edgetunnel.git
synced 2026-03-22 01:22:21 +08:00
6374 lines
181 KiB
JavaScript
6374 lines
181 KiB
JavaScript
/******/ (() => { // webpackBootstrap
|
||
/******/ var __webpack_modules__ = ([
|
||
/* 0 */,
|
||
/* 1 */
|
||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
__webpack_require__.r(__webpack_exports__);
|
||
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
|
||
/* harmony export */ "__assign": () => (/* binding */ __assign),
|
||
/* harmony export */ "__asyncDelegator": () => (/* binding */ __asyncDelegator),
|
||
/* harmony export */ "__asyncGenerator": () => (/* binding */ __asyncGenerator),
|
||
/* harmony export */ "__asyncValues": () => (/* binding */ __asyncValues),
|
||
/* harmony export */ "__await": () => (/* binding */ __await),
|
||
/* harmony export */ "__awaiter": () => (/* binding */ __awaiter),
|
||
/* harmony export */ "__classPrivateFieldGet": () => (/* binding */ __classPrivateFieldGet),
|
||
/* harmony export */ "__classPrivateFieldIn": () => (/* binding */ __classPrivateFieldIn),
|
||
/* harmony export */ "__classPrivateFieldSet": () => (/* binding */ __classPrivateFieldSet),
|
||
/* harmony export */ "__createBinding": () => (/* binding */ __createBinding),
|
||
/* harmony export */ "__decorate": () => (/* binding */ __decorate),
|
||
/* harmony export */ "__esDecorate": () => (/* binding */ __esDecorate),
|
||
/* harmony export */ "__exportStar": () => (/* binding */ __exportStar),
|
||
/* harmony export */ "__extends": () => (/* binding */ __extends),
|
||
/* harmony export */ "__generator": () => (/* binding */ __generator),
|
||
/* harmony export */ "__importDefault": () => (/* binding */ __importDefault),
|
||
/* harmony export */ "__importStar": () => (/* binding */ __importStar),
|
||
/* harmony export */ "__makeTemplateObject": () => (/* binding */ __makeTemplateObject),
|
||
/* harmony export */ "__metadata": () => (/* binding */ __metadata),
|
||
/* harmony export */ "__param": () => (/* binding */ __param),
|
||
/* harmony export */ "__propKey": () => (/* binding */ __propKey),
|
||
/* harmony export */ "__read": () => (/* binding */ __read),
|
||
/* harmony export */ "__rest": () => (/* binding */ __rest),
|
||
/* harmony export */ "__runInitializers": () => (/* binding */ __runInitializers),
|
||
/* harmony export */ "__setFunctionName": () => (/* binding */ __setFunctionName),
|
||
/* harmony export */ "__spread": () => (/* binding */ __spread),
|
||
/* harmony export */ "__spreadArray": () => (/* binding */ __spreadArray),
|
||
/* harmony export */ "__spreadArrays": () => (/* binding */ __spreadArrays),
|
||
/* harmony export */ "__values": () => (/* binding */ __values)
|
||
/* harmony export */ });
|
||
/******************************************************************************
|
||
Copyright (c) Microsoft Corporation.
|
||
|
||
Permission to use, copy, modify, and/or distribute this software for any
|
||
purpose with or without fee is hereby granted.
|
||
|
||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||
PERFORMANCE OF THIS SOFTWARE.
|
||
***************************************************************************** */
|
||
/* global Reflect, Promise */
|
||
|
||
var extendStatics = function(d, b) {
|
||
extendStatics = Object.setPrototypeOf ||
|
||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
|
||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
|
||
return extendStatics(d, b);
|
||
};
|
||
|
||
function __extends(d, b) {
|
||
if (typeof b !== "function" && b !== null)
|
||
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
|
||
extendStatics(d, b);
|
||
function __() { this.constructor = d; }
|
||
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
|
||
}
|
||
|
||
var __assign = function() {
|
||
__assign = Object.assign || function __assign(t) {
|
||
for (var s, i = 1, n = arguments.length; i < n; i++) {
|
||
s = arguments[i];
|
||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p];
|
||
}
|
||
return t;
|
||
}
|
||
return __assign.apply(this, arguments);
|
||
}
|
||
|
||
function __rest(s, e) {
|
||
var t = {};
|
||
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
|
||
t[p] = s[p];
|
||
if (s != null && typeof Object.getOwnPropertySymbols === "function")
|
||
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
|
||
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
|
||
t[p[i]] = s[p[i]];
|
||
}
|
||
return t;
|
||
}
|
||
|
||
function __decorate(decorators, target, key, desc) {
|
||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||
}
|
||
|
||
function __param(paramIndex, decorator) {
|
||
return function (target, key) { decorator(target, key, paramIndex); }
|
||
}
|
||
|
||
function __esDecorate(ctor, descriptorIn, decorators, contextIn, initializers, extraInitializers) {
|
||
function accept(f) { if (f !== void 0 && typeof f !== "function") throw new TypeError("Function expected"); return f; }
|
||
var kind = contextIn.kind, key = kind === "getter" ? "get" : kind === "setter" ? "set" : "value";
|
||
var target = !descriptorIn && ctor ? contextIn["static"] ? ctor : ctor.prototype : null;
|
||
var descriptor = descriptorIn || (target ? Object.getOwnPropertyDescriptor(target, contextIn.name) : {});
|
||
var _, done = false;
|
||
for (var i = decorators.length - 1; i >= 0; i--) {
|
||
var context = {};
|
||
for (var p in contextIn) context[p] = p === "access" ? {} : contextIn[p];
|
||
for (var p in contextIn.access) context.access[p] = contextIn.access[p];
|
||
context.addInitializer = function (f) { if (done) throw new TypeError("Cannot add initializers after decoration has completed"); extraInitializers.push(accept(f || null)); };
|
||
var result = (0, decorators[i])(kind === "accessor" ? { get: descriptor.get, set: descriptor.set } : descriptor[key], context);
|
||
if (kind === "accessor") {
|
||
if (result === void 0) continue;
|
||
if (result === null || typeof result !== "object") throw new TypeError("Object expected");
|
||
if (_ = accept(result.get)) descriptor.get = _;
|
||
if (_ = accept(result.set)) descriptor.set = _;
|
||
if (_ = accept(result.init)) initializers.push(_);
|
||
}
|
||
else if (_ = accept(result)) {
|
||
if (kind === "field") initializers.push(_);
|
||
else descriptor[key] = _;
|
||
}
|
||
}
|
||
if (target) Object.defineProperty(target, contextIn.name, descriptor);
|
||
done = true;
|
||
};
|
||
|
||
function __runInitializers(thisArg, initializers, value) {
|
||
var useValue = arguments.length > 2;
|
||
for (var i = 0; i < initializers.length; i++) {
|
||
value = useValue ? initializers[i].call(thisArg, value) : initializers[i].call(thisArg);
|
||
}
|
||
return useValue ? value : void 0;
|
||
};
|
||
|
||
function __propKey(x) {
|
||
return typeof x === "symbol" ? x : "".concat(x);
|
||
};
|
||
|
||
function __setFunctionName(f, name, prefix) {
|
||
if (typeof name === "symbol") name = name.description ? "[".concat(name.description, "]") : "";
|
||
return Object.defineProperty(f, "name", { configurable: true, value: prefix ? "".concat(prefix, " ", name) : name });
|
||
};
|
||
|
||
function __metadata(metadataKey, metadataValue) {
|
||
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue);
|
||
}
|
||
|
||
function __awaiter(thisArg, _arguments, P, generator) {
|
||
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
||
return new (P || (P = Promise))(function (resolve, reject) {
|
||
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
||
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
||
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
||
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
||
});
|
||
}
|
||
|
||
function __generator(thisArg, body) {
|
||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
||
function verb(n) { return function (v) { return step([n, v]); }; }
|
||
function step(op) {
|
||
if (f) throw new TypeError("Generator is already executing.");
|
||
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
||
if (y = 0, t) op = [op[0] & 2, t.value];
|
||
switch (op[0]) {
|
||
case 0: case 1: t = op; break;
|
||
case 4: _.label++; return { value: op[1], done: false };
|
||
case 5: _.label++; y = op[1]; op = [0]; continue;
|
||
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
||
default:
|
||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
||
if (t[2]) _.ops.pop();
|
||
_.trys.pop(); continue;
|
||
}
|
||
op = body.call(thisArg, _);
|
||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
||
}
|
||
}
|
||
|
||
var __createBinding = Object.create ? (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
var desc = Object.getOwnPropertyDescriptor(m, k);
|
||
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
||
desc = { enumerable: true, get: function() { return m[k]; } };
|
||
}
|
||
Object.defineProperty(o, k2, desc);
|
||
}) : (function(o, m, k, k2) {
|
||
if (k2 === undefined) k2 = k;
|
||
o[k2] = m[k];
|
||
});
|
||
|
||
function __exportStar(m, o) {
|
||
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(o, p)) __createBinding(o, m, p);
|
||
}
|
||
|
||
function __values(o) {
|
||
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
|
||
if (m) return m.call(o);
|
||
if (o && typeof o.length === "number") return {
|
||
next: function () {
|
||
if (o && i >= o.length) o = void 0;
|
||
return { value: o && o[i++], done: !o };
|
||
}
|
||
};
|
||
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
|
||
}
|
||
|
||
function __read(o, n) {
|
||
var m = typeof Symbol === "function" && o[Symbol.iterator];
|
||
if (!m) return o;
|
||
var i = m.call(o), r, ar = [], e;
|
||
try {
|
||
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
|
||
}
|
||
catch (error) { e = { error: error }; }
|
||
finally {
|
||
try {
|
||
if (r && !r.done && (m = i["return"])) m.call(i);
|
||
}
|
||
finally { if (e) throw e.error; }
|
||
}
|
||
return ar;
|
||
}
|
||
|
||
/** @deprecated */
|
||
function __spread() {
|
||
for (var ar = [], i = 0; i < arguments.length; i++)
|
||
ar = ar.concat(__read(arguments[i]));
|
||
return ar;
|
||
}
|
||
|
||
/** @deprecated */
|
||
function __spreadArrays() {
|
||
for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length;
|
||
for (var r = Array(s), k = 0, i = 0; i < il; i++)
|
||
for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++)
|
||
r[k] = a[j];
|
||
return r;
|
||
}
|
||
|
||
function __spreadArray(to, from, pack) {
|
||
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
|
||
if (ar || !(i in from)) {
|
||
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
|
||
ar[i] = from[i];
|
||
}
|
||
}
|
||
return to.concat(ar || Array.prototype.slice.call(from));
|
||
}
|
||
|
||
function __await(v) {
|
||
return this instanceof __await ? (this.v = v, this) : new __await(v);
|
||
}
|
||
|
||
function __asyncGenerator(thisArg, _arguments, generator) {
|
||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||
var g = generator.apply(thisArg, _arguments || []), i, q = [];
|
||
return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i;
|
||
function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; }
|
||
function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } }
|
||
function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); }
|
||
function fulfill(value) { resume("next", value); }
|
||
function reject(value) { resume("throw", value); }
|
||
function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); }
|
||
}
|
||
|
||
function __asyncDelegator(o) {
|
||
var i, p;
|
||
return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i;
|
||
function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: false } : f ? f(v) : v; } : f; }
|
||
}
|
||
|
||
function __asyncValues(o) {
|
||
if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined.");
|
||
var m = o[Symbol.asyncIterator], i;
|
||
return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i);
|
||
function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; }
|
||
function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); }
|
||
}
|
||
|
||
function __makeTemplateObject(cooked, raw) {
|
||
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; }
|
||
return cooked;
|
||
};
|
||
|
||
var __setModuleDefault = Object.create ? (function(o, v) {
|
||
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
||
}) : function(o, v) {
|
||
o["default"] = v;
|
||
};
|
||
|
||
function __importStar(mod) {
|
||
if (mod && mod.__esModule) return mod;
|
||
var result = {};
|
||
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
||
__setModuleDefault(result, mod);
|
||
return result;
|
||
}
|
||
|
||
function __importDefault(mod) {
|
||
return (mod && mod.__esModule) ? mod : { default: mod };
|
||
}
|
||
|
||
function __classPrivateFieldGet(receiver, state, kind, f) {
|
||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
||
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
||
}
|
||
|
||
function __classPrivateFieldSet(receiver, state, value, kind, f) {
|
||
if (kind === "m") throw new TypeError("Private method is not writable");
|
||
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
||
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
||
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
||
}
|
||
|
||
function __classPrivateFieldIn(state, receiver) {
|
||
if (receiver === null || (typeof receiver !== "object" && typeof receiver !== "function")) throw new TypeError("Cannot use 'in' operator on non-object");
|
||
return typeof state === "function" ? receiver === state : state.has(receiver);
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 2 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("http");
|
||
|
||
/***/ }),
|
||
/* 3 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("url");
|
||
|
||
/***/ }),
|
||
/* 4 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const WebSocket = __webpack_require__(5);
|
||
|
||
WebSocket.createWebSocketStream = __webpack_require__(23);
|
||
WebSocket.Server = __webpack_require__(24);
|
||
WebSocket.Receiver = __webpack_require__(17);
|
||
WebSocket.Sender = __webpack_require__(20);
|
||
|
||
WebSocket.WebSocket = WebSocket;
|
||
WebSocket.WebSocketServer = WebSocket.Server;
|
||
|
||
module.exports = WebSocket;
|
||
|
||
|
||
/***/ }),
|
||
/* 5 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^Readable$" }] */
|
||
|
||
|
||
|
||
const EventEmitter = __webpack_require__(6);
|
||
const https = __webpack_require__(7);
|
||
const http = __webpack_require__(2);
|
||
const net = __webpack_require__(8);
|
||
const tls = __webpack_require__(9);
|
||
const { randomBytes, createHash } = __webpack_require__(10);
|
||
const { Readable } = __webpack_require__(11);
|
||
const { URL } = __webpack_require__(3);
|
||
|
||
const PerMessageDeflate = __webpack_require__(12);
|
||
const Receiver = __webpack_require__(17);
|
||
const Sender = __webpack_require__(20);
|
||
const {
|
||
BINARY_TYPES,
|
||
EMPTY_BUFFER,
|
||
GUID,
|
||
kForOnEventAttribute,
|
||
kListener,
|
||
kStatusCode,
|
||
kWebSocket,
|
||
NOOP
|
||
} = __webpack_require__(15);
|
||
const {
|
||
EventTarget: { addEventListener, removeEventListener }
|
||
} = __webpack_require__(21);
|
||
const { format, parse } = __webpack_require__(22);
|
||
const { toBuffer } = __webpack_require__(14);
|
||
|
||
const closeTimeout = 30 * 1000;
|
||
const kAborted = Symbol('kAborted');
|
||
const protocolVersions = [8, 13];
|
||
const readyStates = ['CONNECTING', 'OPEN', 'CLOSING', 'CLOSED'];
|
||
const subprotocolRegex = /^[!#$%&'*+\-.0-9A-Z^_`|a-z~]+$/;
|
||
|
||
/**
|
||
* Class representing a WebSocket.
|
||
*
|
||
* @extends EventEmitter
|
||
*/
|
||
class WebSocket extends EventEmitter {
|
||
/**
|
||
* 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 === undefined) {
|
||
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;
|
||
|
||
//
|
||
// Allow to change `binaryType` on the fly.
|
||
//
|
||
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 receiver = new Receiver({
|
||
binaryType: this.binaryType,
|
||
extensions: this._extensions,
|
||
isServer: this._isServer,
|
||
maxPayload: options.maxPayload,
|
||
skipUTF8Validation: options.skipUTF8Validation
|
||
});
|
||
|
||
this._sender = new Sender(socket, this._extensions, options.generateMask);
|
||
this._receiver = receiver;
|
||
this._socket = socket;
|
||
|
||
receiver[kWebSocket] = this;
|
||
socket[kWebSocket] = this;
|
||
|
||
receiver.on('conclude', receiverOnConclude);
|
||
receiver.on('drain', receiverOnDrain);
|
||
receiver.on('error', receiverOnError);
|
||
receiver.on('message', receiverOnMessage);
|
||
receiver.on('ping', receiverOnPing);
|
||
receiver.on('pong', receiverOnPong);
|
||
|
||
socket.setTimeout(0);
|
||
socket.setNoDelay();
|
||
|
||
if (head.length > 0) socket.unshift(head);
|
||
|
||
socket.on('close', socketOnClose);
|
||
socket.on('data', socketOnData);
|
||
socket.on('end', socketOnEnd);
|
||
socket.on('error', socketOnError);
|
||
|
||
this._readyState = WebSocket.OPEN;
|
||
this.emit('open');
|
||
}
|
||
|
||
/**
|
||
* Emit the `'close'` event.
|
||
*
|
||
* @private
|
||
*/
|
||
emitClose() {
|
||
if (!this._socket) {
|
||
this._readyState = WebSocket.CLOSED;
|
||
this.emit('close', this._closeCode, this._closeMessage);
|
||
return;
|
||
}
|
||
|
||
if (this._extensions[PerMessageDeflate.extensionName]) {
|
||
this._extensions[PerMessageDeflate.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(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) => {
|
||
//
|
||
// This error is handled by the `'error'` listener on the socket. We only
|
||
// want to know if the close frame has been sent here.
|
||
//
|
||
if (err) return;
|
||
|
||
this._closeFrameSent = true;
|
||
|
||
if (
|
||
this._closeFrameReceived ||
|
||
this._receiver._writableState.errorEmitted
|
||
) {
|
||
this._socket.end();
|
||
}
|
||
});
|
||
|
||
//
|
||
// Specify a timeout for the closing handshake to complete.
|
||
//
|
||
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, mask, cb) {
|
||
if (this.readyState === WebSocket.CONNECTING) {
|
||
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
|
||
}
|
||
|
||
if (typeof data === 'function') {
|
||
cb = data;
|
||
data = mask = undefined;
|
||
} else if (typeof mask === 'function') {
|
||
cb = mask;
|
||
mask = undefined;
|
||
}
|
||
|
||
if (typeof data === 'number') data = data.toString();
|
||
|
||
if (this.readyState !== WebSocket.OPEN) {
|
||
sendAfterClose(this, data, cb);
|
||
return;
|
||
}
|
||
|
||
if (mask === undefined) mask = !this._isServer;
|
||
this._sender.ping(data || EMPTY_BUFFER, mask, cb);
|
||
}
|
||
|
||
/**
|
||
* Send a pong.
|
||
*
|
||
* @param {*} [data] The data to send
|
||
* @param {Boolean} [mask] Indicates whether or not to mask `data`
|
||
* @param {Function} [cb] Callback which is executed when the pong is sent
|
||
* @public
|
||
*/
|
||
pong(data, mask, cb) {
|
||
if (this.readyState === WebSocket.CONNECTING) {
|
||
throw new Error('WebSocket is not open: readyState 0 (CONNECTING)');
|
||
}
|
||
|
||
if (typeof data === 'function') {
|
||
cb = data;
|
||
data = mask = undefined;
|
||
} else if (typeof mask === 'function') {
|
||
cb = mask;
|
||
mask = undefined;
|
||
}
|
||
|
||
if (typeof data === 'number') data = data.toString();
|
||
|
||
if (this.readyState !== WebSocket.OPEN) {
|
||
sendAfterClose(this, data, cb);
|
||
return;
|
||
}
|
||
|
||
if (mask === undefined) mask = !this._isServer;
|
||
this._sender.pong(data || EMPTY_BUFFER, mask, cb);
|
||
}
|
||
|
||
/**
|
||
* 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.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(this, this._req, msg);
|
||
return;
|
||
}
|
||
|
||
if (this._socket) {
|
||
this._readyState = WebSocket.CLOSING;
|
||
this._socket.destroy();
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @constant {Number} CONNECTING
|
||
* @memberof WebSocket
|
||
*/
|
||
Object.defineProperty(WebSocket, 'CONNECTING', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('CONNECTING')
|
||
});
|
||
|
||
/**
|
||
* @constant {Number} CONNECTING
|
||
* @memberof WebSocket.prototype
|
||
*/
|
||
Object.defineProperty(WebSocket.prototype, 'CONNECTING', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('CONNECTING')
|
||
});
|
||
|
||
/**
|
||
* @constant {Number} OPEN
|
||
* @memberof WebSocket
|
||
*/
|
||
Object.defineProperty(WebSocket, 'OPEN', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('OPEN')
|
||
});
|
||
|
||
/**
|
||
* @constant {Number} OPEN
|
||
* @memberof WebSocket.prototype
|
||
*/
|
||
Object.defineProperty(WebSocket.prototype, 'OPEN', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('OPEN')
|
||
});
|
||
|
||
/**
|
||
* @constant {Number} CLOSING
|
||
* @memberof WebSocket
|
||
*/
|
||
Object.defineProperty(WebSocket, 'CLOSING', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('CLOSING')
|
||
});
|
||
|
||
/**
|
||
* @constant {Number} CLOSING
|
||
* @memberof WebSocket.prototype
|
||
*/
|
||
Object.defineProperty(WebSocket.prototype, 'CLOSING', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('CLOSING')
|
||
});
|
||
|
||
/**
|
||
* @constant {Number} CLOSED
|
||
* @memberof WebSocket
|
||
*/
|
||
Object.defineProperty(WebSocket, 'CLOSED', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('CLOSED')
|
||
});
|
||
|
||
/**
|
||
* @constant {Number} CLOSED
|
||
* @memberof WebSocket.prototype
|
||
*/
|
||
Object.defineProperty(WebSocket.prototype, 'CLOSED', {
|
||
enumerable: true,
|
||
value: readyStates.indexOf('CLOSED')
|
||
});
|
||
|
||
[
|
||
'binaryType',
|
||
'bufferedAmount',
|
||
'extensions',
|
||
'isPaused',
|
||
'protocol',
|
||
'readyState',
|
||
'url'
|
||
].forEach((property) => {
|
||
Object.defineProperty(WebSocket.prototype, property, { enumerable: true });
|
||
});
|
||
|
||
//
|
||
// Add the `onopen`, `onerror`, `onclose`, and `onmessage` attributes.
|
||
// See https://html.spec.whatwg.org/multipage/comms.html#the-websocket-interface
|
||
//
|
||
['open', 'error', 'close', 'message'].forEach((method) => {
|
||
Object.defineProperty(WebSocket.prototype, `on${method}`, {
|
||
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.prototype.addEventListener = addEventListener;
|
||
WebSocket.prototype.removeEventListener = removeEventListener;
|
||
|
||
module.exports = WebSocket;
|
||
|
||
/**
|
||
* Initialize a WebSocket client.
|
||
*
|
||
* @param {WebSocket} websocket The client to initialize
|
||
* @param {(String|URL)} address The URL to which to connect
|
||
* @param {Array} protocols The subprotocols
|
||
* @param {Object} [options] Connection options
|
||
* @param {Boolean} [options.followRedirects=false] Whether or not to follow
|
||
* redirects
|
||
* @param {Function} [options.generateMask] The function used to generate the
|
||
* masking key
|
||
* @param {Number} [options.handshakeTimeout] Timeout in milliseconds for the
|
||
* handshake request
|
||
* @param {Number} [options.maxPayload=104857600] The maximum allowed message
|
||
* size
|
||
* @param {Number} [options.maxRedirects=10] The maximum number of redirects
|
||
* allowed
|
||
* @param {String} [options.origin] Value of the `Origin` or
|
||
* `Sec-WebSocket-Origin` header
|
||
* @param {(Boolean|Object)} [options.perMessageDeflate=true] Enable/disable
|
||
* permessage-deflate
|
||
* @param {Number} [options.protocolVersion=13] Value of the
|
||
* `Sec-WebSocket-Version` header
|
||
* @param {Boolean} [options.skipUTF8Validation=false] Specifies whether or
|
||
* not to skip UTF-8 validation for text and close messages
|
||
* @private
|
||
*/
|
||
function initAsClient(websocket, address, protocols, options) {
|
||
const opts = {
|
||
protocolVersion: protocolVersions[1],
|
||
maxPayload: 100 * 1024 * 1024,
|
||
skipUTF8Validation: false,
|
||
perMessageDeflate: true,
|
||
followRedirects: false,
|
||
maxRedirects: 10,
|
||
...options,
|
||
createConnection: undefined,
|
||
socketPath: undefined,
|
||
hostname: undefined,
|
||
protocol: undefined,
|
||
timeout: undefined,
|
||
method: 'GET',
|
||
host: undefined,
|
||
path: undefined,
|
||
port: undefined
|
||
};
|
||
|
||
if (!protocolVersions.includes(opts.protocolVersion)) {
|
||
throw new RangeError(
|
||
`Unsupported protocol version: ${opts.protocolVersion} ` +
|
||
`(supported versions: ${protocolVersions.join(', ')})`
|
||
);
|
||
}
|
||
|
||
let parsedUrl;
|
||
|
||
if (address instanceof URL) {
|
||
parsedUrl = address;
|
||
websocket._url = address.href;
|
||
} else {
|
||
try {
|
||
parsedUrl = new URL(address);
|
||
} catch (e) {
|
||
throw new SyntaxError(`Invalid URL: ${address}`);
|
||
}
|
||
|
||
websocket._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 (websocket._redirects === 0) {
|
||
throw err;
|
||
} else {
|
||
emitErrorAndClose(websocket, err);
|
||
return;
|
||
}
|
||
}
|
||
|
||
const defaultPort = isSecure ? 443 : 80;
|
||
const key = randomBytes(16).toString('base64');
|
||
const request = isSecure ? https.request : http.request;
|
||
const protocolSet = 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(
|
||
opts.perMessageDeflate !== true ? opts.perMessageDeflate : {},
|
||
false,
|
||
opts.maxPayload
|
||
);
|
||
opts.headers['Sec-WebSocket-Extensions'] = format({
|
||
[PerMessageDeflate.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 (websocket._redirects === 0) {
|
||
websocket._originalIpc = isIpcUrl;
|
||
websocket._originalSecure = isSecure;
|
||
websocket._originalHostOrSocketPath = isIpcUrl
|
||
? opts.socketPath
|
||
: parsedUrl.host;
|
||
|
||
const headers = options && options.headers;
|
||
|
||
//
|
||
// Shallow copy the user provided options so that headers can be changed
|
||
// without mutating the original object.
|
||
//
|
||
options = { ...options, headers: {} };
|
||
|
||
if (headers) {
|
||
for (const [key, value] of Object.entries(headers)) {
|
||
options.headers[key.toLowerCase()] = value;
|
||
}
|
||
}
|
||
} else if (websocket.listenerCount('redirect') === 0) {
|
||
const isSameHost = isIpcUrl
|
||
? websocket._originalIpc
|
||
? opts.socketPath === websocket._originalHostOrSocketPath
|
||
: false
|
||
: websocket._originalIpc
|
||
? false
|
||
: parsedUrl.host === websocket._originalHostOrSocketPath;
|
||
|
||
if (!isSameHost || (websocket._originalSecure && !isSecure)) {
|
||
//
|
||
// Match curl 7.77.0 behavior and drop the following headers. These
|
||
// headers are also dropped when following a redirect to a subdomain.
|
||
//
|
||
delete opts.headers.authorization;
|
||
delete opts.headers.cookie;
|
||
|
||
if (!isSameHost) delete opts.headers.host;
|
||
|
||
opts.auth = undefined;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Match curl 7.77.0 behavior and make the first `Authorization` header win.
|
||
// If the `Authorization` header is set, then there is nothing to do as it
|
||
// will take precedence.
|
||
//
|
||
if (opts.auth && !options.headers.authorization) {
|
||
options.headers.authorization =
|
||
'Basic ' + Buffer.from(opts.auth).toString('base64');
|
||
}
|
||
|
||
req = websocket._req = request(opts);
|
||
|
||
if (websocket._redirects) {
|
||
//
|
||
// Unlike what is done for the `'upgrade'` event, no early exit is
|
||
// triggered here if the user calls `websocket.close()` or
|
||
// `websocket.terminate()` from a listener of the `'redirect'` event. This
|
||
// is because the user can also call `request.destroy()` with an error
|
||
// before calling `websocket.close()` or `websocket.terminate()` and this
|
||
// would result in an error being emitted on the `request` object with no
|
||
// `'error'` event listeners attached.
|
||
//
|
||
websocket.emit('redirect', websocket.url, req);
|
||
}
|
||
} else {
|
||
req = websocket._req = request(opts);
|
||
}
|
||
|
||
if (opts.timeout) {
|
||
req.on('timeout', () => {
|
||
abortHandshake(websocket, req, 'Opening handshake has timed out');
|
||
});
|
||
}
|
||
|
||
req.on('error', (err) => {
|
||
if (req === null || req[kAborted]) return;
|
||
|
||
req = websocket._req = null;
|
||
emitErrorAndClose(websocket, err);
|
||
});
|
||
|
||
req.on('response', (res) => {
|
||
const location = res.headers.location;
|
||
const statusCode = res.statusCode;
|
||
|
||
if (
|
||
location &&
|
||
opts.followRedirects &&
|
||
statusCode >= 300 &&
|
||
statusCode < 400
|
||
) {
|
||
if (++websocket._redirects > opts.maxRedirects) {
|
||
abortHandshake(websocket, req, 'Maximum redirects exceeded');
|
||
return;
|
||
}
|
||
|
||
req.abort();
|
||
|
||
let addr;
|
||
|
||
try {
|
||
addr = new URL(location, address);
|
||
} catch (e) {
|
||
const err = new SyntaxError(`Invalid URL: ${location}`);
|
||
emitErrorAndClose(websocket, err);
|
||
return;
|
||
}
|
||
|
||
initAsClient(websocket, addr, protocols, options);
|
||
} else if (!websocket.emit('unexpected-response', req, res)) {
|
||
abortHandshake(
|
||
websocket,
|
||
req,
|
||
`Unexpected server response: ${res.statusCode}`
|
||
);
|
||
}
|
||
});
|
||
|
||
req.on('upgrade', (res, socket, head) => {
|
||
websocket.emit('upgrade', res);
|
||
|
||
//
|
||
// The user may have closed the connection from a listener of the
|
||
// `'upgrade'` event.
|
||
//
|
||
if (websocket.readyState !== WebSocket.CONNECTING) return;
|
||
|
||
req = websocket._req = null;
|
||
|
||
if (res.headers.upgrade.toLowerCase() !== 'websocket') {
|
||
abortHandshake(websocket, socket, 'Invalid Upgrade header');
|
||
return;
|
||
}
|
||
|
||
const digest = createHash('sha1')
|
||
.update(key + GUID)
|
||
.digest('base64');
|
||
|
||
if (res.headers['sec-websocket-accept'] !== digest) {
|
||
abortHandshake(websocket, socket, 'Invalid Sec-WebSocket-Accept header');
|
||
return;
|
||
}
|
||
|
||
const serverProt = res.headers['sec-websocket-protocol'];
|
||
let protError;
|
||
|
||
if (serverProt !== undefined) {
|
||
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(websocket, socket, protError);
|
||
return;
|
||
}
|
||
|
||
if (serverProt) websocket._protocol = serverProt;
|
||
|
||
const secWebSocketExtensions = res.headers['sec-websocket-extensions'];
|
||
|
||
if (secWebSocketExtensions !== undefined) {
|
||
if (!perMessageDeflate) {
|
||
const message =
|
||
'Server sent a Sec-WebSocket-Extensions header but no extension ' +
|
||
'was requested';
|
||
abortHandshake(websocket, socket, message);
|
||
return;
|
||
}
|
||
|
||
let extensions;
|
||
|
||
try {
|
||
extensions = parse(secWebSocketExtensions);
|
||
} catch (err) {
|
||
const message = 'Invalid Sec-WebSocket-Extensions header';
|
||
abortHandshake(websocket, socket, message);
|
||
return;
|
||
}
|
||
|
||
const extensionNames = Object.keys(extensions);
|
||
|
||
if (
|
||
extensionNames.length !== 1 ||
|
||
extensionNames[0] !== PerMessageDeflate.extensionName
|
||
) {
|
||
const message = 'Server indicated an extension that was not requested';
|
||
abortHandshake(websocket, socket, message);
|
||
return;
|
||
}
|
||
|
||
try {
|
||
perMessageDeflate.accept(extensions[PerMessageDeflate.extensionName]);
|
||
} catch (err) {
|
||
const message = 'Invalid Sec-WebSocket-Extensions header';
|
||
abortHandshake(websocket, socket, message);
|
||
return;
|
||
}
|
||
|
||
websocket._extensions[PerMessageDeflate.extensionName] =
|
||
perMessageDeflate;
|
||
}
|
||
|
||
websocket.setSocket(socket, head, {
|
||
generateMask: opts.generateMask,
|
||
maxPayload: opts.maxPayload,
|
||
skipUTF8Validation: opts.skipUTF8Validation
|
||
});
|
||
});
|
||
|
||
if (opts.finishRequest) {
|
||
opts.finishRequest(req, websocket);
|
||
} else {
|
||
req.end();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Emit the `'error'` and `'close'` events.
|
||
*
|
||
* @param {WebSocket} websocket The WebSocket instance
|
||
* @param {Error} The error to emit
|
||
* @private
|
||
*/
|
||
function emitErrorAndClose(websocket, err) {
|
||
websocket._readyState = WebSocket.CLOSING;
|
||
websocket.emit('error', err);
|
||
websocket.emitClose();
|
||
}
|
||
|
||
/**
|
||
* Create a `net.Socket` and initiate a connection.
|
||
*
|
||
* @param {Object} options Connection options
|
||
* @return {net.Socket} The newly created socket used to start the connection
|
||
* @private
|
||
*/
|
||
function netConnect(options) {
|
||
options.path = options.socketPath;
|
||
return net.connect(options);
|
||
}
|
||
|
||
/**
|
||
* Create a `tls.TLSSocket` and initiate a connection.
|
||
*
|
||
* @param {Object} options Connection options
|
||
* @return {tls.TLSSocket} The newly created socket used to start the connection
|
||
* @private
|
||
*/
|
||
function tlsConnect(options) {
|
||
options.path = undefined;
|
||
|
||
if (!options.servername && options.servername !== '') {
|
||
options.servername = net.isIP(options.host) ? '' : options.host;
|
||
}
|
||
|
||
return tls.connect(options);
|
||
}
|
||
|
||
/**
|
||
* Abort the handshake and emit an error.
|
||
*
|
||
* @param {WebSocket} websocket The WebSocket instance
|
||
* @param {(http.ClientRequest|net.Socket|tls.Socket)} stream The request to
|
||
* abort or the socket to destroy
|
||
* @param {String} message The error message
|
||
* @private
|
||
*/
|
||
function abortHandshake(websocket, stream, message) {
|
||
websocket._readyState = WebSocket.CLOSING;
|
||
|
||
const err = new Error(message);
|
||
Error.captureStackTrace(err, abortHandshake);
|
||
|
||
if (stream.setHeader) {
|
||
stream[kAborted] = true;
|
||
stream.abort();
|
||
|
||
if (stream.socket && !stream.socket.destroyed) {
|
||
//
|
||
// On Node.js >= 14.3.0 `request.abort()` does not destroy the socket if
|
||
// called after the request completed. See
|
||
// https://github.com/websockets/ws/issues/1869.
|
||
//
|
||
stream.socket.destroy();
|
||
}
|
||
|
||
process.nextTick(emitErrorAndClose, websocket, err);
|
||
} else {
|
||
stream.destroy(err);
|
||
stream.once('error', websocket.emit.bind(websocket, 'error'));
|
||
stream.once('close', websocket.emitClose.bind(websocket));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Handle cases where the `ping()`, `pong()`, or `send()` methods are called
|
||
* when the `readyState` attribute is `CLOSING` or `CLOSED`.
|
||
*
|
||
* @param {WebSocket} websocket The WebSocket instance
|
||
* @param {*} [data] The data to send
|
||
* @param {Function} [cb] Callback
|
||
* @private
|
||
*/
|
||
function sendAfterClose(websocket, data, cb) {
|
||
if (data) {
|
||
const length = toBuffer(data).length;
|
||
|
||
//
|
||
// The `_bufferedAmount` property is used only when the peer is a client and
|
||
// the opening handshake fails. Under these circumstances, in fact, the
|
||
// `setSocket()` method is not called, so the `_socket` and `_sender`
|
||
// properties are set to `null`.
|
||
//
|
||
if (websocket._socket) websocket._sender._bufferedBytes += length;
|
||
else websocket._bufferedAmount += length;
|
||
}
|
||
|
||
if (cb) {
|
||
const err = new Error(
|
||
`WebSocket is not open: readyState ${websocket.readyState} ` +
|
||
`(${readyStates[websocket.readyState]})`
|
||
);
|
||
process.nextTick(cb, err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* The listener of the `Receiver` `'conclude'` event.
|
||
*
|
||
* @param {Number} code The status code
|
||
* @param {Buffer} reason The reason for closing
|
||
* @private
|
||
*/
|
||
function receiverOnConclude(code, reason) {
|
||
const websocket = this[kWebSocket];
|
||
|
||
websocket._closeFrameReceived = true;
|
||
websocket._closeMessage = reason;
|
||
websocket._closeCode = code;
|
||
|
||
if (websocket._socket[kWebSocket] === undefined) return;
|
||
|
||
websocket._socket.removeListener('data', socketOnData);
|
||
process.nextTick(resume, websocket._socket);
|
||
|
||
if (code === 1005) websocket.close();
|
||
else websocket.close(code, reason);
|
||
}
|
||
|
||
/**
|
||
* The listener of the `Receiver` `'drain'` event.
|
||
*
|
||
* @private
|
||
*/
|
||
function receiverOnDrain() {
|
||
const websocket = this[kWebSocket];
|
||
|
||
if (!websocket.isPaused) websocket._socket.resume();
|
||
}
|
||
|
||
/**
|
||
* The listener of the `Receiver` `'error'` event.
|
||
*
|
||
* @param {(RangeError|Error)} err The emitted error
|
||
* @private
|
||
*/
|
||
function receiverOnError(err) {
|
||
const websocket = this[kWebSocket];
|
||
|
||
if (websocket._socket[kWebSocket] !== undefined) {
|
||
websocket._socket.removeListener('data', socketOnData);
|
||
|
||
//
|
||
// On Node.js < 14.0.0 the `'error'` event is emitted synchronously. See
|
||
// https://github.com/websockets/ws/issues/1940.
|
||
//
|
||
process.nextTick(resume, websocket._socket);
|
||
|
||
websocket.close(err[kStatusCode]);
|
||
}
|
||
|
||
websocket.emit('error', err);
|
||
}
|
||
|
||
/**
|
||
* The listener of the `Receiver` `'finish'` event.
|
||
*
|
||
* @private
|
||
*/
|
||
function receiverOnFinish() {
|
||
this[kWebSocket].emitClose();
|
||
}
|
||
|
||
/**
|
||
* The listener of the `Receiver` `'message'` event.
|
||
*
|
||
* @param {Buffer|ArrayBuffer|Buffer[])} data The message
|
||
* @param {Boolean} isBinary Specifies whether the message is binary or not
|
||
* @private
|
||
*/
|
||
function receiverOnMessage(data, isBinary) {
|
||
this[kWebSocket].emit('message', data, isBinary);
|
||
}
|
||
|
||
/**
|
||
* The listener of the `Receiver` `'ping'` event.
|
||
*
|
||
* @param {Buffer} data The data included in the ping frame
|
||
* @private
|
||
*/
|
||
function receiverOnPing(data) {
|
||
const websocket = this[kWebSocket];
|
||
|
||
websocket.pong(data, !websocket._isServer, NOOP);
|
||
websocket.emit('ping', data);
|
||
}
|
||
|
||
/**
|
||
* The listener of the `Receiver` `'pong'` event.
|
||
*
|
||
* @param {Buffer} data The data included in the pong frame
|
||
* @private
|
||
*/
|
||
function receiverOnPong(data) {
|
||
this[kWebSocket].emit('pong', data);
|
||
}
|
||
|
||
/**
|
||
* Resume a readable stream
|
||
*
|
||
* @param {Readable} stream The readable stream
|
||
* @private
|
||
*/
|
||
function resume(stream) {
|
||
stream.resume();
|
||
}
|
||
|
||
/**
|
||
* The listener of the `net.Socket` `'close'` event.
|
||
*
|
||
* @private
|
||
*/
|
||
function socketOnClose() {
|
||
const websocket = this[kWebSocket];
|
||
|
||
this.removeListener('close', socketOnClose);
|
||
this.removeListener('data', socketOnData);
|
||
this.removeListener('end', socketOnEnd);
|
||
|
||
websocket._readyState = WebSocket.CLOSING;
|
||
|
||
let chunk;
|
||
|
||
//
|
||
// The close frame might not have been received or the `'end'` event emitted,
|
||
// for example, if the socket was destroyed due to an error. Ensure that the
|
||
// `receiver` stream is closed after writing any remaining buffered data to
|
||
// it. If the readable side of the socket is in flowing mode then there is no
|
||
// buffered data as everything has been already written and `readable.read()`
|
||
// will return `null`. If instead, the socket is paused, any possible buffered
|
||
// data will be read as a single chunk.
|
||
//
|
||
if (
|
||
!this._readableState.endEmitted &&
|
||
!websocket._closeFrameReceived &&
|
||
!websocket._receiver._writableState.errorEmitted &&
|
||
(chunk = websocket._socket.read()) !== null
|
||
) {
|
||
websocket._receiver.write(chunk);
|
||
}
|
||
|
||
websocket._receiver.end();
|
||
|
||
this[kWebSocket] = undefined;
|
||
|
||
clearTimeout(websocket._closeTimer);
|
||
|
||
if (
|
||
websocket._receiver._writableState.finished ||
|
||
websocket._receiver._writableState.errorEmitted
|
||
) {
|
||
websocket.emitClose();
|
||
} else {
|
||
websocket._receiver.on('error', receiverOnFinish);
|
||
websocket._receiver.on('finish', receiverOnFinish);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* The listener of the `net.Socket` `'data'` event.
|
||
*
|
||
* @param {Buffer} chunk A chunk of data
|
||
* @private
|
||
*/
|
||
function socketOnData(chunk) {
|
||
if (!this[kWebSocket]._receiver.write(chunk)) {
|
||
this.pause();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* The listener of the `net.Socket` `'end'` event.
|
||
*
|
||
* @private
|
||
*/
|
||
function socketOnEnd() {
|
||
const websocket = this[kWebSocket];
|
||
|
||
websocket._readyState = WebSocket.CLOSING;
|
||
websocket._receiver.end();
|
||
this.end();
|
||
}
|
||
|
||
/**
|
||
* The listener of the `net.Socket` `'error'` event.
|
||
*
|
||
* @private
|
||
*/
|
||
function socketOnError() {
|
||
const websocket = this[kWebSocket];
|
||
|
||
this.removeListener('error', socketOnError);
|
||
this.on('error', NOOP);
|
||
|
||
if (websocket) {
|
||
websocket._readyState = WebSocket.CLOSING;
|
||
this.destroy();
|
||
}
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 6 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("events");
|
||
|
||
/***/ }),
|
||
/* 7 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("https");
|
||
|
||
/***/ }),
|
||
/* 8 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("net");
|
||
|
||
/***/ }),
|
||
/* 9 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("tls");
|
||
|
||
/***/ }),
|
||
/* 10 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("crypto");
|
||
|
||
/***/ }),
|
||
/* 11 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("stream");
|
||
|
||
/***/ }),
|
||
/* 12 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const zlib = __webpack_require__(13);
|
||
|
||
const bufferUtil = __webpack_require__(14);
|
||
const Limiter = __webpack_require__(16);
|
||
const { kStatusCode } = __webpack_require__(15);
|
||
|
||
const FastBuffer = Buffer[Symbol.species];
|
||
const TRAILER = Buffer.from([0x00, 0x00, 0xff, 0xff]);
|
||
const kPerMessageDeflate = Symbol('permessage-deflate');
|
||
const kTotalLength = Symbol('total-length');
|
||
const kCallback = Symbol('callback');
|
||
const kBuffers = Symbol('buffers');
|
||
const kError = Symbol('error');
|
||
|
||
//
|
||
// We limit zlib concurrency, which prevents severe memory fragmentation
|
||
// as documented in https://github.com/nodejs/node/issues/8871#issuecomment-250915913
|
||
// and https://github.com/websockets/ws/issues/1202
|
||
//
|
||
// Intentionally global; it's the global thread pool that's an issue.
|
||
//
|
||
let zlibLimiter;
|
||
|
||
/**
|
||
* permessage-deflate implementation.
|
||
*/
|
||
class PerMessageDeflate {
|
||
/**
|
||
* Creates a PerMessageDeflate instance.
|
||
*
|
||
* @param {Object} [options] Configuration options
|
||
* @param {(Boolean|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 !== undefined ? this._options.threshold : 1024;
|
||
this._isServer = !!isServer;
|
||
this._deflate = null;
|
||
this._inflate = null;
|
||
|
||
this.params = null;
|
||
|
||
if (!zlibLimiter) {
|
||
const concurrency =
|
||
this._options.concurrencyLimit !== undefined
|
||
? this._options.concurrencyLimit
|
||
: 10;
|
||
zlibLimiter = new Limiter(concurrency);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @type {String}
|
||
*/
|
||
static get extensionName() {
|
||
return 'permessage-deflate';
|
||
}
|
||
|
||
/**
|
||
* Create an extension negotiation offer.
|
||
*
|
||
* @return {Object} Extension parameters
|
||
* @public
|
||
*/
|
||
offer() {
|
||
const params = {};
|
||
|
||
if (this._options.serverNoContextTakeover) {
|
||
params.server_no_context_takeover = true;
|
||
}
|
||
if (this._options.clientNoContextTakeover) {
|
||
params.client_no_context_takeover = true;
|
||
}
|
||
if (this._options.serverMaxWindowBits) {
|
||
params.server_max_window_bits = this._options.serverMaxWindowBits;
|
||
}
|
||
if (this._options.clientMaxWindowBits) {
|
||
params.client_max_window_bits = this._options.clientMaxWindowBits;
|
||
} else if (this._options.clientMaxWindowBits == null) {
|
||
params.client_max_window_bits = true;
|
||
}
|
||
|
||
return params;
|
||
}
|
||
|
||
/**
|
||
* Accept an extension negotiation offer/response.
|
||
*
|
||
* @param {Array} configurations The extension negotiation offers/reponse
|
||
* @return {Object} Accepted configuration
|
||
* @public
|
||
*/
|
||
accept(configurations) {
|
||
configurations = this.normalizeParams(configurations);
|
||
|
||
this.params = this._isServer
|
||
? this.acceptAsServer(configurations)
|
||
: this.acceptAsClient(configurations);
|
||
|
||
return this.params;
|
||
}
|
||
|
||
/**
|
||
* Releases all resources used by the extension.
|
||
*
|
||
* @public
|
||
*/
|
||
cleanup() {
|
||
if (this._inflate) {
|
||
this._inflate.close();
|
||
this._inflate = null;
|
||
}
|
||
|
||
if (this._deflate) {
|
||
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];
|
||
|
||
if (err) {
|
||
this._inflate.close();
|
||
this._inflate = null;
|
||
callback(err);
|
||
return;
|
||
}
|
||
|
||
const data = 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, data);
|
||
});
|
||
}
|
||
|
||
/**
|
||
* 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) {
|
||
//
|
||
// The deflate stream was closed while data was being processed.
|
||
//
|
||
return;
|
||
}
|
||
|
||
let data = bufferUtil.concat(
|
||
this._deflate[kBuffers],
|
||
this._deflate[kTotalLength]
|
||
);
|
||
|
||
if (fin) {
|
||
data = new FastBuffer(data.buffer, data.byteOffset, data.length - 4);
|
||
}
|
||
|
||
//
|
||
// Ensure that the callback will not be called again in
|
||
// `PerMessageDeflate#cleanup()`.
|
||
//
|
||
this._deflate[kCallback] = null;
|
||
|
||
this._deflate[kTotalLength] = 0;
|
||
this._deflate[kBuffers] = [];
|
||
|
||
if (fin && this.params[`${endpoint}_no_context_takeover`]) {
|
||
this._deflate.reset();
|
||
}
|
||
|
||
callback(null, data);
|
||
});
|
||
}
|
||
}
|
||
|
||
module.exports = PerMessageDeflate;
|
||
|
||
/**
|
||
* The listener of the `zlib.DeflateRaw` stream `'data'` event.
|
||
*
|
||
* @param {Buffer} chunk A chunk of data
|
||
* @private
|
||
*/
|
||
function deflateOnData(chunk) {
|
||
this[kBuffers].push(chunk);
|
||
this[kTotalLength] += chunk.length;
|
||
}
|
||
|
||
/**
|
||
* The listener of the `zlib.InflateRaw` stream `'data'` event.
|
||
*
|
||
* @param {Buffer} chunk A chunk of data
|
||
* @private
|
||
*/
|
||
function inflateOnData(chunk) {
|
||
this[kTotalLength] += chunk.length;
|
||
|
||
if (
|
||
this[kPerMessageDeflate]._maxPayload < 1 ||
|
||
this[kTotalLength] <= this[kPerMessageDeflate]._maxPayload
|
||
) {
|
||
this[kBuffers].push(chunk);
|
||
return;
|
||
}
|
||
|
||
this[kError] = new RangeError('Max payload size exceeded');
|
||
this[kError].code = 'WS_ERR_UNSUPPORTED_MESSAGE_LENGTH';
|
||
this[kError][kStatusCode] = 1009;
|
||
this.removeListener('data', inflateOnData);
|
||
this.reset();
|
||
}
|
||
|
||
/**
|
||
* The listener of the `zlib.InflateRaw` stream `'error'` event.
|
||
*
|
||
* @param {Error} err The emitted error
|
||
* @private
|
||
*/
|
||
function inflateOnError(err) {
|
||
//
|
||
// There is no need to call `Zlib#close()` as the handle is automatically
|
||
// closed when an error is emitted.
|
||
//
|
||
this[kPerMessageDeflate]._inflate = null;
|
||
err[kStatusCode] = 1007;
|
||
this[kCallback](err);
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 13 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("zlib");
|
||
|
||
/***/ }),
|
||
/* 14 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const { EMPTY_BUFFER } = __webpack_require__(15);
|
||
|
||
const FastBuffer = Buffer[Symbol.species];
|
||
|
||
/**
|
||
* Merges an array of buffers into a new buffer.
|
||
*
|
||
* @param {Buffer[]} list The array of buffers to concat
|
||
* @param {Number} totalLength The total length of buffers in the list
|
||
* @return {Buffer} The resulting buffer
|
||
* @public
|
||
*/
|
||
function concat(list, totalLength) {
|
||
if (list.length === 0) return EMPTY_BUFFER;
|
||
if (list.length === 1) return list[0];
|
||
|
||
const target = Buffer.allocUnsafe(totalLength);
|
||
let offset = 0;
|
||
|
||
for (let i = 0; i < list.length; i++) {
|
||
const buf = list[i];
|
||
target.set(buf, offset);
|
||
offset += buf.length;
|
||
}
|
||
|
||
if (offset < totalLength) {
|
||
return new FastBuffer(target.buffer, target.byteOffset, offset);
|
||
}
|
||
|
||
return target;
|
||
}
|
||
|
||
/**
|
||
* Masks a buffer using the given mask.
|
||
*
|
||
* @param {Buffer} source The buffer to mask
|
||
* @param {Buffer} mask The mask to use
|
||
* @param {Buffer} output The buffer where to store the result
|
||
* @param {Number} offset The offset at which to start writing
|
||
* @param {Number} length The number of bytes to mask.
|
||
* @public
|
||
*/
|
||
function _mask(source, mask, output, offset, length) {
|
||
for (let i = 0; i < length; i++) {
|
||
output[offset + i] = source[i] ^ mask[i & 3];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Unmasks a buffer using the given mask.
|
||
*
|
||
* @param {Buffer} buffer The buffer to unmask
|
||
* @param {Buffer} mask The mask to use
|
||
* @public
|
||
*/
|
||
function _unmask(buffer, mask) {
|
||
for (let i = 0; i < buffer.length; i++) {
|
||
buffer[i] ^= mask[i & 3];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Converts a buffer to an `ArrayBuffer`.
|
||
*
|
||
* @param {Buffer} buf The buffer to convert
|
||
* @return {ArrayBuffer} Converted buffer
|
||
* @public
|
||
*/
|
||
function toArrayBuffer(buf) {
|
||
if (buf.length === buf.buffer.byteLength) {
|
||
return buf.buffer;
|
||
}
|
||
|
||
return buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.length);
|
||
}
|
||
|
||
/**
|
||
* Converts `data` to a `Buffer`.
|
||
*
|
||
* @param {*} data The data to convert
|
||
* @return {Buffer} The buffer
|
||
* @throws {TypeError}
|
||
* @public
|
||
*/
|
||
function toBuffer(data) {
|
||
toBuffer.readOnly = true;
|
||
|
||
if (Buffer.isBuffer(data)) return data;
|
||
|
||
let buf;
|
||
|
||
if (data instanceof ArrayBuffer) {
|
||
buf = new FastBuffer(data);
|
||
} else if (ArrayBuffer.isView(data)) {
|
||
buf = new FastBuffer(data.buffer, data.byteOffset, data.byteLength);
|
||
} else {
|
||
buf = Buffer.from(data);
|
||
toBuffer.readOnly = false;
|
||
}
|
||
|
||
return buf;
|
||
}
|
||
|
||
module.exports = {
|
||
concat,
|
||
mask: _mask,
|
||
toArrayBuffer,
|
||
toBuffer,
|
||
unmask: _unmask
|
||
};
|
||
|
||
/* istanbul ignore else */
|
||
if (!process.env.WS_NO_BUFFER_UTIL) {
|
||
try {
|
||
const bufferUtil = __webpack_require__(Object(function webpackMissingModule() { var e = new Error("Cannot find module 'bufferutil'"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));
|
||
|
||
module.exports.mask = function (source, mask, output, offset, length) {
|
||
if (length < 48) _mask(source, mask, output, offset, length);
|
||
else bufferUtil.mask(source, mask, output, offset, length);
|
||
};
|
||
|
||
module.exports.unmask = function (buffer, mask) {
|
||
if (buffer.length < 32) _unmask(buffer, mask);
|
||
else bufferUtil.unmask(buffer, mask);
|
||
};
|
||
} catch (e) {
|
||
// Continue regardless of the error.
|
||
}
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 15 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
module.exports = {
|
||
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: () => {}
|
||
};
|
||
|
||
|
||
/***/ }),
|
||
/* 16 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const kDone = Symbol('kDone');
|
||
const kRun = Symbol('kRun');
|
||
|
||
/**
|
||
* A very simple job queue with adjustable concurrency. Adapted from
|
||
* https://github.com/STRML/async-limiter
|
||
*/
|
||
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]);
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = Limiter;
|
||
|
||
|
||
/***/ }),
|
||
/* 17 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const { Writable } = __webpack_require__(11);
|
||
|
||
const PerMessageDeflate = __webpack_require__(12);
|
||
const {
|
||
BINARY_TYPES,
|
||
EMPTY_BUFFER,
|
||
kStatusCode,
|
||
kWebSocket
|
||
} = __webpack_require__(15);
|
||
const { concat, toArrayBuffer, unmask } = __webpack_require__(14);
|
||
const { isValidStatusCode, isValidUTF8 } = __webpack_require__(18);
|
||
|
||
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;
|
||
|
||
/**
|
||
* HyBi Receiver implementation.
|
||
*
|
||
* @extends Writable
|
||
*/
|
||
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[0];
|
||
this._extensions = options.extensions || {};
|
||
this._isServer = !!options.isServer;
|
||
this._maxPayload = options.maxPayload | 0;
|
||
this._skipUTF8Validation = !!options.skipUTF8Validation;
|
||
this[kWebSocket] = undefined;
|
||
|
||
this._bufferedBytes = 0;
|
||
this._buffers = [];
|
||
|
||
this._compressed = false;
|
||
this._payloadLength = 0;
|
||
this._mask = undefined;
|
||
this._fragmented = 0;
|
||
this._masked = false;
|
||
this._fin = false;
|
||
this._opcode = 0;
|
||
|
||
this._totalPayloadLength = 0;
|
||
this._messageLength = 0;
|
||
this._fragments = [];
|
||
|
||
this._state = GET_INFO;
|
||
this._loop = false;
|
||
}
|
||
|
||
/**
|
||
* Implements `Writable.prototype._write()`.
|
||
*
|
||
* @param {Buffer} chunk The chunk of data to write
|
||
* @param {String} encoding The character encoding of `chunk`
|
||
* @param {Function} cb Callback
|
||
* @private
|
||
*/
|
||
_write(chunk, encoding, cb) {
|
||
if (this._opcode === 0x08 && this._state == GET_INFO) return cb();
|
||
|
||
this._bufferedBytes += chunk.length;
|
||
this._buffers.push(chunk);
|
||
this.startLoop(cb);
|
||
}
|
||
|
||
/**
|
||
* Consumes `n` bytes from the buffered data.
|
||
*
|
||
* @param {Number} n The number of bytes to consume
|
||
* @return {Buffer} The consumed bytes
|
||
* @private
|
||
*/
|
||
consume(n) {
|
||
this._bufferedBytes -= n;
|
||
|
||
if (n === this._buffers[0].length) return this._buffers.shift();
|
||
|
||
if (n < this._buffers[0].length) {
|
||
const buf = this._buffers[0];
|
||
this._buffers[0] = 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:
|
||
// `INFLATING`
|
||
this._loop = false;
|
||
return;
|
||
}
|
||
} while (this._loop);
|
||
|
||
cb(err);
|
||
}
|
||
|
||
/**
|
||
* Reads the first two bytes of a frame.
|
||
*
|
||
* @return {(RangeError|undefined)} A possible error
|
||
* @private
|
||
*/
|
||
getInfo() {
|
||
if (this._bufferedBytes < 2) {
|
||
this._loop = false;
|
||
return;
|
||
}
|
||
|
||
const buf = this.consume(2);
|
||
|
||
if ((buf[0] & 0x30) !== 0x00) {
|
||
this._loop = false;
|
||
return error(
|
||
RangeError,
|
||
'RSV2 and RSV3 must be clear',
|
||
true,
|
||
1002,
|
||
'WS_ERR_UNEXPECTED_RSV_2_3'
|
||
);
|
||
}
|
||
|
||
const compressed = (buf[0] & 0x40) === 0x40;
|
||
|
||
if (compressed && !this._extensions[PerMessageDeflate.extensionName]) {
|
||
this._loop = false;
|
||
return error(
|
||
RangeError,
|
||
'RSV1 must be clear',
|
||
true,
|
||
1002,
|
||
'WS_ERR_UNEXPECTED_RSV_1'
|
||
);
|
||
}
|
||
|
||
this._fin = (buf[0] & 0x80) === 0x80;
|
||
this._opcode = buf[0] & 0x0f;
|
||
this._payloadLength = buf[1] & 0x7f;
|
||
|
||
if (this._opcode === 0x00) {
|
||
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 === 0x01 || this._opcode === 0x02) {
|
||
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 > 0x07 && this._opcode < 0x0b) {
|
||
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 > 0x7d ||
|
||
(this._opcode === 0x08 && 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] & 0x80) === 0x80;
|
||
|
||
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);
|
||
|
||
//
|
||
// The maximum safe integer in JavaScript is 2^53 - 1. An error is returned
|
||
// if payload length is greater than this number.
|
||
//
|
||
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 < 0x08) {
|
||
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;
|
||
|
||
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 > 0x07) return this.controlMessage(data);
|
||
|
||
if (this._compressed) {
|
||
this._state = INFLATING;
|
||
this.decompress(data, cb);
|
||
return;
|
||
}
|
||
|
||
if (data.length) {
|
||
//
|
||
// This message is not compressed so its length is the sum of the payload
|
||
// length of all fragments.
|
||
//
|
||
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.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 === 0x08) {
|
||
this._loop = false;
|
||
|
||
if (data.length === 0) {
|
||
this.emit('conclude', 1005, EMPTY_BUFFER);
|
||
this.end();
|
||
} else {
|
||
const code = data.readUInt16BE(0);
|
||
|
||
if (!isValidStatusCode(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 === 0x09) {
|
||
this.emit('ping', data);
|
||
} else {
|
||
this.emit('pong', data);
|
||
}
|
||
|
||
this._state = GET_INFO;
|
||
}
|
||
}
|
||
|
||
module.exports = Receiver;
|
||
|
||
/**
|
||
* Builds an error object.
|
||
*
|
||
* @param {function(new:Error|RangeError)} ErrorCtor The error constructor
|
||
* @param {String} message The error message
|
||
* @param {Boolean} prefix Specifies whether or not to add a default prefix to
|
||
* `message`
|
||
* @param {Number} statusCode The status code
|
||
* @param {String} errorCode The exposed error code
|
||
* @return {(Error|RangeError)} The error
|
||
* @private
|
||
*/
|
||
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] = statusCode;
|
||
return err;
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 18 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const { isUtf8 } = __webpack_require__(19);
|
||
|
||
//
|
||
// Allowed token characters:
|
||
//
|
||
// '!', '#', '$', '%', '&', ''', '*', '+', '-',
|
||
// '.', 0-9, A-Z, '^', '_', '`', a-z, '|', '~'
|
||
//
|
||
// tokenChars[32] === 0 // ' '
|
||
// tokenChars[33] === 1 // '!'
|
||
// tokenChars[34] === 0 // '"'
|
||
// ...
|
||
//
|
||
// prettier-ignore
|
||
const tokenChars = [
|
||
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
|
||
];
|
||
|
||
/**
|
||
* Checks if a status code is allowed in a close frame.
|
||
*
|
||
* @param {Number} code The status code
|
||
* @return {Boolean} `true` if the status code is valid, else `false`
|
||
* @public
|
||
*/
|
||
function isValidStatusCode(code) {
|
||
return (
|
||
(code >= 1000 &&
|
||
code <= 1014 &&
|
||
code !== 1004 &&
|
||
code !== 1005 &&
|
||
code !== 1006) ||
|
||
(code >= 3000 && code <= 4999)
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Checks if a given buffer contains only correct UTF-8.
|
||
* Ported from https://www.cl.cam.ac.uk/%7Emgk25/ucs/utf8_check.c by
|
||
* Markus Kuhn.
|
||
*
|
||
* @param {Buffer} buf The buffer to check
|
||
* @return {Boolean} `true` if `buf` contains only correct UTF-8, else `false`
|
||
* @public
|
||
*/
|
||
function _isValidUTF8(buf) {
|
||
const len = buf.length;
|
||
let i = 0;
|
||
|
||
while (i < len) {
|
||
if ((buf[i] & 0x80) === 0) {
|
||
// 0xxxxxxx
|
||
i++;
|
||
} else if ((buf[i] & 0xe0) === 0xc0) {
|
||
// 110xxxxx 10xxxxxx
|
||
if (
|
||
i + 1 === len ||
|
||
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||
(buf[i] & 0xfe) === 0xc0 // Overlong
|
||
) {
|
||
return false;
|
||
}
|
||
|
||
i += 2;
|
||
} else if ((buf[i] & 0xf0) === 0xe0) {
|
||
// 1110xxxx 10xxxxxx 10xxxxxx
|
||
if (
|
||
i + 2 >= len ||
|
||
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||
(buf[i + 2] & 0xc0) !== 0x80 ||
|
||
(buf[i] === 0xe0 && (buf[i + 1] & 0xe0) === 0x80) || // Overlong
|
||
(buf[i] === 0xed && (buf[i + 1] & 0xe0) === 0xa0) // Surrogate (U+D800 - U+DFFF)
|
||
) {
|
||
return false;
|
||
}
|
||
|
||
i += 3;
|
||
} else if ((buf[i] & 0xf8) === 0xf0) {
|
||
// 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
|
||
if (
|
||
i + 3 >= len ||
|
||
(buf[i + 1] & 0xc0) !== 0x80 ||
|
||
(buf[i + 2] & 0xc0) !== 0x80 ||
|
||
(buf[i + 3] & 0xc0) !== 0x80 ||
|
||
(buf[i] === 0xf0 && (buf[i + 1] & 0xf0) === 0x80) || // Overlong
|
||
(buf[i] === 0xf4 && buf[i + 1] > 0x8f) ||
|
||
buf[i] > 0xf4 // > U+10FFFF
|
||
) {
|
||
return false;
|
||
}
|
||
|
||
i += 4;
|
||
} else {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
}
|
||
|
||
module.exports = {
|
||
isValidStatusCode,
|
||
isValidUTF8: _isValidUTF8,
|
||
tokenChars
|
||
};
|
||
|
||
if (isUtf8) {
|
||
module.exports.isValidUTF8 = function (buf) {
|
||
return buf.length < 24 ? _isValidUTF8(buf) : isUtf8(buf);
|
||
};
|
||
} /* istanbul ignore else */ else if (!process.env.WS_NO_UTF_8_VALIDATE) {
|
||
try {
|
||
const isValidUTF8 = __webpack_require__(Object(function webpackMissingModule() { var e = new Error("Cannot find module 'utf-8-validate'"); e.code = 'MODULE_NOT_FOUND'; throw e; }()));
|
||
|
||
module.exports.isValidUTF8 = function (buf) {
|
||
return buf.length < 32 ? _isValidUTF8(buf) : isValidUTF8(buf);
|
||
};
|
||
} catch (e) {
|
||
// Continue regardless of the error.
|
||
}
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 19 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("buffer");
|
||
|
||
/***/ }),
|
||
/* 20 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
|
||
|
||
|
||
|
||
const net = __webpack_require__(8);
|
||
const tls = __webpack_require__(9);
|
||
const { randomFillSync } = __webpack_require__(10);
|
||
|
||
const PerMessageDeflate = __webpack_require__(12);
|
||
const { EMPTY_BUFFER } = __webpack_require__(15);
|
||
const { isValidStatusCode } = __webpack_require__(18);
|
||
const { mask: applyMask, toBuffer } = __webpack_require__(14);
|
||
|
||
const kByteLength = Symbol('kByteLength');
|
||
const maskBuffer = Buffer.alloc(4);
|
||
|
||
/**
|
||
* HyBi Sender implementation.
|
||
*/
|
||
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 mask;
|
||
let merge = false;
|
||
let offset = 2;
|
||
let skipMasking = false;
|
||
|
||
if (options.mask) {
|
||
mask = options.maskBuffer || maskBuffer;
|
||
|
||
if (options.generateMask) {
|
||
options.generateMask(mask);
|
||
} else {
|
||
randomFillSync(mask, 0, 4);
|
||
}
|
||
|
||
skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
|
||
offset = 6;
|
||
}
|
||
|
||
let dataLength;
|
||
|
||
if (typeof data === 'string') {
|
||
if (
|
||
(!options.mask || skipMasking) &&
|
||
options[kByteLength] !== undefined
|
||
) {
|
||
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 | 0x80 : options.opcode;
|
||
if (options.rsv1) target[0] |= 0x40;
|
||
|
||
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] |= 0x80;
|
||
target[offset - 4] = mask[0];
|
||
target[offset - 3] = mask[1];
|
||
target[offset - 2] = mask[2];
|
||
target[offset - 1] = mask[3];
|
||
|
||
if (skipMasking) return [target, data];
|
||
|
||
if (merge) {
|
||
applyMask(data, mask, target, offset, dataLength);
|
||
return [target];
|
||
}
|
||
|
||
applyMask(data, mask, 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, mask, cb) {
|
||
let buf;
|
||
|
||
if (code === undefined) {
|
||
buf = EMPTY_BUFFER;
|
||
} else if (typeof code !== 'number' || !isValidStatusCode(code)) {
|
||
throw new TypeError('First argument must be a valid error code number');
|
||
} else if (data === undefined || !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,
|
||
maskBuffer: this._maskBuffer,
|
||
opcode: 0x08,
|
||
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, mask, cb) {
|
||
let byteLength;
|
||
let readOnly;
|
||
|
||
if (typeof data === 'string') {
|
||
byteLength = Buffer.byteLength(data);
|
||
readOnly = false;
|
||
} else {
|
||
data = toBuffer(data);
|
||
byteLength = data.length;
|
||
readOnly = toBuffer.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,
|
||
maskBuffer: this._maskBuffer,
|
||
opcode: 0x09,
|
||
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, mask, cb) {
|
||
let byteLength;
|
||
let readOnly;
|
||
|
||
if (typeof data === 'string') {
|
||
byteLength = Buffer.byteLength(data);
|
||
readOnly = false;
|
||
} else {
|
||
data = toBuffer(data);
|
||
byteLength = data.length;
|
||
readOnly = toBuffer.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,
|
||
maskBuffer: this._maskBuffer,
|
||
opcode: 0x0a,
|
||
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.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(data);
|
||
byteLength = data.length;
|
||
readOnly = toBuffer.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.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);
|
||
}
|
||
}
|
||
}
|
||
|
||
module.exports = Sender;
|
||
|
||
|
||
/***/ }),
|
||
/* 21 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const { kForOnEventAttribute, kListener } = __webpack_require__(15);
|
||
|
||
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 representing an event.
|
||
*/
|
||
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 representing a close event.
|
||
*
|
||
* @extends Event
|
||
*/
|
||
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 === undefined ? 0 : options.code;
|
||
this[kReason] = options.reason === undefined ? '' : options.reason;
|
||
this[kWasClean] = options.wasClean === undefined ? 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 representing an error event.
|
||
*
|
||
* @extends Event
|
||
*/
|
||
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 === undefined ? null : options.error;
|
||
this[kMessage] = options.message === undefined ? '' : 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 representing a message event.
|
||
*
|
||
* @extends Event
|
||
*/
|
||
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 === undefined ? null : options.data;
|
||
}
|
||
|
||
/**
|
||
* @type {*}
|
||
*/
|
||
get data() {
|
||
return this[kData];
|
||
}
|
||
}
|
||
|
||
Object.defineProperty(MessageEvent.prototype, 'data', { enumerable: true });
|
||
|
||
/**
|
||
* This provides methods for emulating the `EventTarget` interface. It's not
|
||
* meant to be used directly.
|
||
*
|
||
* @mixin
|
||
*/
|
||
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] &&
|
||
listener[kListener] === handler &&
|
||
!listener[kForOnEventAttribute]
|
||
) {
|
||
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(error) {
|
||
const event = new ErrorEvent('error', {
|
||
error,
|
||
message: error.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] = !!options[kForOnEventAttribute];
|
||
wrapper[kListener] = 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] === handler && !listener[kForOnEventAttribute]) {
|
||
this.removeListener(type, listener);
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
};
|
||
|
||
module.exports = {
|
||
CloseEvent,
|
||
ErrorEvent,
|
||
Event,
|
||
EventTarget,
|
||
MessageEvent
|
||
};
|
||
|
||
/**
|
||
* Call an event listener
|
||
*
|
||
* @param {(Function|Object)} listener The listener to call
|
||
* @param {*} thisArg The value to use as `this`` when calling the listener
|
||
* @param {Event} event The event to pass to the listener
|
||
* @private
|
||
*/
|
||
function callListener(listener, thisArg, event) {
|
||
if (typeof listener === 'object' && listener.handleEvent) {
|
||
listener.handleEvent.call(listener, event);
|
||
} else {
|
||
listener.call(thisArg, event);
|
||
}
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 22 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const { tokenChars } = __webpack_require__(18);
|
||
|
||
/**
|
||
* Adds an offer to the map of extension offers or a parameter to the map of
|
||
* parameters.
|
||
*
|
||
* @param {Object} dest The map of extension offers or parameters
|
||
* @param {String} name The extension or parameter name
|
||
* @param {(Object|Boolean|String)} elem The extension parameters or the
|
||
* parameter value
|
||
* @private
|
||
*/
|
||
function push(dest, name, elem) {
|
||
if (dest[name] === undefined) dest[name] = [elem];
|
||
else dest[name].push(elem);
|
||
}
|
||
|
||
/**
|
||
* Parses the `Sec-WebSocket-Extensions` header into an object.
|
||
*
|
||
* @param {String} header The field value of the header
|
||
* @return {Object} The parsed object
|
||
* @public
|
||
*/
|
||
function parse(header) {
|
||
const offers = Object.create(null);
|
||
let params = 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 === undefined) {
|
||
if (end === -1 && tokenChars[code] === 1) {
|
||
if (start === -1) start = i;
|
||
} else if (
|
||
i !== 0 &&
|
||
(code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
|
||
) {
|
||
if (end === -1 && start !== -1) end = i;
|
||
} else if (code === 0x3b /* ';' */ || code === 0x2c /* ',' */) {
|
||
if (start === -1) {
|
||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||
}
|
||
|
||
if (end === -1) end = i;
|
||
const name = header.slice(start, end);
|
||
if (code === 0x2c) {
|
||
push(offers, name, params);
|
||
params = Object.create(null);
|
||
} else {
|
||
extensionName = name;
|
||
}
|
||
|
||
start = end = -1;
|
||
} else {
|
||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||
}
|
||
} else if (paramName === undefined) {
|
||
if (end === -1 && tokenChars[code] === 1) {
|
||
if (start === -1) start = i;
|
||
} else if (code === 0x20 || code === 0x09) {
|
||
if (end === -1 && start !== -1) end = i;
|
||
} else if (code === 0x3b || code === 0x2c) {
|
||
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 === 0x2c) {
|
||
push(offers, extensionName, params);
|
||
params = Object.create(null);
|
||
extensionName = undefined;
|
||
}
|
||
|
||
start = end = -1;
|
||
} else if (code === 0x3d /* '=' */ && start !== -1 && end === -1) {
|
||
paramName = header.slice(start, i);
|
||
start = end = -1;
|
||
} else {
|
||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||
}
|
||
} else {
|
||
//
|
||
// The value of a quoted-string after unescaping must conform to the
|
||
// token ABNF, so only token characters are valid.
|
||
// Ref: https://tools.ietf.org/html/rfc6455#section-9.1
|
||
//
|
||
if (isEscaping) {
|
||
if (tokenChars[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[code] === 1) {
|
||
if (start === -1) start = i;
|
||
} else if (code === 0x22 /* '"' */ && start !== -1) {
|
||
inQuotes = false;
|
||
end = i;
|
||
} else if (code === 0x5c /* '\' */) {
|
||
isEscaping = true;
|
||
} else {
|
||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||
}
|
||
} else if (code === 0x22 && header.charCodeAt(i - 1) === 0x3d) {
|
||
inQuotes = true;
|
||
} else if (end === -1 && tokenChars[code] === 1) {
|
||
if (start === -1) start = i;
|
||
} else if (start !== -1 && (code === 0x20 || code === 0x09)) {
|
||
if (end === -1) end = i;
|
||
} else if (code === 0x3b || code === 0x2c) {
|
||
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 === 0x2c) {
|
||
push(offers, extensionName, params);
|
||
params = Object.create(null);
|
||
extensionName = undefined;
|
||
}
|
||
|
||
paramName = undefined;
|
||
start = end = -1;
|
||
} else {
|
||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (start === -1 || inQuotes || code === 0x20 || code === 0x09) {
|
||
throw new SyntaxError('Unexpected end of input');
|
||
}
|
||
|
||
if (end === -1) end = i;
|
||
const token = header.slice(start, end);
|
||
if (extensionName === undefined) {
|
||
push(offers, token, params);
|
||
} else {
|
||
if (paramName === undefined) {
|
||
push(params, token, true);
|
||
} else if (mustUnescape) {
|
||
push(params, paramName, token.replace(/\\/g, ''));
|
||
} else {
|
||
push(params, paramName, token);
|
||
}
|
||
push(offers, extensionName, params);
|
||
}
|
||
|
||
return offers;
|
||
}
|
||
|
||
/**
|
||
* Builds the `Sec-WebSocket-Extensions` header field value.
|
||
*
|
||
* @param {Object} extensions The map of extensions and parameters to format
|
||
* @return {String} A string representing the given object
|
||
* @public
|
||
*/
|
||
function format(extensions) {
|
||
return Object.keys(extensions)
|
||
.map((extension) => {
|
||
let configurations = extensions[extension];
|
||
if (!Array.isArray(configurations)) configurations = [configurations];
|
||
return configurations
|
||
.map((params) => {
|
||
return [extension]
|
||
.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(', ');
|
||
}
|
||
|
||
module.exports = { format, parse };
|
||
|
||
|
||
/***/ }),
|
||
/* 23 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const { Duplex } = __webpack_require__(11);
|
||
|
||
/**
|
||
* Emits the `'close'` event on a stream.
|
||
*
|
||
* @param {Duplex} stream The stream.
|
||
* @private
|
||
*/
|
||
function emitClose(stream) {
|
||
stream.emit('close');
|
||
}
|
||
|
||
/**
|
||
* The listener of the `'end'` event.
|
||
*
|
||
* @private
|
||
*/
|
||
function duplexOnEnd() {
|
||
if (!this.destroyed && this._writableState.finished) {
|
||
this.destroy();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* The listener of the `'error'` event.
|
||
*
|
||
* @param {Error} err The error
|
||
* @private
|
||
*/
|
||
function duplexOnError(err) {
|
||
this.removeListener('error', duplexOnError);
|
||
this.destroy();
|
||
if (this.listenerCount('error') === 0) {
|
||
// Do not suppress the throwing behavior.
|
||
this.emit('error', err);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Wraps a `WebSocket` in a duplex stream.
|
||
*
|
||
* @param {WebSocket} ws The `WebSocket` to wrap
|
||
* @param {Object} [options] The options for the `Duplex` constructor
|
||
* @return {Duplex} The duplex stream
|
||
* @public
|
||
*/
|
||
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 error(err) {
|
||
if (duplex.destroyed) return;
|
||
|
||
// Prevent `ws.terminate()` from being called by `duplex._destroy()`.
|
||
//
|
||
// - If the `'error'` event is emitted before the `'open'` event, then
|
||
// `ws.terminate()` is a noop as no socket is assigned.
|
||
// - Otherwise, the error is re-emitted by the listener of the `'error'`
|
||
// event of the `Receiver` object. The listener already closes the
|
||
// connection by calling `ws.close()`. This allows a close frame to be
|
||
// sent to the other peer. If `ws.terminate()` is called right after this,
|
||
// then the close frame might not be sent.
|
||
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, duplex);
|
||
return;
|
||
}
|
||
|
||
let called = false;
|
||
|
||
ws.once('error', function error(err) {
|
||
called = true;
|
||
callback(err);
|
||
});
|
||
|
||
ws.once('close', function close() {
|
||
if (!called) callback(err);
|
||
process.nextTick(emitClose, duplex);
|
||
});
|
||
|
||
if (terminateOnDestroy) ws.terminate();
|
||
};
|
||
|
||
duplex._final = function (callback) {
|
||
if (ws.readyState === ws.CONNECTING) {
|
||
ws.once('open', function open() {
|
||
duplex._final(callback);
|
||
});
|
||
return;
|
||
}
|
||
|
||
// If the value of the `_socket` property is `null` it means that `ws` is a
|
||
// client websocket and the handshake failed. In fact, when this happens, a
|
||
// socket is never assigned to the websocket. Wait for the `'error'` event
|
||
// that will be emitted by the websocket.
|
||
if (ws._socket === null) return;
|
||
|
||
if (ws._socket._writableState.finished) {
|
||
callback();
|
||
if (duplex._readableState.endEmitted) duplex.destroy();
|
||
} else {
|
||
ws._socket.once('finish', function finish() {
|
||
// `duplex` is not destroyed here because the `'end'` event will be
|
||
// emitted on `duplex` after this `'finish'` event. The EOF signaling
|
||
// `null` chunk is, in fact, pushed when the websocket emits `'close'`.
|
||
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;
|
||
}
|
||
|
||
module.exports = createWebSocketStream;
|
||
|
||
|
||
/***/ }),
|
||
/* 24 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls|https$" }] */
|
||
|
||
|
||
|
||
const EventEmitter = __webpack_require__(6);
|
||
const http = __webpack_require__(2);
|
||
const https = __webpack_require__(7);
|
||
const net = __webpack_require__(8);
|
||
const tls = __webpack_require__(9);
|
||
const { createHash } = __webpack_require__(10);
|
||
|
||
const extension = __webpack_require__(22);
|
||
const PerMessageDeflate = __webpack_require__(12);
|
||
const subprotocol = __webpack_require__(25);
|
||
const WebSocket = __webpack_require__(5);
|
||
const { GUID, kWebSocket } = __webpack_require__(15);
|
||
|
||
const keyRegex = /^[+/0-9A-Za-z]{22}==$/;
|
||
|
||
const RUNNING = 0;
|
||
const CLOSING = 1;
|
||
const CLOSED = 2;
|
||
|
||
/**
|
||
* Class representing a WebSocket server.
|
||
*
|
||
* @extends EventEmitter
|
||
*/
|
||
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,
|
||
...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 = 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;
|
||
|
||
//
|
||
// The HTTP/S server was created internally. Close it, and rely on its
|
||
// `'close'` event.
|
||
//
|
||
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 = new Set();
|
||
|
||
if (secWebSocketProtocol !== undefined) {
|
||
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 !== undefined
|
||
) {
|
||
const perMessageDeflate = new PerMessageDeflate(
|
||
this.options.perMessageDeflate,
|
||
true,
|
||
this.options.maxPayload
|
||
);
|
||
|
||
try {
|
||
const offers = extension.parse(secWebSocketExtensions);
|
||
|
||
if (offers[PerMessageDeflate.extensionName]) {
|
||
perMessageDeflate.accept(offers[PerMessageDeflate.extensionName]);
|
||
extensions[PerMessageDeflate.extensionName] = perMessageDeflate;
|
||
}
|
||
} catch (err) {
|
||
const message =
|
||
'Invalid or unacceptable Sec-WebSocket-Extensions header';
|
||
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
|
||
return;
|
||
}
|
||
}
|
||
|
||
//
|
||
// Optionally call external client verification handler.
|
||
//
|
||
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) {
|
||
//
|
||
// Destroy the socket if the client has already sent a FIN packet.
|
||
//
|
||
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) {
|
||
//
|
||
// Optionally call external protocol selection handler.
|
||
//
|
||
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[PerMessageDeflate.extensionName]) {
|
||
const params = extensions[PerMessageDeflate.extensionName].params;
|
||
const value = extension.format({
|
||
[PerMessageDeflate.extensionName]: [params]
|
||
});
|
||
headers.push(`Sec-WebSocket-Extensions: ${value}`);
|
||
ws._extensions = extensions;
|
||
}
|
||
|
||
//
|
||
// Allow external modification/inspection of handshake headers.
|
||
//
|
||
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);
|
||
}
|
||
}
|
||
|
||
module.exports = WebSocketServer;
|
||
|
||
/**
|
||
* Add event listeners on an `EventEmitter` using a map of <event, listener>
|
||
* pairs.
|
||
*
|
||
* @param {EventEmitter} server The event emitter
|
||
* @param {Object.<String, Function>} map The listeners to add
|
||
* @return {Function} A function that will remove the added listeners when
|
||
* called
|
||
* @private
|
||
*/
|
||
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]);
|
||
}
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Emit a `'close'` event on an `EventEmitter`.
|
||
*
|
||
* @param {EventEmitter} server The event emitter
|
||
* @private
|
||
*/
|
||
function emitClose(server) {
|
||
server._state = CLOSED;
|
||
server.emit('close');
|
||
}
|
||
|
||
/**
|
||
* Handle socket errors.
|
||
*
|
||
* @private
|
||
*/
|
||
function socketOnError() {
|
||
this.destroy();
|
||
}
|
||
|
||
/**
|
||
* Close the connection when preconditions are not fulfilled.
|
||
*
|
||
* @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
|
||
* @param {Number} code The HTTP response status code
|
||
* @param {String} [message] The HTTP response body
|
||
* @param {Object} [headers] Additional HTTP response headers
|
||
* @private
|
||
*/
|
||
function abortHandshake(socket, code, message, headers) {
|
||
//
|
||
// The socket is writable unless the user destroyed or ended it before calling
|
||
// `server.handleUpgrade()` or in the `verifyClient` function, which is a user
|
||
// error. Handling this does not make much sense as the worst that can happen
|
||
// is that some of the data written by the user might be discarded due to the
|
||
// call to `socket.end()` below, which triggers an `'error'` event that in
|
||
// turn causes the socket to be destroyed.
|
||
//
|
||
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\n` +
|
||
Object.keys(headers)
|
||
.map((h) => `${h}: ${headers[h]}`)
|
||
.join('\r\n') +
|
||
'\r\n\r\n' +
|
||
message
|
||
);
|
||
}
|
||
|
||
/**
|
||
* Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least
|
||
* one listener for it, otherwise call `abortHandshake()`.
|
||
*
|
||
* @param {WebSocketServer} server The WebSocket server
|
||
* @param {http.IncomingMessage} req The request object
|
||
* @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
|
||
* @param {Number} code The HTTP response status code
|
||
* @param {String} message The HTTP response body
|
||
* @private
|
||
*/
|
||
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);
|
||
}
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 25 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
|
||
const { tokenChars } = __webpack_require__(18);
|
||
|
||
/**
|
||
* Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names.
|
||
*
|
||
* @param {String} header The field value of the header
|
||
* @return {Set} The subprotocol names
|
||
* @public
|
||
*/
|
||
function parse(header) {
|
||
const protocols = 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 === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
|
||
) {
|
||
if (end === -1 && start !== -1) end = i;
|
||
} else if (code === 0x2c /* ',' */) {
|
||
if (start === -1) {
|
||
throw new SyntaxError(`Unexpected character at index ${i}`);
|
||
}
|
||
|
||
if (end === -1) end = i;
|
||
|
||
const protocol = header.slice(start, end);
|
||
|
||
if (protocols.has(protocol)) {
|
||
throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
|
||
}
|
||
|
||
protocols.add(protocol);
|
||
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;
|
||
}
|
||
|
||
module.exports = { parse };
|
||
|
||
|
||
/***/ }),
|
||
/* 26 */
|
||
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||
exports.serverIndexPage = exports.index401 = exports.serverStaticFile = void 0;
|
||
const node_fs_1 = __webpack_require__(27);
|
||
const node_path_1 = __webpack_require__(28);
|
||
const pretty_cache_header_1 = __webpack_require__(29);
|
||
const mimeLookup = {
|
||
'.js': 'application/javascript,charset=UTF-8',
|
||
'.html': 'text/html,charset=UTF-8',
|
||
'.css': 'text/css; charset=UTF-8',
|
||
};
|
||
const staticPath = 'dist/apps/cf-page/';
|
||
const file401 = 'dist/apps/node-vless/assets/401.html';
|
||
let filepath = null;
|
||
function serverStaticFile(req, resp) {
|
||
const url = new URL(req.url, `http://${req.headers['host']}`);
|
||
let fileurl = url.pathname;
|
||
fileurl = (0, node_path_1.join)(staticPath, fileurl);
|
||
console.log('....', fileurl);
|
||
filepath = (0, node_path_1.resolve)(fileurl);
|
||
console.log(filepath);
|
||
if ((0, node_fs_1.existsSync)(filepath)) {
|
||
let fileExt = (0, node_path_1.extname)(filepath);
|
||
console.log('fileExt', fileExt);
|
||
let mimeType = mimeLookup[fileExt];
|
||
resp.writeHead(200, {
|
||
'Content-Type': mimeType,
|
||
'Cache-Control': (0, pretty_cache_header_1.cacheHeader)({
|
||
public: true,
|
||
maxAge: '1year',
|
||
staleWhileRevalidate: '1year',
|
||
}),
|
||
});
|
||
return (0, node_fs_1.createReadStream)(filepath).pipe(resp);
|
||
}
|
||
else {
|
||
resp.writeHead(404);
|
||
resp.write('not found');
|
||
resp.end();
|
||
return resp;
|
||
}
|
||
}
|
||
exports.serverStaticFile = serverStaticFile;
|
||
function index401(req, resp) {
|
||
const file401Path = (0, node_path_1.resolve)(file401);
|
||
if ((0, node_fs_1.existsSync)(file401Path)) {
|
||
(0, node_fs_1.createReadStream)(file401Path).pipe(resp);
|
||
}
|
||
else {
|
||
resp.writeHead(401);
|
||
resp.write('UUID env not set');
|
||
resp.end();
|
||
}
|
||
}
|
||
exports.index401 = index401;
|
||
function serverIndexPage(req, resp, uuid) {
|
||
// if()
|
||
}
|
||
exports.serverIndexPage = serverIndexPage;
|
||
|
||
|
||
/***/ }),
|
||
/* 27 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("node:fs");
|
||
|
||
/***/ }),
|
||
/* 28 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("node:path");
|
||
|
||
/***/ }),
|
||
/* 29 */
|
||
/***/ ((module, __unused_webpack_exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
var __create = Object.create;
|
||
var __defProp = Object.defineProperty;
|
||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||
var __getProtoOf = Object.getPrototypeOf;
|
||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||
var __export = (target, all) => {
|
||
for (var name in all)
|
||
__defProp(target, name, { get: all[name], enumerable: true });
|
||
};
|
||
var __copyProps = (to, from, except, desc) => {
|
||
if (from && typeof from === "object" || typeof from === "function") {
|
||
for (let key of __getOwnPropNames(from))
|
||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||
}
|
||
return to;
|
||
};
|
||
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
||
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
||
mod
|
||
));
|
||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||
|
||
// src/index.ts
|
||
var src_exports = {};
|
||
__export(src_exports, {
|
||
cacheHeader: () => cacheHeader
|
||
});
|
||
module.exports = __toCommonJS(src_exports);
|
||
|
||
// src/cache-header.ts
|
||
var import_timestring = __toESM(__webpack_require__(30), 1);
|
||
function cacheHeader(params) {
|
||
const transformed = Object.entries(params).reduce((acc, [key, value]) => {
|
||
const kebabKey = key.replace(/[A-Z]/g, (char) => "-" + char.toLowerCase());
|
||
return typeof value === "string" || value === true ? [...acc, value === true ? kebabKey : `${kebabKey}=${(0, import_timestring.default)(value)}`] : acc;
|
||
}, []);
|
||
return transformed.join(", ");
|
||
}
|
||
// Annotate the CommonJS export names for ESM import in node:
|
||
0 && (0);
|
||
//# sourceMappingURL=index.cjs.map
|
||
|
||
/***/ }),
|
||
/* 30 */
|
||
/***/ ((module) => {
|
||
|
||
/**
|
||
* Exports
|
||
*/
|
||
|
||
module.exports = parseTimestring
|
||
|
||
/**
|
||
* Default options to use when parsing a timestring
|
||
*
|
||
* @type {Object}
|
||
*/
|
||
|
||
const DEFAULT_OPTS = {
|
||
hoursPerDay: 24,
|
||
daysPerWeek: 7,
|
||
weeksPerMonth: 4,
|
||
monthsPerYear: 12,
|
||
daysPerYear: 365.25
|
||
}
|
||
|
||
/**
|
||
* Map of accepted strings to unit
|
||
*
|
||
* @type {Object}
|
||
*/
|
||
|
||
const UNIT_MAP = {
|
||
ms: ['ms', 'milli', 'millisecond', 'milliseconds'],
|
||
s: ['s', 'sec', 'secs', 'second', 'seconds'],
|
||
m: ['m', 'min', 'mins', 'minute', 'minutes'],
|
||
h: ['h', 'hr', 'hrs', 'hour', 'hours'],
|
||
d: ['d', 'day', 'days'],
|
||
w: ['w', 'week', 'weeks'],
|
||
mth: ['mon', 'mth', 'mths', 'month', 'months'],
|
||
y: ['y', 'yr', 'yrs', 'year', 'years']
|
||
}
|
||
|
||
/**
|
||
* Parse a timestring
|
||
*
|
||
* @param {String} string
|
||
* @param {String} returnUnit
|
||
* @param {Object} opts
|
||
* @return {Number}
|
||
*/
|
||
|
||
function parseTimestring (string, returnUnit, opts) {
|
||
opts = Object.assign({}, DEFAULT_OPTS, opts || {})
|
||
|
||
let totalSeconds = 0
|
||
let unitValues = getUnitValues(opts)
|
||
let groups = string
|
||
.toLowerCase()
|
||
.replace(/[^.\w+-]+/g, '')
|
||
.match(/[-+]?[0-9.]+[a-z]+/g)
|
||
|
||
if (groups === null) {
|
||
throw new Error(`The string [${string}] could not be parsed by timestring`)
|
||
}
|
||
|
||
groups.forEach(group => {
|
||
let value = group.match(/[0-9.]+/g)[0]
|
||
let unit = group.match(/[a-z]+/g)[0]
|
||
|
||
totalSeconds += getSeconds(value, unit, unitValues)
|
||
})
|
||
|
||
if (returnUnit) {
|
||
return convert(totalSeconds, returnUnit, unitValues)
|
||
}
|
||
|
||
return totalSeconds
|
||
}
|
||
|
||
/**
|
||
* Get unit values based on the passed options
|
||
*
|
||
* @param {Object} opts
|
||
* @returns {Object}
|
||
*/
|
||
|
||
function getUnitValues (opts) {
|
||
let unitValues = {
|
||
ms: 0.001,
|
||
s: 1,
|
||
m: 60,
|
||
h: 3600
|
||
}
|
||
|
||
unitValues.d = opts.hoursPerDay * unitValues.h
|
||
unitValues.w = opts.daysPerWeek * unitValues.d
|
||
unitValues.mth = (opts.daysPerYear / opts.monthsPerYear) * unitValues.d
|
||
unitValues.y = opts.daysPerYear * unitValues.d
|
||
|
||
return unitValues
|
||
}
|
||
|
||
/**
|
||
* Get the key for a unit
|
||
*
|
||
* @param {String} unit
|
||
* @returns {String}
|
||
*/
|
||
|
||
function getUnitKey (unit) {
|
||
for (let key of Object.keys(UNIT_MAP)) {
|
||
if (UNIT_MAP[key].indexOf(unit) > -1) {
|
||
return key
|
||
}
|
||
}
|
||
|
||
throw new Error(`The unit [${unit}] is not supported by timestring`)
|
||
}
|
||
|
||
/**
|
||
* Get the number of seconds for a value, based on the unit
|
||
*
|
||
* @param {Number} value
|
||
* @param {String} unit
|
||
* @param {Object} unitValues
|
||
* @returns {Number}
|
||
*/
|
||
|
||
function getSeconds (value, unit, unitValues) {
|
||
return value * unitValues[getUnitKey(unit)]
|
||
}
|
||
|
||
/**
|
||
* Convert a value from its existing unit to a new unit
|
||
*
|
||
* @param {Number} value
|
||
* @param {String} unit
|
||
* @param {Object} unitValues
|
||
* @returns {Number}
|
||
*/
|
||
|
||
function convert (value, unit, unitValues) {
|
||
return value / unitValues[getUnitKey(unit)]
|
||
}
|
||
|
||
|
||
/***/ }),
|
||
/* 31 */
|
||
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
// ESM COMPAT FLAG
|
||
__webpack_require__.r(__webpack_exports__);
|
||
|
||
// EXPORTS
|
||
__webpack_require__.d(__webpack_exports__, {
|
||
"NIL": () => (/* reexport */ nil),
|
||
"parse": () => (/* reexport */ esm_node_parse),
|
||
"stringify": () => (/* reexport */ esm_node_stringify),
|
||
"v1": () => (/* reexport */ esm_node_v1),
|
||
"v3": () => (/* reexport */ esm_node_v3),
|
||
"v4": () => (/* reexport */ esm_node_v4),
|
||
"v5": () => (/* reexport */ esm_node_v5),
|
||
"validate": () => (/* reexport */ esm_node_validate),
|
||
"version": () => (/* reexport */ esm_node_version)
|
||
});
|
||
|
||
// EXTERNAL MODULE: external "crypto"
|
||
var external_crypto_ = __webpack_require__(10);
|
||
var external_crypto_default = /*#__PURE__*/__webpack_require__.n(external_crypto_);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/rng.js
|
||
|
||
const rnds8Pool = new Uint8Array(256); // # of random values to pre-allocate
|
||
|
||
let poolPtr = rnds8Pool.length;
|
||
function rng() {
|
||
if (poolPtr > rnds8Pool.length - 16) {
|
||
external_crypto_default().randomFillSync(rnds8Pool);
|
||
poolPtr = 0;
|
||
}
|
||
|
||
return rnds8Pool.slice(poolPtr, poolPtr += 16);
|
||
}
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/regex.js
|
||
/* harmony default export */ const regex = (/^(?:[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}|00000000-0000-0000-0000-000000000000)$/i);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/validate.js
|
||
|
||
|
||
function validate(uuid) {
|
||
return typeof uuid === 'string' && regex.test(uuid);
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_validate = (validate);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/stringify.js
|
||
|
||
/**
|
||
* Convert array of 16 byte values to UUID string format of the form:
|
||
* XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
|
||
*/
|
||
|
||
const byteToHex = [];
|
||
|
||
for (let i = 0; i < 256; ++i) {
|
||
byteToHex.push((i + 0x100).toString(16).slice(1));
|
||
}
|
||
|
||
function unsafeStringify(arr, offset = 0) {
|
||
// Note: Be careful editing this code! It's been tuned for performance
|
||
// and works in ways you may not expect. See https://github.com/uuidjs/uuid/pull/434
|
||
return (byteToHex[arr[offset + 0]] + byteToHex[arr[offset + 1]] + byteToHex[arr[offset + 2]] + byteToHex[arr[offset + 3]] + '-' + byteToHex[arr[offset + 4]] + byteToHex[arr[offset + 5]] + '-' + byteToHex[arr[offset + 6]] + byteToHex[arr[offset + 7]] + '-' + byteToHex[arr[offset + 8]] + byteToHex[arr[offset + 9]] + '-' + byteToHex[arr[offset + 10]] + byteToHex[arr[offset + 11]] + byteToHex[arr[offset + 12]] + byteToHex[arr[offset + 13]] + byteToHex[arr[offset + 14]] + byteToHex[arr[offset + 15]]).toLowerCase();
|
||
}
|
||
|
||
function stringify(arr, offset = 0) {
|
||
const uuid = unsafeStringify(arr, offset); // Consistency check for valid UUID. If this throws, it's likely due to one
|
||
// of the following:
|
||
// - One or more input array values don't map to a hex octet (leading to
|
||
// "undefined" in the uuid)
|
||
// - Invalid input values for the RFC `version` or `variant` fields
|
||
|
||
if (!esm_node_validate(uuid)) {
|
||
throw TypeError('Stringified UUID is invalid');
|
||
}
|
||
|
||
return uuid;
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_stringify = (stringify);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/v1.js
|
||
|
||
// **`v1()` - Generate time-based UUID**
|
||
//
|
||
// Inspired by https://github.com/LiosK/UUID.js
|
||
// and http://docs.python.org/library/uuid.html
|
||
|
||
let _nodeId;
|
||
|
||
let _clockseq; // Previous uuid creation time
|
||
|
||
|
||
let _lastMSecs = 0;
|
||
let _lastNSecs = 0; // See https://github.com/uuidjs/uuid for API details
|
||
|
||
function v1(options, buf, offset) {
|
||
let i = buf && offset || 0;
|
||
const b = buf || new Array(16);
|
||
options = options || {};
|
||
let node = options.node || _nodeId;
|
||
let clockseq = options.clockseq !== undefined ? options.clockseq : _clockseq; // node and clockseq need to be initialized to random values if they're not
|
||
// specified. We do this lazily to minimize issues related to insufficient
|
||
// system entropy. See #189
|
||
|
||
if (node == null || clockseq == null) {
|
||
const seedBytes = options.random || (options.rng || rng)();
|
||
|
||
if (node == null) {
|
||
// Per 4.5, create and 48-bit node id, (47 random bits + multicast bit = 1)
|
||
node = _nodeId = [seedBytes[0] | 0x01, seedBytes[1], seedBytes[2], seedBytes[3], seedBytes[4], seedBytes[5]];
|
||
}
|
||
|
||
if (clockseq == null) {
|
||
// Per 4.2.2, randomize (14 bit) clockseq
|
||
clockseq = _clockseq = (seedBytes[6] << 8 | seedBytes[7]) & 0x3fff;
|
||
}
|
||
} // UUID timestamps are 100 nano-second units since the Gregorian epoch,
|
||
// (1582-10-15 00:00). JSNumbers aren't precise enough for this, so
|
||
// time is handled internally as 'msecs' (integer milliseconds) and 'nsecs'
|
||
// (100-nanoseconds offset from msecs) since unix epoch, 1970-01-01 00:00.
|
||
|
||
|
||
let msecs = options.msecs !== undefined ? options.msecs : Date.now(); // Per 4.2.1.2, use count of uuid's generated during the current clock
|
||
// cycle to simulate higher resolution clock
|
||
|
||
let nsecs = options.nsecs !== undefined ? options.nsecs : _lastNSecs + 1; // Time since last uuid creation (in msecs)
|
||
|
||
const dt = msecs - _lastMSecs + (nsecs - _lastNSecs) / 10000; // Per 4.2.1.2, Bump clockseq on clock regression
|
||
|
||
if (dt < 0 && options.clockseq === undefined) {
|
||
clockseq = clockseq + 1 & 0x3fff;
|
||
} // Reset nsecs if clock regresses (new clockseq) or we've moved onto a new
|
||
// time interval
|
||
|
||
|
||
if ((dt < 0 || msecs > _lastMSecs) && options.nsecs === undefined) {
|
||
nsecs = 0;
|
||
} // Per 4.2.1.2 Throw error if too many uuids are requested
|
||
|
||
|
||
if (nsecs >= 10000) {
|
||
throw new Error("uuid.v1(): Can't create more than 10M uuids/sec");
|
||
}
|
||
|
||
_lastMSecs = msecs;
|
||
_lastNSecs = nsecs;
|
||
_clockseq = clockseq; // Per 4.1.4 - Convert from unix epoch to Gregorian epoch
|
||
|
||
msecs += 12219292800000; // `time_low`
|
||
|
||
const tl = ((msecs & 0xfffffff) * 10000 + nsecs) % 0x100000000;
|
||
b[i++] = tl >>> 24 & 0xff;
|
||
b[i++] = tl >>> 16 & 0xff;
|
||
b[i++] = tl >>> 8 & 0xff;
|
||
b[i++] = tl & 0xff; // `time_mid`
|
||
|
||
const tmh = msecs / 0x100000000 * 10000 & 0xfffffff;
|
||
b[i++] = tmh >>> 8 & 0xff;
|
||
b[i++] = tmh & 0xff; // `time_high_and_version`
|
||
|
||
b[i++] = tmh >>> 24 & 0xf | 0x10; // include version
|
||
|
||
b[i++] = tmh >>> 16 & 0xff; // `clock_seq_hi_and_reserved` (Per 4.2.2 - include variant)
|
||
|
||
b[i++] = clockseq >>> 8 | 0x80; // `clock_seq_low`
|
||
|
||
b[i++] = clockseq & 0xff; // `node`
|
||
|
||
for (let n = 0; n < 6; ++n) {
|
||
b[i + n] = node[n];
|
||
}
|
||
|
||
return buf || unsafeStringify(b);
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_v1 = (v1);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/parse.js
|
||
|
||
|
||
function parse(uuid) {
|
||
if (!esm_node_validate(uuid)) {
|
||
throw TypeError('Invalid UUID');
|
||
}
|
||
|
||
let v;
|
||
const arr = new Uint8Array(16); // Parse ########-....-....-....-............
|
||
|
||
arr[0] = (v = parseInt(uuid.slice(0, 8), 16)) >>> 24;
|
||
arr[1] = v >>> 16 & 0xff;
|
||
arr[2] = v >>> 8 & 0xff;
|
||
arr[3] = v & 0xff; // Parse ........-####-....-....-............
|
||
|
||
arr[4] = (v = parseInt(uuid.slice(9, 13), 16)) >>> 8;
|
||
arr[5] = v & 0xff; // Parse ........-....-####-....-............
|
||
|
||
arr[6] = (v = parseInt(uuid.slice(14, 18), 16)) >>> 8;
|
||
arr[7] = v & 0xff; // Parse ........-....-....-####-............
|
||
|
||
arr[8] = (v = parseInt(uuid.slice(19, 23), 16)) >>> 8;
|
||
arr[9] = v & 0xff; // Parse ........-....-....-....-############
|
||
// (Use "/" to avoid 32-bit truncation when bit-shifting high-order bytes)
|
||
|
||
arr[10] = (v = parseInt(uuid.slice(24, 36), 16)) / 0x10000000000 & 0xff;
|
||
arr[11] = v / 0x100000000 & 0xff;
|
||
arr[12] = v >>> 24 & 0xff;
|
||
arr[13] = v >>> 16 & 0xff;
|
||
arr[14] = v >>> 8 & 0xff;
|
||
arr[15] = v & 0xff;
|
||
return arr;
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_parse = (parse);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/v35.js
|
||
|
||
|
||
|
||
function stringToBytes(str) {
|
||
str = unescape(encodeURIComponent(str)); // UTF8 escape
|
||
|
||
const bytes = [];
|
||
|
||
for (let i = 0; i < str.length; ++i) {
|
||
bytes.push(str.charCodeAt(i));
|
||
}
|
||
|
||
return bytes;
|
||
}
|
||
|
||
const DNS = '6ba7b810-9dad-11d1-80b4-00c04fd430c8';
|
||
const URL = '6ba7b811-9dad-11d1-80b4-00c04fd430c8';
|
||
function v35(name, version, hashfunc) {
|
||
function generateUUID(value, namespace, buf, offset) {
|
||
var _namespace;
|
||
|
||
if (typeof value === 'string') {
|
||
value = stringToBytes(value);
|
||
}
|
||
|
||
if (typeof namespace === 'string') {
|
||
namespace = esm_node_parse(namespace);
|
||
}
|
||
|
||
if (((_namespace = namespace) === null || _namespace === void 0 ? void 0 : _namespace.length) !== 16) {
|
||
throw TypeError('Namespace must be array-like (16 iterable integer values, 0-255)');
|
||
} // Compute hash of namespace and value, Per 4.3
|
||
// Future: Use spread syntax when supported on all platforms, e.g. `bytes =
|
||
// hashfunc([...namespace, ... value])`
|
||
|
||
|
||
let bytes = new Uint8Array(16 + value.length);
|
||
bytes.set(namespace);
|
||
bytes.set(value, namespace.length);
|
||
bytes = hashfunc(bytes);
|
||
bytes[6] = bytes[6] & 0x0f | version;
|
||
bytes[8] = bytes[8] & 0x3f | 0x80;
|
||
|
||
if (buf) {
|
||
offset = offset || 0;
|
||
|
||
for (let i = 0; i < 16; ++i) {
|
||
buf[offset + i] = bytes[i];
|
||
}
|
||
|
||
return buf;
|
||
}
|
||
|
||
return unsafeStringify(bytes);
|
||
} // Function#name is not settable on some platforms (#270)
|
||
|
||
|
||
try {
|
||
generateUUID.name = name; // eslint-disable-next-line no-empty
|
||
} catch (err) {} // For CommonJS default export support
|
||
|
||
|
||
generateUUID.DNS = DNS;
|
||
generateUUID.URL = URL;
|
||
return generateUUID;
|
||
}
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/md5.js
|
||
|
||
|
||
function md5(bytes) {
|
||
if (Array.isArray(bytes)) {
|
||
bytes = Buffer.from(bytes);
|
||
} else if (typeof bytes === 'string') {
|
||
bytes = Buffer.from(bytes, 'utf8');
|
||
}
|
||
|
||
return external_crypto_default().createHash('md5').update(bytes).digest();
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_md5 = (md5);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/v3.js
|
||
|
||
|
||
const v3 = v35('v3', 0x30, esm_node_md5);
|
||
/* harmony default export */ const esm_node_v3 = (v3);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/native.js
|
||
|
||
/* harmony default export */ const esm_node_native = ({
|
||
randomUUID: (external_crypto_default()).randomUUID
|
||
});
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/v4.js
|
||
|
||
|
||
|
||
|
||
function v4(options, buf, offset) {
|
||
if (esm_node_native.randomUUID && !buf && !options) {
|
||
return esm_node_native.randomUUID();
|
||
}
|
||
|
||
options = options || {};
|
||
const rnds = options.random || (options.rng || rng)(); // Per 4.4, set bits for version and `clock_seq_hi_and_reserved`
|
||
|
||
rnds[6] = rnds[6] & 0x0f | 0x40;
|
||
rnds[8] = rnds[8] & 0x3f | 0x80; // Copy bytes to buffer, if provided
|
||
|
||
if (buf) {
|
||
offset = offset || 0;
|
||
|
||
for (let i = 0; i < 16; ++i) {
|
||
buf[offset + i] = rnds[i];
|
||
}
|
||
|
||
return buf;
|
||
}
|
||
|
||
return unsafeStringify(rnds);
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_v4 = (v4);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/sha1.js
|
||
|
||
|
||
function sha1(bytes) {
|
||
if (Array.isArray(bytes)) {
|
||
bytes = Buffer.from(bytes);
|
||
} else if (typeof bytes === 'string') {
|
||
bytes = Buffer.from(bytes, 'utf8');
|
||
}
|
||
|
||
return external_crypto_default().createHash('sha1').update(bytes).digest();
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_sha1 = (sha1);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/v5.js
|
||
|
||
|
||
const v5 = v35('v5', 0x50, esm_node_sha1);
|
||
/* harmony default export */ const esm_node_v5 = (v5);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/nil.js
|
||
/* harmony default export */ const nil = ('00000000-0000-0000-0000-000000000000');
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/version.js
|
||
|
||
|
||
function version(uuid) {
|
||
if (!esm_node_validate(uuid)) {
|
||
throw TypeError('Invalid UUID');
|
||
}
|
||
|
||
return parseInt(uuid.slice(14, 15), 16);
|
||
}
|
||
|
||
/* harmony default export */ const esm_node_version = (version);
|
||
;// CONCATENATED MODULE: ../../node_modules/uuid/dist/esm-node/index.js
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
/***/ }),
|
||
/* 32 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("node:dns");
|
||
|
||
/***/ }),
|
||
/* 33 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("node:dgram");
|
||
|
||
/***/ }),
|
||
/* 34 */
|
||
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||
exports.vlessJs = exports.processVlessHeader = exports.closeWebSocket = exports.makeReadableWebSocketStream = exports.delay = void 0;
|
||
var vless_js_1 = __webpack_require__(35);
|
||
Object.defineProperty(exports, "delay", ({ enumerable: true, get: function () { return vless_js_1.delay; } }));
|
||
Object.defineProperty(exports, "makeReadableWebSocketStream", ({ enumerable: true, get: function () { return vless_js_1.makeReadableWebSocketStream; } }));
|
||
Object.defineProperty(exports, "closeWebSocket", ({ enumerable: true, get: function () { return vless_js_1.safeCloseWebSocket; } }));
|
||
Object.defineProperty(exports, "processVlessHeader", ({ enumerable: true, get: function () { return vless_js_1.processVlessHeader; } }));
|
||
Object.defineProperty(exports, "vlessJs", ({ enumerable: true, get: function () { return vless_js_1.vlessJs; } }));
|
||
|
||
|
||
/***/ }),
|
||
/* 35 */
|
||
/***/ ((__unused_webpack_module, exports, __webpack_require__) => {
|
||
|
||
"use strict";
|
||
|
||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||
exports.processVlessHeader = exports.safeCloseWebSocket = exports.makeReadableWebSocketStream = exports.delay = exports.vlessJs = void 0;
|
||
const tslib_1 = __webpack_require__(1);
|
||
const uuid_1 = __webpack_require__(31);
|
||
function vlessJs() {
|
||
return 'vless-js';
|
||
}
|
||
exports.vlessJs = vlessJs;
|
||
function delay(ms) {
|
||
return new Promise((resolve, rej) => {
|
||
setTimeout(resolve, ms);
|
||
});
|
||
}
|
||
exports.delay = delay;
|
||
function makeReadableWebSocketStream(ws, earlyDataHeader, log) {
|
||
let readableStreamCancel = false;
|
||
return new ReadableStream({
|
||
start(controller) {
|
||
ws.addEventListener('message', (e) => tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
// is stream is cancel, skip controller.enqueue
|
||
if (readableStreamCancel) {
|
||
return;
|
||
}
|
||
const vlessBuffer = e.data;
|
||
// console.log('MESSAGE', vlessBuffer);
|
||
// console.log(`message is ${vlessBuffer.byteLength}`);
|
||
// this is not backpressure, but backpressure is depends on underying websocket can pasue
|
||
// https://streams.spec.whatwg.org/#example-rs-push-backpressure
|
||
controller.enqueue(vlessBuffer);
|
||
}));
|
||
ws.addEventListener('error', (e) => {
|
||
log('socket has error');
|
||
readableStreamCancel = true;
|
||
controller.error(e);
|
||
});
|
||
ws.addEventListener('close', () => {
|
||
try {
|
||
log('webSocket is close');
|
||
// is stream is cancel, skip controller.close
|
||
if (readableStreamCancel) {
|
||
return;
|
||
}
|
||
controller.close();
|
||
}
|
||
catch (error) {
|
||
log(`websocketStream can't close DUE to `, error);
|
||
}
|
||
});
|
||
// header ws 0rtt
|
||
const { earlyData, error } = base64ToArrayBuffer(earlyDataHeader);
|
||
if (error) {
|
||
log(`earlyDataHeader has invaild base64`);
|
||
safeCloseWebSocket(ws);
|
||
return;
|
||
}
|
||
if (earlyData) {
|
||
controller.enqueue(earlyData);
|
||
}
|
||
},
|
||
pull(controller) {
|
||
// if ws can stop read if stream is full, we can implement backpressure
|
||
// https://streams.spec.whatwg.org/#example-rs-push-backpressure
|
||
},
|
||
cancel(reason) {
|
||
// TODO: log can be remove, if writestream has error, write stream will has log
|
||
log(`websocketStream is cancel DUE to `, reason);
|
||
if (readableStreamCancel) {
|
||
return;
|
||
}
|
||
readableStreamCancel = true;
|
||
safeCloseWebSocket(ws);
|
||
},
|
||
});
|
||
}
|
||
exports.makeReadableWebSocketStream = makeReadableWebSocketStream;
|
||
function base64ToArrayBuffer(base64Str) {
|
||
if (!base64Str) {
|
||
return { error: null };
|
||
}
|
||
try {
|
||
// go use modified Base64 for URL rfc4648 which js atob not support
|
||
base64Str = base64Str.replace(/-/g, '+').replace(/_/g, '/');
|
||
const decode = atob(base64Str);
|
||
const arryBuffer = Uint8Array.from(decode, (c) => c.charCodeAt(0));
|
||
return { earlyData: arryBuffer.buffer, error: null };
|
||
}
|
||
catch (error) {
|
||
return { error };
|
||
}
|
||
}
|
||
function safeCloseWebSocket(socket) {
|
||
try {
|
||
if (socket.readyState === socket.OPEN) {
|
||
socket.close();
|
||
}
|
||
}
|
||
catch (error) {
|
||
console.error('safeCloseWebSocket error', error);
|
||
}
|
||
}
|
||
exports.safeCloseWebSocket = safeCloseWebSocket;
|
||
//https://github.com/v2ray/v2ray-core/issues/2636
|
||
// 1 字节 16 字节 1 字节 M 字节 1 字节 2 字节 1 字节 S 字节 X 字节
|
||
// 协议版本 等价 UUID 附加信息长度 M (附加信息 ProtoBuf) 指令(udp/tcp) 端口 地址类型 地址 请求数据
|
||
// 00 00 01 01bb(443) 02(ip/host)
|
||
// 1 字节 1 字节 N 字节 Y 字节
|
||
// 协议版本,与请求的一致 附加信息长度 N 附加信息 ProtoBuf 响应数据
|
||
function processVlessHeader(vlessBuffer, userID
|
||
// uuidLib: any,
|
||
// lodash: any
|
||
) {
|
||
if (vlessBuffer.byteLength < 24) {
|
||
// console.log('invalid data');
|
||
// controller.error('invalid data');
|
||
return {
|
||
hasError: true,
|
||
message: 'invalid data',
|
||
};
|
||
}
|
||
const version = new Uint8Array(vlessBuffer.slice(0, 1));
|
||
let isValidUser = false;
|
||
let isUDP = false;
|
||
if ((0, uuid_1.stringify)(new Uint8Array(vlessBuffer.slice(1, 17))) === userID) {
|
||
isValidUser = true;
|
||
}
|
||
if (!isValidUser) {
|
||
// console.log('in valid user');
|
||
// controller.error('in valid user');
|
||
return {
|
||
hasError: true,
|
||
message: 'invalid user',
|
||
};
|
||
}
|
||
const optLength = new Uint8Array(vlessBuffer.slice(17, 18))[0];
|
||
//skip opt for now
|
||
const command = new Uint8Array(vlessBuffer.slice(18 + optLength, 18 + optLength + 1))[0];
|
||
// 0x01 TCP
|
||
// 0x02 UDP
|
||
// 0x03 MUX
|
||
if (command === 1) {
|
||
}
|
||
else if (command === 2) {
|
||
isUDP = true;
|
||
}
|
||
else {
|
||
return {
|
||
hasError: true,
|
||
message: `command ${command} is not support, command 01-tcp,02-udp,03-mux`,
|
||
};
|
||
}
|
||
const portIndex = 18 + optLength + 1;
|
||
const portBuffer = vlessBuffer.slice(portIndex, portIndex + 2);
|
||
// port is big-Endian in raw data etc 80 == 0x005d
|
||
const portRemote = new DataView(portBuffer).getInt16(0);
|
||
let addressIndex = portIndex + 2;
|
||
const addressBuffer = new Uint8Array(vlessBuffer.slice(addressIndex, addressIndex + 1));
|
||
// 1--> ipv4 addressLength =4
|
||
// 2--> domain name addressLength=addressBuffer[1]
|
||
// 3--> ipv6 addressLength =16
|
||
const addressType = addressBuffer[0];
|
||
let addressLength = 0;
|
||
let addressValueIndex = addressIndex + 1;
|
||
let addressValue = '';
|
||
switch (addressType) {
|
||
case 1:
|
||
addressLength = 4;
|
||
addressValue = new Uint8Array(vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength)).join('.');
|
||
break;
|
||
case 2:
|
||
addressLength = new Uint8Array(vlessBuffer.slice(addressValueIndex, addressValueIndex + 1))[0];
|
||
addressValueIndex += 1;
|
||
addressValue = new TextDecoder().decode(vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength));
|
||
break;
|
||
case 3:
|
||
addressLength = 16;
|
||
const dataView = new DataView(vlessBuffer.slice(addressValueIndex, addressValueIndex + addressLength));
|
||
// 2001:0db8:85a3:0000:0000:8a2e:0370:7334
|
||
const ipv6 = [];
|
||
for (let i = 0; i < 8; i++) {
|
||
ipv6.push(dataView.getUint16(i * 2).toString(16));
|
||
}
|
||
addressValue = ipv6.join(':');
|
||
// console.log('---------', addressValue)
|
||
// seems no need add [] for ipv6
|
||
// if (addressValue) {
|
||
// addressValue = `[${addressValue}]`;
|
||
// }
|
||
break;
|
||
default:
|
||
console.log(`invild addressType is ${addressType}`);
|
||
}
|
||
if (!addressValue) {
|
||
// console.log(`[${address}:${port}] addressValue is empty`);
|
||
// controller.error(`[${address}:${portWithRandomLog}] addressValue is empty`);
|
||
return {
|
||
hasError: true,
|
||
message: `addressValue is empty, addressType is ${addressType}`,
|
||
};
|
||
}
|
||
return {
|
||
hasError: false,
|
||
addressRemote: addressValue,
|
||
portRemote,
|
||
rawDataIndex: addressValueIndex + addressLength,
|
||
vlessVersion: version,
|
||
isUDP,
|
||
};
|
||
}
|
||
exports.processVlessHeader = processVlessHeader;
|
||
|
||
|
||
/***/ }),
|
||
/* 36 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("node:net");
|
||
|
||
/***/ }),
|
||
/* 37 */
|
||
/***/ ((module) => {
|
||
|
||
"use strict";
|
||
module.exports = require("node:stream/web");
|
||
|
||
/***/ })
|
||
/******/ ]);
|
||
/************************************************************************/
|
||
/******/ // The module cache
|
||
/******/ var __webpack_module_cache__ = {};
|
||
/******/
|
||
/******/ // The require function
|
||
/******/ function __webpack_require__(moduleId) {
|
||
/******/ // Check if module is in cache
|
||
/******/ var cachedModule = __webpack_module_cache__[moduleId];
|
||
/******/ if (cachedModule !== undefined) {
|
||
/******/ return cachedModule.exports;
|
||
/******/ }
|
||
/******/ // Create a new module (and put it into the cache)
|
||
/******/ var module = __webpack_module_cache__[moduleId] = {
|
||
/******/ // no module.id needed
|
||
/******/ // no module.loaded needed
|
||
/******/ exports: {}
|
||
/******/ };
|
||
/******/
|
||
/******/ // Execute the module function
|
||
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
|
||
/******/
|
||
/******/ // Return the exports of the module
|
||
/******/ return module.exports;
|
||
/******/ }
|
||
/******/
|
||
/************************************************************************/
|
||
/******/ /* webpack/runtime/compat get default export */
|
||
/******/ (() => {
|
||
/******/ // getDefaultExport function for compatibility with non-harmony modules
|
||
/******/ __webpack_require__.n = (module) => {
|
||
/******/ var getter = module && module.__esModule ?
|
||
/******/ () => (module['default']) :
|
||
/******/ () => (module);
|
||
/******/ __webpack_require__.d(getter, { a: getter });
|
||
/******/ return getter;
|
||
/******/ };
|
||
/******/ })();
|
||
/******/
|
||
/******/ /* webpack/runtime/define property getters */
|
||
/******/ (() => {
|
||
/******/ // define getter functions for harmony exports
|
||
/******/ __webpack_require__.d = (exports, definition) => {
|
||
/******/ for(var key in definition) {
|
||
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
|
||
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
|
||
/******/ }
|
||
/******/ }
|
||
/******/ };
|
||
/******/ })();
|
||
/******/
|
||
/******/ /* webpack/runtime/hasOwnProperty shorthand */
|
||
/******/ (() => {
|
||
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
|
||
/******/ })();
|
||
/******/
|
||
/******/ /* webpack/runtime/make namespace object */
|
||
/******/ (() => {
|
||
/******/ // define __esModule on exports
|
||
/******/ __webpack_require__.r = (exports) => {
|
||
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
|
||
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
|
||
/******/ }
|
||
/******/ Object.defineProperty(exports, '__esModule', { value: true });
|
||
/******/ };
|
||
/******/ })();
|
||
/******/
|
||
/************************************************************************/
|
||
var __webpack_exports__ = {};
|
||
// This entry need to be wrapped in an IIFE because it need to be in strict mode.
|
||
(() => {
|
||
"use strict";
|
||
var exports = __webpack_exports__;
|
||
|
||
Object.defineProperty(exports, "__esModule", ({ value: true }));
|
||
const tslib_1 = __webpack_require__(1);
|
||
const http_1 = __webpack_require__(2);
|
||
const url_1 = __webpack_require__(3);
|
||
const ws_1 = __webpack_require__(4);
|
||
const utils_1 = __webpack_require__(26);
|
||
const uuid_1 = __webpack_require__(31);
|
||
const node_fs_1 = __webpack_require__(27);
|
||
const node_dns_1 = __webpack_require__(32);
|
||
const node_dgram_1 = __webpack_require__(33);
|
||
const vless_js_1 = __webpack_require__(34);
|
||
const node_net_1 = __webpack_require__(36);
|
||
const stream_1 = __webpack_require__(11);
|
||
const web_1 = __webpack_require__(37);
|
||
const port = process.env.PORT;
|
||
const smallRAM = process.env.SMALLRAM || false;
|
||
const userID = process.env.UUID || '';
|
||
//'ipv4first' or 'verbatim'
|
||
const dnOder = process.env.DNSORDER || 'verbatim';
|
||
if (dnOder === 'ipv4first') {
|
||
(0, node_dns_1.setDefaultResultOrder)(dnOder);
|
||
}
|
||
let isVaildUser = (0, uuid_1.validate)(userID);
|
||
if (!isVaildUser) {
|
||
console.log('not set valid UUID');
|
||
}
|
||
const server = (0, http_1.createServer)((req, resp) => {
|
||
var _a;
|
||
if (!isVaildUser) {
|
||
return (0, utils_1.index401)(req, resp);
|
||
}
|
||
const url = new URL(req.url, `http://${req.headers['host']}`);
|
||
// health check
|
||
if (req.method === 'GET' && url.pathname.startsWith('/health')) {
|
||
resp.writeHead(200);
|
||
resp.write('health 200');
|
||
resp.end();
|
||
return;
|
||
}
|
||
// index page
|
||
if (url.pathname.includes(userID)) {
|
||
const index = 'dist/apps/cf-page/index.html';
|
||
resp.writeHead(200, {
|
||
'Content-Type': 'text/html,charset=UTF-8',
|
||
});
|
||
return (0, node_fs_1.createReadStream)(index).pipe(resp);
|
||
}
|
||
if (req.method === 'GET' && url.pathname.startsWith('/assets')) {
|
||
return (0, utils_1.serverStaticFile)(req, resp);
|
||
}
|
||
const basicAuth = req.headers.authorization || '';
|
||
const authStringBase64 = ((_a = basicAuth.split(' ')) === null || _a === void 0 ? void 0 : _a[1]) || '';
|
||
const authString = Buffer.from(authStringBase64, 'base64').toString('ascii');
|
||
if (authString && authString.includes(userID)) {
|
||
resp.writeHead(302, {
|
||
'content-type': 'text/html; charset=utf-8',
|
||
Location: `./${userID}`,
|
||
});
|
||
resp.end();
|
||
}
|
||
else {
|
||
resp.writeHead(401, {
|
||
'content-type': 'text/html; charset=utf-8',
|
||
'WWW-Authenticate': 'Basic',
|
||
});
|
||
resp.end();
|
||
}
|
||
});
|
||
const vlessWServer = new ws_1.WebSocketServer({ noServer: true });
|
||
vlessWServer.on('connection', function connection(ws, request) {
|
||
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
let address = '';
|
||
let portWithRandomLog = '';
|
||
try {
|
||
const log = (info, event) => {
|
||
console.log(`[${address}:${portWithRandomLog}] ${info}`, event || '');
|
||
};
|
||
let remoteConnection = null;
|
||
let udpClientStream = null;
|
||
let remoteConnectionReadyResolve;
|
||
const earlyDataHeader = request.headers['sec-websocket-protocol'];
|
||
const readableWebSocketStream = (0, vless_js_1.makeReadableWebSocketStream)(ws, earlyDataHeader, log);
|
||
let vlessResponseHeader = null;
|
||
// ws --> remote
|
||
readableWebSocketStream
|
||
.pipeTo(new web_1.WritableStream({
|
||
write(chunk, controller) {
|
||
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
if (!Buffer.isBuffer(chunk)) {
|
||
chunk = Buffer.from(chunk);
|
||
}
|
||
if (udpClientStream) {
|
||
const writer = udpClientStream.writable.getWriter();
|
||
// nodejs buffer to ArrayBuffer issue
|
||
// https://nodejs.org/dist/latest-v18.x/docs/api/buffer.html#bufbuffer
|
||
yield writer.write(chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.length));
|
||
writer.releaseLock();
|
||
return;
|
||
}
|
||
if (remoteConnection) {
|
||
yield socketAsyncWrite(remoteConnection, chunk);
|
||
// remoteConnection.write(chunk);
|
||
return;
|
||
}
|
||
const vlessBuffer = chunk.buffer.slice(chunk.byteOffset, chunk.byteOffset + chunk.length);
|
||
const { hasError, message, portRemote, addressRemote, rawDataIndex, vlessVersion, isUDP, } = (0, vless_js_1.processVlessHeader)(vlessBuffer, userID);
|
||
address = addressRemote || '';
|
||
portWithRandomLog = `${portRemote}--${Math.random()} ${isUDP ? 'udp ' : 'tcp '} `;
|
||
if (hasError) {
|
||
controller.error(`[${address}:${portWithRandomLog}] ${message} `);
|
||
return;
|
||
}
|
||
// const addressType = requestAddr >> 42
|
||
// const addressLength = requestAddr & 0x0f;
|
||
console.log(`[${address}:${portWithRandomLog}] connecting`);
|
||
vlessResponseHeader = new Uint8Array([vlessVersion[0], 0]);
|
||
const rawClientData = vlessBuffer.slice(rawDataIndex);
|
||
if (isUDP) {
|
||
// 如果仅仅是针对DNS, 这样是没有必要的。因为xray 客户端 DNS A/AAA query 都有长度 header,
|
||
// 所以直接和 DNS server over TCP。所以无需 runtime 支持 UDP API。
|
||
// DNS over UDP 和 TCP 唯一的区别就是 Header section format 多了长度
|
||
// https://www.rfc-editor.org/rfc/rfc1035#section-4.2.2
|
||
udpClientStream = makeUDPSocketStream(portRemote, address);
|
||
const writer = udpClientStream.writable.getWriter();
|
||
writer.write(rawClientData).catch((error) => console.log);
|
||
writer.releaseLock();
|
||
remoteConnectionReadyResolve(udpClientStream);
|
||
}
|
||
else {
|
||
remoteConnection = yield connect2Remote(portRemote, address, log);
|
||
remoteConnection.write(new Uint8Array(rawClientData));
|
||
remoteConnectionReadyResolve(remoteConnection);
|
||
}
|
||
});
|
||
},
|
||
close() {
|
||
// if (udpClientStream ) {
|
||
// udpClientStream.writable.close();
|
||
// }
|
||
// (remoteConnection as Socket).end();
|
||
console.log(`[${address}:${portWithRandomLog}] readableWebSocketStream is close`);
|
||
},
|
||
abort(reason) {
|
||
// TODO: log can be remove, abort will catch by catch block
|
||
console.log(`[${address}:${portWithRandomLog}] readableWebSocketStream is abort`, JSON.stringify(reason));
|
||
},
|
||
}))
|
||
.catch((error) => {
|
||
console.error(`[${address}:${portWithRandomLog}] readableWebSocketStream pipeto has exception`, error.stack || error);
|
||
// error is cancel readable stream anyway, no need close websocket in here
|
||
// closeWebSocket(webSocket);
|
||
// close remote conn
|
||
// remoteConnection?.close();
|
||
});
|
||
yield new Promise((resolve) => (remoteConnectionReadyResolve = resolve));
|
||
// remote --> ws
|
||
let responseStream = udpClientStream === null || udpClientStream === void 0 ? void 0 : udpClientStream.readable;
|
||
if (remoteConnection) {
|
||
// ignore type error
|
||
// @ts-ignore
|
||
responseStream = stream_1.Readable.toWeb(remoteConnection, {
|
||
strategy: {
|
||
// due to nodejs issue https://github.com/nodejs/node/issues/46347
|
||
highWaterMark: smallRAM ? 100 : 1000, // 1000 * tcp mtu(64kb) = 64mb
|
||
},
|
||
});
|
||
}
|
||
let count = 0;
|
||
// ws.send(vlessResponseHeader!);
|
||
// remoteConnection.pipe(
|
||
// new Writable({
|
||
// async write(chunk: Uint8Array, encoding, callback) {
|
||
// count += chunk.byteLength;
|
||
// console.log('ws write', count / (1024 * 1024));
|
||
// console.log(
|
||
// '-----++++',
|
||
// (remoteConnection as Socket).bytesRead / (1024 * 1024)
|
||
// );
|
||
// if (ws.readyState === ws.OPEN) {
|
||
// await wsAsyncWrite(ws, chunk);
|
||
// callback();
|
||
// }
|
||
// },
|
||
// })
|
||
// );
|
||
// if readable not pipe can't wait fro writeable write method
|
||
yield responseStream.pipeTo(new web_1.WritableStream({
|
||
start() {
|
||
if (ws.readyState === ws.OPEN) {
|
||
ws.send(vlessResponseHeader);
|
||
}
|
||
},
|
||
write(chunk, controller) {
|
||
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
// count += chunk.byteLength;
|
||
// console.log('ws write', count / (1024 * 1024));
|
||
// console.log(
|
||
// '-----++++',
|
||
// (remoteConnection as Socket).bytesRead / (1024 * 1024),
|
||
// (remoteConnection as Socket).readableHighWaterMark
|
||
// );
|
||
// we have issue there, maybe beacsue nodejs web stream has bug.
|
||
// socket web stream will read more data from socket
|
||
if (ws.readyState === ws.OPEN) {
|
||
yield wsAsyncWrite(ws, chunk);
|
||
}
|
||
else {
|
||
if (!remoteConnection.destroyed) {
|
||
remoteConnection.destroy();
|
||
}
|
||
}
|
||
});
|
||
},
|
||
close() {
|
||
console.log(`[${address}:${portWithRandomLog}] remoteConnection!.readable is close`);
|
||
},
|
||
abort(reason) {
|
||
(0, vless_js_1.closeWebSocket)(ws);
|
||
console.error(`[${address}:${portWithRandomLog}] remoteConnection!.readable abort`, reason);
|
||
},
|
||
}));
|
||
}
|
||
catch (error) {
|
||
console.error(`[${address}:${portWithRandomLog}] processWebSocket has exception `, error.stack || error);
|
||
(0, vless_js_1.closeWebSocket)(ws);
|
||
}
|
||
});
|
||
});
|
||
server.on('upgrade', function upgrade(request, socket, head) {
|
||
const { pathname } = (0, url_1.parse)(request.url);
|
||
vlessWServer.handleUpgrade(request, socket, head, function done(ws) {
|
||
vlessWServer.emit('connection', ws, request);
|
||
});
|
||
});
|
||
server.listen({
|
||
port: port,
|
||
host: '::',
|
||
// host: '0.0.0.0',
|
||
}, () => {
|
||
console.log(`server listen in http://127.0.0.1:${port}`);
|
||
});
|
||
function connect2Remote(port, host, log) {
|
||
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
return new Promise((resole, reject) => {
|
||
const remoteSocket = (0, node_net_1.connect)({
|
||
port: port,
|
||
host: host,
|
||
// https://github.com/nodejs/node/pull/46587
|
||
// autoSelectFamily: true,
|
||
}, () => {
|
||
log(`connected`);
|
||
resole(remoteSocket);
|
||
});
|
||
remoteSocket.addListener('error', () => {
|
||
reject('remoteSocket has error');
|
||
});
|
||
});
|
||
});
|
||
}
|
||
function socketAsyncWrite(ws, chunk) {
|
||
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
return new Promise((resolve, reject) => {
|
||
ws.write(chunk, (error) => {
|
||
if (error) {
|
||
reject(error);
|
||
}
|
||
else {
|
||
resolve('');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
}
|
||
function wsAsyncWrite(ws, chunk) {
|
||
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
return new Promise((resolve, reject) => {
|
||
ws.send(chunk, (error) => {
|
||
if (error) {
|
||
reject(error);
|
||
}
|
||
else {
|
||
resolve('');
|
||
}
|
||
});
|
||
});
|
||
});
|
||
}
|
||
function makeUDPSocketStream(portRemote, address) {
|
||
const udpClient = (0, node_dgram_1.createSocket)('udp4');
|
||
const transformStream = new web_1.TransformStream({
|
||
start(controller) {
|
||
/* … */
|
||
udpClient.on('message', (message, info) => {
|
||
// console.log(
|
||
// `udp package received ${info.size} bytes from ${info.address}:${info.port}`,
|
||
// Buffer.from(message).toString('hex')
|
||
// );
|
||
controller.enqueue(Buffer.concat([
|
||
new Uint8Array([(info.size >> 8) & 0xff, info.size & 0xff]),
|
||
message,
|
||
]));
|
||
});
|
||
udpClient.on('error', (error) => {
|
||
console.log('udpClient error event', error);
|
||
controller.error(error);
|
||
});
|
||
},
|
||
transform(chunk, controller) {
|
||
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
||
//seems v2ray will use same web socket for dns query..
|
||
//And v2ray will combine A record and AAAA record into one ws message and use 2 btye for dns query length
|
||
for (let index = 0; index < chunk.byteLength;) {
|
||
const lengthBuffer = chunk.slice(index, index + 2);
|
||
const udpPakcetLength = new DataView(lengthBuffer).getInt16(0);
|
||
const udpData = new Uint8Array(chunk.slice(index + 2, index + 2 + udpPakcetLength));
|
||
index = index + 2 + udpPakcetLength;
|
||
yield new Promise((resolve, reject) => {
|
||
udpClient.send(udpData, portRemote, address, (err) => {
|
||
if (err) {
|
||
console.log('udps send error', err);
|
||
controller.error(`Failed to send UDP packet !! ${err}`);
|
||
safeCloseUDP(udpClient);
|
||
}
|
||
// console.log(
|
||
// 'udp package sent',
|
||
// Buffer.from(udpData).toString('hex')
|
||
// );
|
||
resolve(true);
|
||
});
|
||
});
|
||
index = index;
|
||
}
|
||
// console.log('dns chunk', chunk);
|
||
// console.log(portRemote, address);
|
||
// port is big-Endian in raw data etc 80 == 0x005d
|
||
});
|
||
},
|
||
flush(controller) {
|
||
safeCloseUDP(udpClient);
|
||
controller.terminate();
|
||
},
|
||
});
|
||
return transformStream;
|
||
}
|
||
function safeCloseUDP(client) {
|
||
try {
|
||
client.close();
|
||
}
|
||
catch (error) {
|
||
console.log('error close udp', error);
|
||
}
|
||
}
|
||
|
||
})();
|
||
|
||
var __webpack_export_target__ = exports;
|
||
for(var i in __webpack_exports__) __webpack_export_target__[i] = __webpack_exports__[i];
|
||
if(__webpack_exports__.__esModule) Object.defineProperty(__webpack_export_target__, "__esModule", { value: true });
|
||
/******/ })()
|
||
;
|
||
//# sourceMappingURL=main.js.map
|