more
This commit is contained in:
dopeuni444
2025-07-31 12:23:33 +04:00
parent 20b46678b7
commit b5a22951ae
3401 changed files with 331100 additions and 0 deletions

View File

@@ -0,0 +1,20 @@
(The MIT License)
Copyright (c) 2014 Guillermo Rauch <guillermo@learnboost.com>
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the 'Software'), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,674 @@
"use strict";
var __rest = (this && this.__rest) || function (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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.ClusterAdapterWithHeartbeat = exports.ClusterAdapter = exports.MessageType = void 0;
const in_memory_adapter_1 = require("./in-memory-adapter");
const debug_1 = require("debug");
const crypto_1 = require("crypto");
const debug = (0, debug_1.debug)("socket.io-adapter");
const EMITTER_UID = "emitter";
const DEFAULT_TIMEOUT = 5000;
function randomId() {
return (0, crypto_1.randomBytes)(8).toString("hex");
}
var MessageType;
(function (MessageType) {
MessageType[MessageType["INITIAL_HEARTBEAT"] = 1] = "INITIAL_HEARTBEAT";
MessageType[MessageType["HEARTBEAT"] = 2] = "HEARTBEAT";
MessageType[MessageType["BROADCAST"] = 3] = "BROADCAST";
MessageType[MessageType["SOCKETS_JOIN"] = 4] = "SOCKETS_JOIN";
MessageType[MessageType["SOCKETS_LEAVE"] = 5] = "SOCKETS_LEAVE";
MessageType[MessageType["DISCONNECT_SOCKETS"] = 6] = "DISCONNECT_SOCKETS";
MessageType[MessageType["FETCH_SOCKETS"] = 7] = "FETCH_SOCKETS";
MessageType[MessageType["FETCH_SOCKETS_RESPONSE"] = 8] = "FETCH_SOCKETS_RESPONSE";
MessageType[MessageType["SERVER_SIDE_EMIT"] = 9] = "SERVER_SIDE_EMIT";
MessageType[MessageType["SERVER_SIDE_EMIT_RESPONSE"] = 10] = "SERVER_SIDE_EMIT_RESPONSE";
MessageType[MessageType["BROADCAST_CLIENT_COUNT"] = 11] = "BROADCAST_CLIENT_COUNT";
MessageType[MessageType["BROADCAST_ACK"] = 12] = "BROADCAST_ACK";
MessageType[MessageType["ADAPTER_CLOSE"] = 13] = "ADAPTER_CLOSE";
})(MessageType = exports.MessageType || (exports.MessageType = {}));
function encodeOptions(opts) {
return {
rooms: [...opts.rooms],
except: [...opts.except],
flags: opts.flags,
};
}
function decodeOptions(opts) {
return {
rooms: new Set(opts.rooms),
except: new Set(opts.except),
flags: opts.flags,
};
}
/**
* A cluster-ready adapter. Any extending class must:
*
* - implement {@link ClusterAdapter#doPublish} and {@link ClusterAdapter#doPublishResponse}
* - call {@link ClusterAdapter#onMessage} and {@link ClusterAdapter#onResponse}
*/
class ClusterAdapter extends in_memory_adapter_1.Adapter {
constructor(nsp) {
super(nsp);
this.requests = new Map();
this.ackRequests = new Map();
this.uid = randomId();
}
/**
* Called when receiving a message from another member of the cluster.
*
* @param message
* @param offset
* @protected
*/
onMessage(message, offset) {
if (message.uid === this.uid) {
return debug("[%s] ignore message from self", this.uid);
}
debug("[%s] new event of type %d from %s", this.uid, message.type, message.uid);
switch (message.type) {
case MessageType.BROADCAST: {
const withAck = message.data.requestId !== undefined;
if (withAck) {
super.broadcastWithAck(message.data.packet, decodeOptions(message.data.opts), (clientCount) => {
debug("[%s] waiting for %d client acknowledgements", this.uid, clientCount);
this.publishResponse(message.uid, {
type: MessageType.BROADCAST_CLIENT_COUNT,
data: {
requestId: message.data.requestId,
clientCount,
},
});
}, (arg) => {
debug("[%s] received acknowledgement with value %j", this.uid, arg);
this.publishResponse(message.uid, {
type: MessageType.BROADCAST_ACK,
data: {
requestId: message.data.requestId,
packet: arg,
},
});
});
}
else {
const packet = message.data.packet;
const opts = decodeOptions(message.data.opts);
this.addOffsetIfNecessary(packet, opts, offset);
super.broadcast(packet, opts);
}
break;
}
case MessageType.SOCKETS_JOIN:
super.addSockets(decodeOptions(message.data.opts), message.data.rooms);
break;
case MessageType.SOCKETS_LEAVE:
super.delSockets(decodeOptions(message.data.opts), message.data.rooms);
break;
case MessageType.DISCONNECT_SOCKETS:
super.disconnectSockets(decodeOptions(message.data.opts), message.data.close);
break;
case MessageType.FETCH_SOCKETS: {
debug("[%s] calling fetchSockets with opts %j", this.uid, message.data.opts);
super
.fetchSockets(decodeOptions(message.data.opts))
.then((localSockets) => {
this.publishResponse(message.uid, {
type: MessageType.FETCH_SOCKETS_RESPONSE,
data: {
requestId: message.data.requestId,
sockets: localSockets.map((socket) => {
// remove sessionStore from handshake, as it may contain circular references
const _a = socket.handshake, { sessionStore } = _a, handshake = __rest(_a, ["sessionStore"]);
return {
id: socket.id,
handshake,
rooms: [...socket.rooms],
data: socket.data,
};
}),
},
});
});
break;
}
case MessageType.SERVER_SIDE_EMIT: {
const packet = message.data.packet;
const withAck = message.data.requestId !== undefined;
if (!withAck) {
this.nsp._onServerSideEmit(packet);
return;
}
let called = false;
const callback = (arg) => {
// only one argument is expected
if (called) {
return;
}
called = true;
debug("[%s] calling acknowledgement with %j", this.uid, arg);
this.publishResponse(message.uid, {
type: MessageType.SERVER_SIDE_EMIT_RESPONSE,
data: {
requestId: message.data.requestId,
packet: arg,
},
});
};
this.nsp._onServerSideEmit([...packet, callback]);
break;
}
// @ts-ignore
case MessageType.BROADCAST_CLIENT_COUNT:
// @ts-ignore
case MessageType.BROADCAST_ACK:
// @ts-ignore
case MessageType.FETCH_SOCKETS_RESPONSE:
// @ts-ignore
case MessageType.SERVER_SIDE_EMIT_RESPONSE:
// extending classes may not make a distinction between a ClusterMessage and a ClusterResponse payload and may
// always call the onMessage() method
this.onResponse(message);
break;
default:
debug("[%s] unknown message type: %s", this.uid, message.type);
}
}
/**
* Called when receiving a response from another member of the cluster.
*
* @param response
* @protected
*/
onResponse(response) {
var _a, _b;
const requestId = response.data.requestId;
debug("[%s] received response %s to request %s", this.uid, response.type, requestId);
switch (response.type) {
case MessageType.BROADCAST_CLIENT_COUNT: {
(_a = this.ackRequests
.get(requestId)) === null || _a === void 0 ? void 0 : _a.clientCountCallback(response.data.clientCount);
break;
}
case MessageType.BROADCAST_ACK: {
(_b = this.ackRequests.get(requestId)) === null || _b === void 0 ? void 0 : _b.ack(response.data.packet);
break;
}
case MessageType.FETCH_SOCKETS_RESPONSE: {
const request = this.requests.get(requestId);
if (!request) {
return;
}
request.current++;
response.data.sockets.forEach((socket) => request.responses.push(socket));
if (request.current === request.expected) {
clearTimeout(request.timeout);
request.resolve(request.responses);
this.requests.delete(requestId);
}
break;
}
case MessageType.SERVER_SIDE_EMIT_RESPONSE: {
const request = this.requests.get(requestId);
if (!request) {
return;
}
request.current++;
request.responses.push(response.data.packet);
if (request.current === request.expected) {
clearTimeout(request.timeout);
request.resolve(null, request.responses);
this.requests.delete(requestId);
}
break;
}
default:
// @ts-ignore
debug("[%s] unknown response type: %s", this.uid, response.type);
}
}
async broadcast(packet, opts) {
var _a;
const onlyLocal = (_a = opts.flags) === null || _a === void 0 ? void 0 : _a.local;
if (!onlyLocal) {
try {
const offset = await this.publishAndReturnOffset({
type: MessageType.BROADCAST,
data: {
packet,
opts: encodeOptions(opts),
},
});
this.addOffsetIfNecessary(packet, opts, offset);
}
catch (e) {
return debug("[%s] error while broadcasting message: %s", this.uid, e.message);
}
}
super.broadcast(packet, opts);
}
/**
* Adds an offset at the end of the data array in order to allow the client to receive any missed packets when it
* reconnects after a temporary disconnection.
*
* @param packet
* @param opts
* @param offset
* @private
*/
addOffsetIfNecessary(packet, opts, offset) {
var _a;
if (!this.nsp.server.opts.connectionStateRecovery) {
return;
}
const isEventPacket = packet.type === 2;
// packets with acknowledgement are not stored because the acknowledgement function cannot be serialized and
// restored on another server upon reconnection
const withoutAcknowledgement = packet.id === undefined;
const notVolatile = ((_a = opts.flags) === null || _a === void 0 ? void 0 : _a.volatile) === undefined;
if (isEventPacket && withoutAcknowledgement && notVolatile) {
packet.data.push(offset);
}
}
broadcastWithAck(packet, opts, clientCountCallback, ack) {
var _a;
const onlyLocal = (_a = opts === null || opts === void 0 ? void 0 : opts.flags) === null || _a === void 0 ? void 0 : _a.local;
if (!onlyLocal) {
const requestId = randomId();
this.ackRequests.set(requestId, {
clientCountCallback,
ack,
});
this.publish({
type: MessageType.BROADCAST,
data: {
packet,
requestId,
opts: encodeOptions(opts),
},
});
// we have no way to know at this level whether the server has received an acknowledgement from each client, so we
// will simply clean up the ackRequests map after the given delay
setTimeout(() => {
this.ackRequests.delete(requestId);
}, opts.flags.timeout);
}
super.broadcastWithAck(packet, opts, clientCountCallback, ack);
}
async addSockets(opts, rooms) {
var _a;
const onlyLocal = (_a = opts.flags) === null || _a === void 0 ? void 0 : _a.local;
if (!onlyLocal) {
try {
await this.publishAndReturnOffset({
type: MessageType.SOCKETS_JOIN,
data: {
opts: encodeOptions(opts),
rooms,
},
});
}
catch (e) {
debug("[%s] error while publishing message: %s", this.uid, e.message);
}
}
super.addSockets(opts, rooms);
}
async delSockets(opts, rooms) {
var _a;
const onlyLocal = (_a = opts.flags) === null || _a === void 0 ? void 0 : _a.local;
if (!onlyLocal) {
try {
await this.publishAndReturnOffset({
type: MessageType.SOCKETS_LEAVE,
data: {
opts: encodeOptions(opts),
rooms,
},
});
}
catch (e) {
debug("[%s] error while publishing message: %s", this.uid, e.message);
}
}
super.delSockets(opts, rooms);
}
async disconnectSockets(opts, close) {
var _a;
const onlyLocal = (_a = opts.flags) === null || _a === void 0 ? void 0 : _a.local;
if (!onlyLocal) {
try {
await this.publishAndReturnOffset({
type: MessageType.DISCONNECT_SOCKETS,
data: {
opts: encodeOptions(opts),
close,
},
});
}
catch (e) {
debug("[%s] error while publishing message: %s", this.uid, e.message);
}
}
super.disconnectSockets(opts, close);
}
async fetchSockets(opts) {
var _a;
const [localSockets, serverCount] = await Promise.all([
super.fetchSockets(opts),
this.serverCount(),
]);
const expectedResponseCount = serverCount - 1;
if (((_a = opts.flags) === null || _a === void 0 ? void 0 : _a.local) || expectedResponseCount <= 0) {
return localSockets;
}
const requestId = randomId();
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
const storedRequest = this.requests.get(requestId);
if (storedRequest) {
reject(new Error(`timeout reached: only ${storedRequest.current} responses received out of ${storedRequest.expected}`));
this.requests.delete(requestId);
}
}, opts.flags.timeout || DEFAULT_TIMEOUT);
const storedRequest = {
type: MessageType.FETCH_SOCKETS,
resolve,
timeout,
current: 0,
expected: expectedResponseCount,
responses: localSockets,
};
this.requests.set(requestId, storedRequest);
this.publish({
type: MessageType.FETCH_SOCKETS,
data: {
opts: encodeOptions(opts),
requestId,
},
});
});
}
async serverSideEmit(packet) {
const withAck = typeof packet[packet.length - 1] === "function";
if (!withAck) {
return this.publish({
type: MessageType.SERVER_SIDE_EMIT,
data: {
packet,
},
});
}
const ack = packet.pop();
const expectedResponseCount = (await this.serverCount()) - 1;
debug('[%s] waiting for %d responses to "serverSideEmit" request', this.uid, expectedResponseCount);
if (expectedResponseCount <= 0) {
return ack(null, []);
}
const requestId = randomId();
const timeout = setTimeout(() => {
const storedRequest = this.requests.get(requestId);
if (storedRequest) {
ack(new Error(`timeout reached: only ${storedRequest.current} responses received out of ${storedRequest.expected}`), storedRequest.responses);
this.requests.delete(requestId);
}
}, DEFAULT_TIMEOUT);
const storedRequest = {
type: MessageType.SERVER_SIDE_EMIT,
resolve: ack,
timeout,
current: 0,
expected: expectedResponseCount,
responses: [],
};
this.requests.set(requestId, storedRequest);
this.publish({
type: MessageType.SERVER_SIDE_EMIT,
data: {
requestId,
packet,
},
});
}
publish(message) {
this.publishAndReturnOffset(message).catch((err) => {
debug("[%s] error while publishing message: %s", this.uid, err);
});
}
publishAndReturnOffset(message) {
message.uid = this.uid;
message.nsp = this.nsp.name;
return this.doPublish(message);
}
publishResponse(requesterUid, response) {
response.uid = this.uid;
response.nsp = this.nsp.name;
this.doPublishResponse(requesterUid, response).catch((err) => {
debug("[%s] error while publishing response: %s", this.uid, err);
});
}
}
exports.ClusterAdapter = ClusterAdapter;
class ClusterAdapterWithHeartbeat extends ClusterAdapter {
constructor(nsp, opts) {
super(nsp);
this.nodesMap = new Map(); // uid => timestamp of last message
this.customRequests = new Map();
this._opts = Object.assign({
heartbeatInterval: 5000,
heartbeatTimeout: 10000,
}, opts);
this.cleanupTimer = setInterval(() => {
const now = Date.now();
this.nodesMap.forEach((lastSeen, uid) => {
const nodeSeemsDown = now - lastSeen > this._opts.heartbeatTimeout;
if (nodeSeemsDown) {
debug("[%s] node %s seems down", this.uid, uid);
this.removeNode(uid);
}
});
}, 1000);
}
init() {
this.publish({
type: MessageType.INITIAL_HEARTBEAT,
});
}
scheduleHeartbeat() {
if (this.heartbeatTimer) {
this.heartbeatTimer.refresh();
}
else {
this.heartbeatTimer = setTimeout(() => {
this.publish({
type: MessageType.HEARTBEAT,
});
}, this._opts.heartbeatInterval);
}
}
close() {
this.publish({
type: MessageType.ADAPTER_CLOSE,
});
clearTimeout(this.heartbeatTimer);
if (this.cleanupTimer) {
clearInterval(this.cleanupTimer);
}
}
onMessage(message, offset) {
if (message.uid === this.uid) {
return debug("[%s] ignore message from self", this.uid);
}
if (message.uid && message.uid !== EMITTER_UID) {
// we track the UID of each sender, in order to know how many servers there are in the cluster
this.nodesMap.set(message.uid, Date.now());
}
debug("[%s] new event of type %d from %s", this.uid, message.type, message.uid);
switch (message.type) {
case MessageType.INITIAL_HEARTBEAT:
this.publish({
type: MessageType.HEARTBEAT,
});
break;
case MessageType.HEARTBEAT:
// nothing to do
break;
case MessageType.ADAPTER_CLOSE:
this.removeNode(message.uid);
break;
default:
super.onMessage(message, offset);
}
}
serverCount() {
return Promise.resolve(1 + this.nodesMap.size);
}
publish(message) {
this.scheduleHeartbeat();
return super.publish(message);
}
async serverSideEmit(packet) {
const withAck = typeof packet[packet.length - 1] === "function";
if (!withAck) {
return this.publish({
type: MessageType.SERVER_SIDE_EMIT,
data: {
packet,
},
});
}
const ack = packet.pop();
const expectedResponseCount = this.nodesMap.size;
debug('[%s] waiting for %d responses to "serverSideEmit" request', this.uid, expectedResponseCount);
if (expectedResponseCount <= 0) {
return ack(null, []);
}
const requestId = randomId();
const timeout = setTimeout(() => {
const storedRequest = this.customRequests.get(requestId);
if (storedRequest) {
ack(new Error(`timeout reached: missing ${storedRequest.missingUids.size} responses`), storedRequest.responses);
this.customRequests.delete(requestId);
}
}, DEFAULT_TIMEOUT);
const storedRequest = {
type: MessageType.SERVER_SIDE_EMIT,
resolve: ack,
timeout,
missingUids: new Set([...this.nodesMap.keys()]),
responses: [],
};
this.customRequests.set(requestId, storedRequest);
this.publish({
type: MessageType.SERVER_SIDE_EMIT,
data: {
requestId,
packet,
},
});
}
async fetchSockets(opts) {
var _a;
const [localSockets, serverCount] = await Promise.all([
super.fetchSockets({
rooms: opts.rooms,
except: opts.except,
flags: {
local: true,
},
}),
this.serverCount(),
]);
const expectedResponseCount = serverCount - 1;
if (((_a = opts.flags) === null || _a === void 0 ? void 0 : _a.local) || expectedResponseCount <= 0) {
return localSockets;
}
const requestId = randomId();
return new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
const storedRequest = this.customRequests.get(requestId);
if (storedRequest) {
reject(new Error(`timeout reached: missing ${storedRequest.missingUids.size} responses`));
this.customRequests.delete(requestId);
}
}, opts.flags.timeout || DEFAULT_TIMEOUT);
const storedRequest = {
type: MessageType.FETCH_SOCKETS,
resolve,
timeout,
missingUids: new Set([...this.nodesMap.keys()]),
responses: localSockets,
};
this.customRequests.set(requestId, storedRequest);
this.publish({
type: MessageType.FETCH_SOCKETS,
data: {
opts: encodeOptions(opts),
requestId,
},
});
});
}
onResponse(response) {
const requestId = response.data.requestId;
debug("[%s] received response %s to request %s", this.uid, response.type, requestId);
switch (response.type) {
case MessageType.FETCH_SOCKETS_RESPONSE: {
const request = this.customRequests.get(requestId);
if (!request) {
return;
}
response.data.sockets.forEach((socket) => request.responses.push(socket));
request.missingUids.delete(response.uid);
if (request.missingUids.size === 0) {
clearTimeout(request.timeout);
request.resolve(request.responses);
this.customRequests.delete(requestId);
}
break;
}
case MessageType.SERVER_SIDE_EMIT_RESPONSE: {
const request = this.customRequests.get(requestId);
if (!request) {
return;
}
request.responses.push(response.data.packet);
request.missingUids.delete(response.uid);
if (request.missingUids.size === 0) {
clearTimeout(request.timeout);
request.resolve(null, request.responses);
this.customRequests.delete(requestId);
}
break;
}
default:
super.onResponse(response);
}
}
removeNode(uid) {
this.customRequests.forEach((request, requestId) => {
request.missingUids.delete(uid);
if (request.missingUids.size === 0) {
clearTimeout(request.timeout);
if (request.type === MessageType.FETCH_SOCKETS) {
request.resolve(request.responses);
}
else if (request.type === MessageType.SERVER_SIDE_EMIT) {
request.resolve(null, request.responses);
}
this.customRequests.delete(requestId);
}
});
this.nodesMap.delete(uid);
}
}
exports.ClusterAdapterWithHeartbeat = ClusterAdapterWithHeartbeat;

