123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674 |
- "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;
|