675 lines
26 KiB
JavaScript
675 lines
26 KiB
JavaScript
|
"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;
|