View File

@@ -0,0 +1,394 @@
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.SessionAwareAdapter = exports.Adapter = void 0;
const events_1 = require("events");
const yeast_1 = require("./contrib/yeast");
const WebSocket = require("ws");
const canPreComputeFrame = typeof ((_a = WebSocket === null || WebSocket === void 0 ? void 0 : WebSocket.Sender) === null || _a === void 0 ? void 0 : _a.frame) === "function";
class Adapter extends events_1.EventEmitter {
/**
* In-memory adapter constructor.
*
* @param {Namespace} nsp
*/
constructor(nsp) {
super();
this.nsp = nsp;
this.rooms = new Map();
this.sids = new Map();
this.encoder = nsp.server.encoder;
}
/**
* To be overridden
*/
init() { }
/**
* To be overridden
*/
close() { }
/**
* Returns the number of Socket.IO servers in the cluster
*
* @public
*/
serverCount() {
return Promise.resolve(1);
}
/**
* Adds a socket to a list of room.
*
* @param {SocketId} id the socket id
* @param {Set<Room>} rooms a set of rooms
* @public
*/
addAll(id, rooms) {
if (!this.sids.has(id)) {
this.sids.set(id, new Set());
}
for (const room of rooms) {
this.sids.get(id).add(room);
if (!this.rooms.has(room)) {
this.rooms.set(room, new Set());
this.emit("create-room", room);
}
if (!this.rooms.get(room).has(id)) {
this.rooms.get(room).add(id);
this.emit("join-room", room, id);
}
}
}
/**
* Removes a socket from a room.
*
* @param {SocketId} id the socket id
* @param {Room} room the room name
*/
del(id, room) {
if (this.sids.has(id)) {
this.sids.get(id).delete(room);
}
this._del(room, id);
}
_del(room, id) {
const _room = this.rooms.get(room);
if (_room != null) {
const deleted = _room.delete(id);
if (deleted) {
this.emit("leave-room", room, id);
}
if (_room.size === 0 && this.rooms.delete(room)) {
this.emit("delete-room", room);
}
}
}
/**
* Removes a socket from all rooms it's joined.
*
* @param {SocketId} id the socket id
*/
delAll(id) {
if (!this.sids.has(id)) {
return;
}
for (const room of this.sids.get(id)) {
this._del(room, id);
}
this.sids.delete(id);
}
/**
* Broadcasts a packet.
*
* Options:
* - `flags` {Object} flags for this packet
* - `except` {Array} sids that should be excluded
* - `rooms` {Array} list of rooms to broadcast to
*
* @param {Object} packet the packet object
* @param {Object} opts the options
* @public
*/
broadcast(packet, opts) {
const flags = opts.flags || {};
const packetOpts = {
preEncoded: true,
volatile: flags.volatile,
compress: flags.compress,
};
packet.nsp = this.nsp.name;
const encodedPackets = this._encode(packet, packetOpts);
this.apply(opts, (socket) => {
if (typeof socket.notifyOutgoingListeners === "function") {
socket.notifyOutgoingListeners(packet);
}
socket.client.writeToEngine(encodedPackets, packetOpts);
});
}
/**
* Broadcasts a packet and expects multiple acknowledgements.
*
* Options:
* - `flags` {Object} flags for this packet
* - `except` {Array} sids that should be excluded
* - `rooms` {Array} list of rooms to broadcast to
*
* @param {Object} packet the packet object
* @param {Object} opts the options
* @param clientCountCallback - the number of clients that received the packet
* @param ack - the callback that will be called for each client response
*
* @public
*/
broadcastWithAck(packet, opts, clientCountCallback, ack) {
const flags = opts.flags || {};
const packetOpts = {
preEncoded: true,
volatile: flags.volatile,
compress: flags.compress,
};
packet.nsp = this.nsp.name;
// we can use the same id for each packet, since the _ids counter is common (no duplicate)
packet.id = this.nsp._ids++;
const encodedPackets = this._encode(packet, packetOpts);
let clientCount = 0;
this.apply(opts, (socket) => {
// track the total number of acknowledgements that are expected
clientCount++;
// call the ack callback for each client response
socket.acks.set(packet.id, ack);
if (typeof socket.notifyOutgoingListeners === "function") {
socket.notifyOutgoingListeners(packet);
}
socket.client.writeToEngine(encodedPackets, packetOpts);
});
clientCountCallback(clientCount);
}
_encode(packet, packetOpts) {
const encodedPackets = this.encoder.encode(packet);
if (canPreComputeFrame &&
encodedPackets.length === 1 &&
typeof encodedPackets[0] === "string") {
// "4" being the "message" packet type in the Engine.IO protocol
const data = Buffer.from("4" + encodedPackets[0]);
// see https://github.com/websockets/ws/issues/617#issuecomment-283002469
packetOpts.wsPreEncodedFrame = WebSocket.Sender.frame(data, {
readOnly: false,
mask: false,
rsv1: false,
opcode: 1,
fin: true,
});
}
return encodedPackets;
}
/**
* Gets a list of sockets by sid.
*
* @param {Set<Room>} rooms the explicit set of rooms to check.
*/
sockets(rooms) {
const sids = new Set();
this.apply({ rooms }, (socket) => {
sids.add(socket.id);
});
return Promise.resolve(sids);
}
/**
* Gets the list of rooms a given socket has joined.
*
* @param {SocketId} id the socket id
*/
socketRooms(id) {
return this.sids.get(id);
}
/**
* Returns the matching socket instances
*
* @param opts - the filters to apply
*/
fetchSockets(opts) {
const sockets = [];
this.apply(opts, (socket) => {
sockets.push(socket);
});
return Promise.resolve(sockets);
}
/**
* Makes the matching socket instances join the specified rooms
*
* @param opts - the filters to apply
* @param rooms - the rooms to join
*/
addSockets(opts, rooms) {
this.apply(opts, (socket) => {
socket.join(rooms);
});
}
/**
* Makes the matching socket instances leave the specified rooms
*
* @param opts - the filters to apply
* @param rooms - the rooms to leave
*/
delSockets(opts, rooms) {
this.apply(opts, (socket) => {
rooms.forEach((room) => socket.leave(room));
});
}
/**
* Makes the matching socket instances disconnect
*
* @param opts - the filters to apply
* @param close - whether to close the underlying connection
*/
disconnectSockets(opts, close) {
this.apply(opts, (socket) => {
socket.disconnect(close);
});
}
apply(opts, callback) {
const rooms = opts.rooms;
const except = this.computeExceptSids(opts.except);
if (rooms.size) {
const ids = new Set();
for (const room of rooms) {
if (!this.rooms.has(room))
continue;
for (const id of this.rooms.get(room)) {
if (ids.has(id) || except.has(id))
continue;
const socket = this.nsp.sockets.get(id);
if (socket) {
callback(socket);
ids.add(id);
}
}
}
}
else {
for (const [id] of this.sids) {
if (except.has(id))
continue;
const socket = this.nsp.sockets.get(id);
if (socket)
callback(socket);
}
}
}
computeExceptSids(exceptRooms) {
const exceptSids = new Set();
if (exceptRooms && exceptRooms.size > 0) {
for (const room of exceptRooms) {
if (this.rooms.has(room)) {
this.rooms.get(room).forEach((sid) => exceptSids.add(sid));
}
}
}
return exceptSids;
}
/**
* Send a packet to the other Socket.IO servers in the cluster
* @param packet - an array of arguments, which may include an acknowledgement callback at the end
*/
serverSideEmit(packet) {
console.warn("this adapter does not support the serverSideEmit() functionality");
}
/**
* Save the client session in order to restore it upon reconnection.
*/
persistSession(session) { }
/**
* Restore the session and find the packets that were missed by the client.
* @param pid
* @param offset
*/
restoreSession(pid, offset) {
return null;
}
}
exports.Adapter = Adapter;
class SessionAwareAdapter extends Adapter {
constructor(nsp) {
super(nsp);
this.nsp = nsp;
this.sessions = new Map();
this.packets = [];
this.maxDisconnectionDuration =
nsp.server.opts.connectionStateRecovery.maxDisconnectionDuration;
const timer = setInterval(() => {
const threshold = Date.now() - this.maxDisconnectionDuration;
this.sessions.forEach((session, sessionId) => {
const hasExpired = session.disconnectedAt < threshold;
if (hasExpired) {
this.sessions.delete(sessionId);
}
});
for (let i = this.packets.length - 1; i >= 0; i--) {
const hasExpired = this.packets[i].emittedAt < threshold;
if (hasExpired) {
this.packets.splice(0, i + 1);
break;
}
}
}, 60 * 1000);
// prevents the timer from keeping the process alive
timer.unref();
}
persistSession(session) {
session.disconnectedAt = Date.now();
this.sessions.set(session.pid, session);
}
restoreSession(pid, offset) {
const session = this.sessions.get(pid);
if (!session) {
// the session may have expired
return null;
}
const hasExpired = session.disconnectedAt + this.maxDisconnectionDuration < Date.now();
if (hasExpired) {
// the session has expired
this.sessions.delete(pid);
return null;
}
const index = this.packets.findIndex((packet) => packet.id === offset);
if (index === -1) {
// the offset may be too old
return null;
}
const missedPackets = [];
for (let i = index + 1; i < this.packets.length; i++) {
const packet = this.packets[i];
if (shouldIncludePacket(session.rooms, packet.opts)) {
missedPackets.push(packet.data);
}
}
return Promise.resolve(Object.assign(Object.assign({}, session), { missedPackets }));
}
broadcast(packet, opts) {
var _a;
const isEventPacket = packet.type === 2;
// packets with acknowledgement are not stored because the acknowledgement function cannot be serialized and
// restored on another server upon reconnection
const withoutAcknowledgement = packet.id === undefined;
const notVolatile = ((_a = opts.flags) === null || _a === void 0 ? void 0 : _a.volatile) === undefined;
if (isEventPacket && withoutAcknowledgement && notVolatile) {
const id = (0, yeast_1.yeast)();
// the offset is stored at the end of the data array, so the client knows the ID of the last packet it has
// processed (and the format is backward-compatible)
packet.data.push(id);
this.packets.push({
id,
opts,
data: packet.data,
emittedAt: Date.now(),
});
}
super.broadcast(packet, opts);
}
}
exports.SessionAwareAdapter = SessionAwareAdapter;
function shouldIncludePacket(sessionRooms, opts) {
const included = opts.rooms.size === 0 || sessionRooms.some((room) => opts.rooms.has(room));
const notExcluded = sessionRooms.every((room) => !opts.except.has(room));
return included && notExcluded;
}

View File

@@ -0,0 +1,20 @@
(The MIT License)
Copyright (c) 2014-2017 TJ Holowaychuk <tj@vision-media.ca>
Copyright (c) 2018-2021 Josh Junon
Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the 'Software'), to deal in the Software without restriction,
including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial
portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,271 @@
/* eslint-env browser */
/**
* This is the web browser implementation of `debug()`.
*/
exports.formatArgs = formatArgs;
exports.save = save;
exports.load = load;
exports.useColors = useColors;
exports.storage = localstorage();
exports.destroy = (() => {
let warned = false;
return () => {
if (!warned) {
warned = true;
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
};
})();
/**
* Colors.
*/
exports.colors = [
'#0000CC',
'#0000FF',
'#0033CC',
'#0033FF',
'#0066CC',
'#0066FF',
'#0099CC',
'#0099FF',
'#00CC00',
'#00CC33',
'#00CC66',
'#00CC99',
'#00CCCC',
'#00CCFF',
'#3300CC',
'#3300FF',
'#3333CC',
'#3333FF',
'#3366CC',
'#3366FF',
'#3399CC',
'#3399FF',
'#33CC00',
'#33CC33',
'#33CC66',
'#33CC99',
'#33CCCC',
'#33CCFF',
'#6600CC',
'#6600FF',
'#6633CC',
'#6633FF',
'#66CC00',
'#66CC33',
'#9900CC',
'#9900FF',
'#9933CC',
'#9933FF',
'#99CC00',
'#99CC33',
'#CC0000',
'#CC0033',
'#CC0066',
'#CC0099',
'#CC00CC',
'#CC00FF',
'#CC3300',
'#CC3333',
'#CC3366',
'#CC3399',
'#CC33CC',
'#CC33FF',
'#CC6600',
'#CC6633',
'#CC9900',
'#CC9933',
'#CCCC00',
'#CCCC33',
'#FF0000',
'#FF0033',
'#FF0066',
'#FF0099',
'#FF00CC',
'#FF00FF',
'#FF3300',
'#FF3333',
'#FF3366',
'#FF3399',
'#FF33CC',
'#FF33FF',
'#FF6600',
'#FF6633',
'#FF9900',
'#FF9933',
'#FFCC00',
'#FFCC33'
];
/**
* Currently only WebKit-based Web Inspectors, Firefox >= v31,
* and the Firebug extension (any Firefox version) are known
* to support "%c" CSS customizations.
*
* TODO: add a `localStorage` variable to explicitly enable/disable colors
*/
// eslint-disable-next-line complexity
function useColors() {
// NB: In an Electron preload script, document will be defined but not fully
// initialized. Since we know we're in Chrome, we'll just detect this case
// explicitly
if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) {
return true;
}
// Internet Explorer and Edge do not support colors.
if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
return false;
}
let m;
// Is webkit? http://stackoverflow.com/a/16459606/376773
// document is undefined in react-native: https://github.com/facebook/react-native/pull/1632
return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) ||
// Is firebug? http://stackoverflow.com/a/398120/376773
(typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) ||
// Is firefox >= v31?
// https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages
(typeof navigator !== 'undefined' && navigator.userAgent && (m = navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/)) && parseInt(m[1], 10) >= 31) ||
// Double check webkit in userAgent just in case we are in a worker
(typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/));
}
/**
* Colorize log arguments if enabled.
*
* @api public
*/
function formatArgs(args) {
args[0] = (this.useColors ? '%c' : '') +
this.namespace +
(this.useColors ? ' %c' : ' ') +
args[0] +
(this.useColors ? '%c ' : ' ') +
'+' + module.exports.humanize(this.diff);
if (!this.useColors) {
return;
}
const c = 'color: ' + this.color;
args.splice(1, 0, c, 'color: inherit');
// The final "%c" is somewhat tricky, because there could be other
// arguments passed either before or after the %c, so we need to
// figure out the correct index to insert the CSS into
let index = 0;
let lastC = 0;
args[0].replace(/%[a-zA-Z%]/g, match => {
if (match === '%%') {
return;
}
index++;
if (match === '%c') {
// We only are interested in the *last* %c
// (the user may have provided their own)
lastC = index;
}
});
args.splice(lastC, 0, c);
}
/**
* Invokes `console.debug()` when available.
* No-op when `console.debug` is not a "function".
* If `console.debug` is not available, falls back
* to `console.log`.
*
* @api public
*/
exports.log = console.debug || console.log || (() => {});
/**
* Save `namespaces`.
*
* @param {String} namespaces
* @api private
*/
function save(namespaces) {
try {
if (namespaces) {
exports.storage.setItem('debug', namespaces);
} else {
exports.storage.removeItem('debug');
}
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
/**
* Load `namespaces`.
*
* @return {String} returns the previously persisted debug modes
* @api private
*/
function load() {
let r;
try {
r = exports.storage.getItem('debug');
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
// If debug isn't set in LS, and we're in Electron, try to load $DEBUG
if (!r && typeof process !== 'undefined' && 'env' in process) {
r = process.env.DEBUG;
}
return r;
}
/**
* Localstorage attempts to return the localstorage.
*
* This is necessary because safari throws
* when a user disables cookies/localstorage
* and you attempt to access it.
*
* @return {LocalStorage}
* @api private
*/
function localstorage() {
try {
// TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context
// The Browser also has localStorage in the global context.
return localStorage;
} catch (error) {
// Swallow
// XXX (@Qix-) should we be logging these?
}
}
module.exports = require('./common')(exports);
const {formatters} = module.exports;
/**
* Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
*/
formatters.j = function (v) {
try {
return JSON.stringify(v);
} catch (error) {
return '[UnexpectedJSONParseError]: ' + error.message;
}
};

View File

@@ -0,0 +1,274 @@
/**
* This is the common logic for both the Node.js and web browser
* implementations of `debug()`.
*/
function setup(env) {
createDebug.debug = createDebug;
createDebug.default = createDebug;
createDebug.coerce = coerce;
createDebug.disable = disable;
createDebug.enable = enable;
createDebug.enabled = enabled;
createDebug.humanize = require('ms');
createDebug.destroy = destroy;
Object.keys(env).forEach(key => {
createDebug[key] = env[key];
});
/**
* The currently active debug mode names, and names to skip.
*/
createDebug.names = [];
createDebug.skips = [];
/**
* Map of special "%n" handling functions, for the debug "format" argument.
*
* Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
*/
createDebug.formatters = {};
/**
* Selects a color for a debug namespace
* @param {String} namespace The namespace string for the debug instance to be colored
* @return {Number|String} An ANSI color code for the given namespace
* @api private
*/
function selectColor(namespace) {
let hash = 0;
for (let i = 0; i < namespace.length; i++) {
hash = ((hash << 5) - hash) + namespace.charCodeAt(i);
hash |= 0; // Convert to 32bit integer
}
return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
}
createDebug.selectColor = selectColor;
/**
* Create a debugger with the given `namespace`.
*
* @param {String} namespace
* @return {Function}
* @api public
*/
function createDebug(namespace) {
let prevTime;
let enableOverride = null;
let namespacesCache;
let enabledCache;
function debug(...args) {
// Disabled?
if (!debug.enabled) {
return;
}
const self = debug;
// Set `diff` timestamp
const curr = Number(new Date());
const ms = curr - (prevTime || curr);
self.diff = ms;
self.prev = prevTime;
self.curr = curr;
prevTime = curr;
args[0] = createDebug.coerce(args[0]);
if (typeof args[0] !== 'string') {
// Anything else let's inspect with %O
args.unshift('%O');
}
// Apply any `formatters` transformations
let index = 0;
args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => {
// If we encounter an escaped % then don't increase the array index
if (match === '%%') {
return '%';
}
index++;
const formatter = createDebug.formatters[format];
if (typeof formatter === 'function') {
const val = args[index];
match = formatter.call(self, val);
// Now we need to remove `args[index]` since it's inlined in the `format`
args.splice(index, 1);
index--;
}
return match;
});
// Apply env-specific formatting (colors, etc.)
createDebug.formatArgs.call(self, args);
const logFn = self.log || createDebug.log;
logFn.apply(self, args);
}
debug.namespace = namespace;
debug.useColors = createDebug.useColors();
debug.color = createDebug.selectColor(namespace);
debug.extend = extend;
debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release.
Object.defineProperty(debug, 'enabled', {
enumerable: true,
configurable: false,
get: () => {
if (enableOverride !== null) {
return enableOverride;
}
if (namespacesCache !== createDebug.namespaces) {
namespacesCache = createDebug.namespaces;
enabledCache = createDebug.enabled(namespace);
}
return enabledCache;
},
set: v => {
enableOverride = v;
}
});
// Env-specific initialization logic for debug instances
if (typeof createDebug.init === 'function') {
createDebug.init(debug);
}
return debug;
}
function extend(namespace, delimiter) {
const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
newDebug.log = this.log;
return newDebug;
}
/**
* Enables a debug mode by namespaces. This can include modes
* separated by a colon and wildcards.
*
* @param {String} namespaces
* @api public
*/
function enable(namespaces) {
createDebug.save(namespaces);
createDebug.namespaces = namespaces;
createDebug.names = [];
createDebug.skips = [];
let i;
const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
const len = split.length;
for (i = 0; i < len; i++) {
if (!split[i]) {
// ignore empty strings
continue;
}
namespaces = split[i].replace(/\*/g, '.*?');
if (namespaces[0] === '-') {
createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$'));
} else {
createDebug.names.push(new RegExp('^' + namespaces + '$'));
}
}
}
/**
* Disable debug output.
*
* @return {String} namespaces
* @api public
*/
function disable() {
const namespaces = [
...createDebug.names.map(toNamespace),
...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace)
].join(',');
createDebug.enable('');
return namespaces;
}
/**
* Returns true if the given mode name is enabled, false otherwise.
*
* @param {String} name
* @return {Boolean}
* @api public
*/
function enabled(name) {
if (name[name.length - 1] === '*') {
return true;
}
let i;
let len;
for (i = 0, len = createDebug.skips.length; i < len; i++) {
if (createDebug.skips[i].test(name)) {
return false;
}
}
for (i = 0, len = createDebug.names.length; i < len; i++) {
if (createDebug.names[i].test(name)) {
return true;
}
}
return false;
}
/**
* Convert regexp to namespace
*
* @param {RegExp} regxep
* @return {String} namespace
* @api private
*/
function toNamespace(regexp) {
return regexp.toString()
.substring(2, regexp.toString().length - 2)
.replace(/\.\*\?$/, '*');
}
/**
* Coerce `val`.
*
* @param {Mixed} val
* @return {Mixed}
* @api private
*/
function coerce(val) {
if (val instanceof Error) {
return val.stack || val.message;
}
return val;
}
/**
* XXX DO NOT USE. This is a temporary stub function.
* XXX It WILL be removed in the next major release.
*/
function destroy() {
console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.');
}
createDebug.enable(createDebug.load());
return createDebug;
}
module.exports = setup;

View File

@@ -0,0 +1,20 @@
Copyright (c) 2011 Einar Otto Stangvik <einaros@gmail.com>
Copyright (c) 2013 Arnout Kazemier and contributors
Copyright (c) 2016 Luigi Pinca and contributors
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,8 @@
'use strict';
module.exports = function () {
throw new Error(
'ws does not work in the browser. Browser clients must use the native ' +
'WebSocket object'
);
};

View File

@@ -0,0 +1,131 @@
'use strict';
const { EMPTY_BUFFER } = require('./constants');
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 = require('bufferutil');
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.
}
}