'use strict'; var protocol = require('@qsocket/protocol'); // src/core/QSocketConnection.ts var contentTypeMap = /* @__PURE__ */ new Map([ [protocol.EQSocketProtocolContentType.UNDEFINED, "undefined"], [protocol.EQSocketProtocolContentType.NULL, "null"], [protocol.EQSocketProtocolContentType.BOOLEAN, "boolean"], [protocol.EQSocketProtocolContentType.NUMBER, "number"], [protocol.EQSocketProtocolContentType.STRING, "string"], [protocol.EQSocketProtocolContentType.JSON, "json"], [protocol.EQSocketProtocolContentType.BUFFER, "buffer"] ]); var reverseContentTypeMap = /* @__PURE__ */ new Map([ ["undefined", protocol.EQSocketProtocolContentType.UNDEFINED], ["null", protocol.EQSocketProtocolContentType.NULL], ["boolean", protocol.EQSocketProtocolContentType.BOOLEAN], ["number", protocol.EQSocketProtocolContentType.NUMBER], ["string", protocol.EQSocketProtocolContentType.STRING], ["json", protocol.EQSocketProtocolContentType.JSON], ["buffer", protocol.EQSocketProtocolContentType.BUFFER] ]); var contentEncodingMap = /* @__PURE__ */ new Map([ [protocol.EQSocketProtocolContentEncoding.RAW, "raw"], [protocol.EQSocketProtocolContentEncoding.GZIP, "gzip"], [protocol.EQSocketProtocolContentEncoding.DEFLATE, "deflate"] ]); var reverseContentEncodingMap = /* @__PURE__ */ new Map([ ["raw", protocol.EQSocketProtocolContentEncoding.RAW], ["gzip", protocol.EQSocketProtocolContentEncoding.GZIP], ["deflate", protocol.EQSocketProtocolContentEncoding.DEFLATE] ]); function determineContentType(data, contentType) { if (contentType) { const type = reverseContentTypeMap.get(contentType); if (type !== void 0) return type; } switch (typeof data) { case "undefined": return protocol.EQSocketProtocolContentType.UNDEFINED; case "boolean": return protocol.EQSocketProtocolContentType.BOOLEAN; case "number": return protocol.EQSocketProtocolContentType.NUMBER; case "string": return protocol.EQSocketProtocolContentType.STRING; case "symbol": return protocol.EQSocketProtocolContentType.UNDEFINED; case "object": if (data === null) return protocol.EQSocketProtocolContentType.NULL; if (Buffer.isBuffer(data)) return protocol.EQSocketProtocolContentType.BUFFER; return protocol.EQSocketProtocolContentType.JSON; default: return protocol.EQSocketProtocolContentType.UNDEFINED; } } function determineContentEncoding(contentEncoding) { if (contentEncoding) { const encoding = reverseContentEncodingMap.get(contentEncoding); if (encoding !== void 0) return encoding; } return protocol.EQSocketProtocolContentEncoding.RAW; } function getContentTypeString(contentType) { var _a; if (contentType === void 0) return "undefined"; return (_a = contentTypeMap.get(contentType)) != null ? _a : "undefined"; } function getContentEncodingString(contentEncoding) { var _a; if (contentEncoding === void 0) return "raw"; return (_a = contentEncodingMap.get(contentEncoding)) != null ? _a : "raw"; } function createConfirmAckMessage(chunk, result) { return [ { meta: { type: protocol.EQSocketProtocolMessageType.ACK, uuid: chunk.meta.uuid }, payload: { data: result, "Content-Type": protocol.EQSocketProtocolContentType.BOOLEAN, "Content-Encoding": protocol.EQSocketProtocolContentEncoding.RAW } } ]; } function createDataAckChunk(chunk, data, contentType, contentEncoding) { return { meta: { type: protocol.EQSocketProtocolMessageType.ACK, uuid: chunk.meta.uuid }, payload: { data, "Content-Type": determineContentType(data, contentType), "Content-Encoding": determineContentEncoding(contentEncoding) } }; } function createDataChunk(uuid, event, namespace, data, contentType, contentEncoding) { return { meta: { type: protocol.EQSocketProtocolMessageType.DATA, uuid, event, namespace }, payload: { data, "Content-Type": determineContentType(data, contentType), "Content-Encoding": determineContentEncoding(contentEncoding) } }; } // src/core/QSocketEventEmetter.ts var QSocketEventEmetterBase = class { constructor() { /** * Map of all event listeners, supporting multiple listeners for each event type. * @private * @type {Map[]>} */ this.listeners = /* @__PURE__ */ new Map(); /** * Listeners for the "connection" event, triggered upon establishing a new connection. * @private * @type {((connection: QSocketConnection) => void)[]} */ this.connectionListeners = []; /** * Listeners for the "disconnection" event, triggered when a connection is terminated. * @private * @type {(() => void)[]} */ this.disconnectionListeners = []; } addEventListener(event, listener, type, contentType, contentEncoding) { let listeners = this.listeners.get(event); if (!listeners) { listeners = []; this.listeners.set(event, listeners); } listeners.push({ type, listener, contentType, contentEncoding }); } removeEventListener(event, listener) { const listeners = this.listeners.get(event); if (!listeners) return; const index = listeners.findIndex((item) => item.listener === listener); if (index !== -1) listeners.splice(index, 1); } async executor(chunk) { const event = chunk.meta.event; const listeners = this.listeners.get(event); if (!listeners) return []; const payload = chunk.payload; this.listeners.set( event, listeners.filter(({ type }) => type === 0 /* ON */) ); const results = await Promise.allSettled( listeners.map(async (eventInstance) => { const data = await Promise.resolve( eventInstance.listener(payload.data, getContentTypeString(payload["Content-Type"]), getContentEncodingString(payload["Content-Encoding"])) ); return createDataAckChunk(chunk, data, eventInstance.contentType, eventInstance.contentEncoding); }) ); return results.reduce((acc, cur) => { if (cur.status === "fulfilled" && cur.value) acc.push(cur.value); return acc; }, []); } }; var QSocketConnectionEventEmitter = class extends QSocketEventEmetterBase { /** * Main implementation of the `on` method, determining which handler to add. * @param {string} event - Event name. * @param {Function} listener - Callback function for the event. * @param {TQSocketContentType} [contentType] - Optional content type. * @param {TQSocketContentEncoding} [contentEncoding] - Optional encoding: 'raw' | 'gzip' | 'deflate'. */ on(event, listener, contentType, contentEncoding) { if (event === "disconnection") { this.disconnectionListeners.push(listener); } else { this.addEventListener(event, listener, 0 /* ON */, contentType, contentEncoding); } } /** * Main implementation of the `once` method, determining the addition of a one-time handler. * @param {string} event - Event name. * @param {Function} listener - Callback function for the event. * @param {TQSocketContentType} [contentType] - Optional content type. * @param {TQSocketContentEncoding} [contentEncoding] - Optional encoding: 'raw' | 'gzip' | 'deflate'. */ once(event, listener, contentType, contentEncoding) { if (event === "disconnection") { this.disconnectionListeners.push(listener); } else { this.addEventListener(event, listener, 0 /* ON */, contentType, contentEncoding); } } /** * Removes a listener for a custom event. * @example * ```typescript * emitter.off('customEvent', customEventHandler); * ``` * @param {string} event - Custom event name. * @param {Function} listener - Callback function registered for the event. */ off(event, listener) { if (event === "disconnection") { const index = this.disconnectionListeners.lastIndexOf(listener); if (index !== -1) this.disconnectionListeners.splice(index, 1); } else { this.removeEventListener(event, listener); } } }; var QSocketNamespaceEventEmitter = class extends QSocketEventEmetterBase { /** * Main implementation of the `on` method, determining which handler to add. * @param {string} event - Event name. * @param {Function} listener - Callback function for the event. * @param {TQSocketContentType} [contentType] - Optional content type. * @param {TQSocketContentEncoding} [contentEncoding] - Optional encoding: 'raw' | 'gzip' | 'deflate'. */ on(event, listener, contentType, contentEncoding) { if (event === "connection") { this.connectionListeners.push(listener); this.addConnectionListennerHandle(listener); } else if (event === "disconnection") { this.disconnectionListeners.push(listener); } else { this.addEventListener(event, listener, 0 /* ON */, contentType, contentEncoding); } } /** * Main implementation of the `once` method, determining the addition of a one-time handler. * @param {string} event - Event name. * @param {Function} listener - Callback function for the event. * @param {TQSocketContentType} [contentType] - Optional content type. * @param {TQSocketContentEncoding} [contentEncoding] - Optional encoding: 'raw' | 'gzip' | 'deflate'. */ once(event, listener, contentType, contentEncoding) { if (event === "connection") { this.connectionListeners.push(listener); this.addConnectionListennerHandle(listener); } else if (event === "disconnection") { this.disconnectionListeners.push(listener); } else { this.addEventListener(event, listener, 0 /* ON */, contentType, contentEncoding); } } /** * Removes a listener for a custom event. * @example * ```typescript * emitter.off('customEvent', customEventHandler); * ``` * @param {string} event - Custom event name. * @param {Function} listener - Callback function registered for the event. */ off(event, listener) { if (event === "connection") { const index = this.connectionListeners.lastIndexOf(listener); if (index !== -1) this.connectionListeners.splice(index, 1); } else if (event === "disconnection") { const index = this.disconnectionListeners.lastIndexOf(listener); if (index !== -1) this.disconnectionListeners.splice(index, 1); } else { this.removeEventListener(event, listener); } } }; // src/core/QSocketConnection.ts var QSocketConnection = class extends QSocketConnectionEventEmitter { //#endregion //#region Конструктор constructor(interaction, namespace) { super(); this.interaction = interaction; this.namespace = namespace; } //#endregion //#region Методы отправки и передачи данных /** * @description Отправка данных на связанный клиент */ async emit(event, data, options) { const message = [ { payload: { data, "Content-Type": determineContentType(data, options == null ? void 0 : options.contentType), "Content-Encoding": determineContentEncoding(options == null ? void 0 : options.contentEncoding) }, meta: { type: protocol.EQSocketProtocolMessageType.DATA, uuid: this.interaction.uuid.next(), namespace: this.namespace.name, event } } ]; const returns = await this.interaction.sendData(message, options == null ? void 0 : options.timeout); return returns === void 0 ? [] : returns[0]; } async broadcast(event, data, options) { const chunk = createDataChunk(this.interaction.uuid.next(), event, this.namespace.name, data, options == null ? void 0 : options.contentType, options == null ? void 0 : options.contentEncoding); const interactionsResults = await this.interaction.broadcast([chunk], options == null ? void 0 : options.timeout); return interactionsResults.map((interactionResult) => interactionResult[0]); } static async pipe(connection, chunk) { return await connection.executor(chunk); } //#endregion //#region Методы управления соединением static close(connection) { connection.disconnectionListeners.forEach((listener) => listener()); connection.listeners.clear(); } //#endregion }; // src/core/QSocketNamespace.ts var QSocketNamespace = class extends QSocketNamespaceEventEmitter { constructor(name, isActivated = true, debuger) { super(); this.connections = /* @__PURE__ */ new Map(); this.waiterWaited = () => void 0; this._name = name; if (!isActivated) { this.waiter = new Promise((resolve) => { this.waiterWaited = resolve; }); } this.debuger = debuger; } get name() { return this._name; } //#region Методы событий async emit(event, data, options) { if (this.waiter) { this.debuger.log(`The namespace "${this.name}" is not activated. Waiting for activation before sending...`); await this.waiter; this.debuger.log(`The waiting for sending in the namespace "${this.name}" is complete. Continuing with the event ${event}.`); } const promises = []; this.connections.forEach((connection) => { promises.push(connection.emit(event, data, options)); }); return (await Promise.allSettled(promises)).filter((res) => res.status === "fulfilled").map(({ value }) => value); } //#endregion //#region Методы управления потоком данных static async pipe(interaction, namespace, chunk) { if (namespace.waiter) await namespace.waiter; const connection = namespace.connections.get(interaction); if (!connection) return []; const namespaceResult = await namespace.executor(chunk); const connectionResult = await QSocketConnection.pipe(connection, chunk); const acks = [...namespaceResult, ...connectionResult]; if (acks.length > 0) return acks; else return [createDataAckChunk(chunk, void 0, "undefined", "raw")]; } //#endregion //#region Методы управления клиентами static async addClient(namespace, interaction) { const connection = new QSocketConnection(interaction, namespace); namespace.connections.set(interaction, connection); await Promise.allSettled( namespace.connectionListeners.map(async (listener) => { try { return await Promise.resolve(listener(connection)); } catch (error) { return namespace.debuger.error("Connection event error:", error); } }) ); namespace.debuger.info(`Interaction "${interaction.id}" join namespace "${namespace.name}"`); } static async deleteClient(namespace, interaction) { const connection = namespace.connections.get(interaction); namespace.connections.delete(interaction); await Promise.allSettled( namespace.disconnectionListeners.map(async (listener) => { try { return await Promise.resolve(listener()); } catch (error) { return namespace.debuger.error("Disconnection event error:", error); } }) ); namespace.debuger.info(`Interaction "${interaction.id}" leave namespace "${namespace.name}"`); if (connection !== void 0) QSocketConnection.close(connection); } static destroy(namespace) { namespace.connections.forEach((_, interaction) => this.deleteClient(namespace, interaction)); } //#endregion addConnectionListennerHandle(listenner) { this.connections.forEach((connection) => listenner(connection)); } static activate(namespace) { if (namespace.waiter !== void 0 && namespace.waiterWaited !== void 0) { namespace.debuger.log(`The namespace "${namespace.name}" has been activated!`); namespace.waiterWaited(); namespace.waiter = void 0; namespace.waiterWaited = void 0; } } static diactivate(namespace) { if (namespace.waiter === void 0 && namespace.waiterWaited === void 0) { namespace.waiter = new Promise((resolve) => { namespace.waiterWaited = resolve; }); namespace.debuger.log(`The namespace "${namespace.name}" has been deactivated!`); } } }; // src/core/QSocketDebuger.ts var QSocketDebuger = class { /** * Конструктор класса QSocketUtils. * @param {IQSocketConfigBase['debug']} [debugConfig] - Конфигурация для режима отладки. */ constructor(debugConfig) { /** Логгер, используемый для вывода сообщений */ this.logger = console; const { enabled = false, logger = console, prefix = "" } = debugConfig != null ? debugConfig : {}; this.enabled = enabled; this.logger = logger; this.prefix = prefix; } /** * Логирует сообщение, если включен режим отладки. * @param {...any[]} message - Сообщение или данные для логирования. */ log(...message) { if (this.enabled) this.logger.log(this.prefix, ...message); } /** * Логирует сообщение об ошибке, если включен режим отладки. * @param {...any[]} message - Сообщение или данные для логирования ошибок. */ error(...message) { if (this.enabled) this.logger.error(this.prefix, ...message); } /** * Логирует информационное сообщение, если включен режим отладки. * @param {...any[]} message - Сообщение или данные для информационного логирования. */ info(...message) { if (this.enabled) this.logger.info(this.prefix, ...message); } /** * Логирует предупреждение, если включен режим отладки. * @param {...any[]} message - Сообщение или данные для логирования предупреждений. */ warn(...message) { if (this.enabled) this.logger.warn(this.prefix, ...message); } //#endregion }; // src/core/QSocketUniqueGenerator.ts var MAX_VALUE = Number.MAX_SAFE_INTEGER; var QSocketUniqueGenerator = class { constructor(prefix = "") { /** * Текущий индекс для генерации UUID. * @private */ this.uuidIndex = 0; this.prefix = prefix; } /** * Метод для генерации следующего уникального идентификатора. */ next() { if (++this.uuidIndex > MAX_VALUE) this.uuidIndex = 0; return `${this.prefix}${this.uuidIndex.toString(16)}`; } }; // src/core/QSocketInteraction.ts var QSocketInteraction = class { constructor(id, socket, allNamespaces = /* @__PURE__ */ new Map(), interactions, protocol, timeout, debuger) { this.acks = /* @__PURE__ */ new Map(); this.connectedNamespaces = /* @__PURE__ */ new Map(); this.allNamespaces = /* @__PURE__ */ new Map(); this.id = id; this.uuid = new QSocketUniqueGenerator(`${this.id}-M`); this.debuger = debuger; this.socket = socket; this.interactions = interactions; this.allNamespaces = allNamespaces; this.protocol = protocol; this.timeout = timeout; this.socket.on("message", this.onHandle.bind(this)); } static close(interaction) { interaction.socket.close(); interaction.closeHandle(); } closeHandle() { this.debuger.log("The connection termination process has started.", this.id); this.acks.clear(); this.socket.close(); this.connectedNamespaces.forEach((namespace) => QSocketNamespace.deleteClient(namespace, this)); } //#region ПРОСЛУШИВАНИЕ СОБЫТИЙ async onHandle(data) { if (typeof data === "string") { this.debuger.error("Communication via the QSocket protocol is only possible in buffer format."); return; } const buffer = new Uint8Array(data); let message = await this.protocol.from(buffer); if (message instanceof Error) { this.debuger.error(message); return; } const ackChunks = []; message.forEach((chunk) => { switch (chunk.meta.type) { case protocol.EQSocketProtocolMessageType.DATA: this.onData(chunk); break; case protocol.EQSocketProtocolMessageType.ACK: ackChunks.push(chunk); break; case protocol.EQSocketProtocolMessageType.CONTROL: this.onControl(chunk); break; } }); this.onAck(ackChunks); } onData(chunk) { const namespaceInstance = this.connectedNamespaces.get(chunk.meta.namespace); if (!namespaceInstance) { this.debuger.error(`Namespace "${chunk.meta.namespace}" does not exist`); return; } QSocketNamespace.pipe(this, namespaceInstance, chunk).then((results) => { this.sendAck(results); }).catch((error) => { this.debuger.error(`\u041E\u0448\u0438\u0431\u043A\u0430 \u043F\u0440\u0438 \u043E\u0431\u0440\u0430\u0431\u043E\u0442\u043A\u0435 \u0434\u0430\u043D\u043D\u044B\u0445: ${error.message}`); this.sendAck([ { meta: { type: protocol.EQSocketProtocolMessageType.ACK, uuid: chunk.meta.uuid }, payload: { data: `Error: ${error.message}`, "Content-Type": protocol.EQSocketProtocolContentType.STRING, "Content-Encoding": protocol.EQSocketProtocolContentEncoding.RAW } } ]); }); } onAck(message) { const ackMap = /* @__PURE__ */ new Map(); message.forEach((chunk) => { let ack = ackMap.get(chunk.meta.uuid) || []; ack.push(chunk); ackMap.set(chunk.meta.uuid, ack); }); ackMap.forEach((value, uuid) => { const resolve = this.acks.get(uuid); if (!resolve) { this.debuger.error(`Return message UUID not found [id: ${this.id}, uuid: ${uuid}]`); return; } resolve(value.map((item) => item.payload)); this.acks.delete(uuid); }); } async onControl(chunk) { const data = chunk.payload.data; if (data.command === "join-namespace") { const namespace = this.allNamespaces.get(data.namespace); if (!namespace) { this.debuger.error(`Namespace "${data.namespace}" not found`); return; } await this.sendAck(createConfirmAckMessage(chunk, true)); if (!this.connectedNamespaces.has(namespace.name)) { this.connectedNamespaces.set(namespace.name, namespace); } QSocketNamespace.addClient(namespace, this); } else if (data.command === "leave-namespace") { if (typeof data.namespace === "string") { const namespace = this.connectedNamespaces.get(data.namespace); if (!namespace) { this.debuger.error(`Namespace "${data.namespace}" not found`); return; } await this.sendAck(createConfirmAckMessage(chunk, true)); this.connectedNamespaces.delete(namespace.name); QSocketNamespace.deleteClient(namespace, this); } } else { this.debuger.error("Unknown control command"); } } //#endregion //#region ОТПРАВКА ДАННЫХ async broadcast(message, timeout = this.timeout.value) { const promises = []; this.interactions.forEach((interaction) => { if (interaction !== this) { promises.push(interaction.sendData(message, timeout)); } }); const interactionsResults = await Promise.allSettled(promises).then( (result) => result.filter((item) => item.status === "fulfilled").map(({ value }) => value).filter((value) => value !== void 0) ); return interactionsResults; } async sendData(message, timeout = this.timeout.value) { const data = await this.protocol.to(message); if (data instanceof Error) { this.debuger.error(data); return; } this.socket.send(data); const result = (await Promise.allSettled( message.map((chunk) => { return new Promise((emitResolve, emitReject) => { const ackResolver = (ackResult) => { clearTimeout(timer); this.acks.delete(chunk.meta.uuid); emitResolve(ackResult); }; this.acks.set(chunk.meta.uuid, ackResolver); const timer = setTimeout(() => { this.acks.delete(chunk.meta.uuid); this.debuger.error(`\u0412\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0438\u0441\u0442\u0435\u043A\u043B\u043E [event: ${chunk.meta.event}, uuid: ${chunk.meta.uuid}, timeout: ${timeout}]`); emitReject(new Error("Timeout")); }, timeout); }); }) )).filter((res) => res.status === "fulfilled").map(({ value }) => value); return result; } async sendAck(message) { const data = await this.protocol.to(message); this.socket.send(data); } async sendCommand(message, timeout = this.timeout.value) { const data = await this.protocol.to(message); if (data instanceof Error) { this.debuger.error(data); return; } this.socket.send(data); return (await Promise.allSettled( message.map((chunk) => { return new Promise((emitResolve, emitReject) => { const ackResolver = (ackResult) => { clearTimeout(timer); emitResolve(ackResult); this.acks.delete(chunk.meta.uuid); }; this.acks.set(chunk.meta.uuid, ackResolver); const timer = setTimeout(() => { this.debuger.error(`\u0412\u0440\u0435\u043C\u044F \u043E\u0436\u0438\u0434\u0430\u043D\u0438\u044F \u0438\u0441\u0442\u0435\u043A\u043B\u043E [command, uuid: ${chunk.meta.uuid}]`); emitReject(new Error("Timeout")); this.acks.delete(chunk.meta.uuid); }, timeout); }); }) )).filter((res) => res.status === "fulfilled").map(({ value }) => value); } //#endregion //#region NAMESPACES static joinNamespace(interaction, namespace) { return interaction.joinNamespace(namespace); } joinNamespace(namespace) { this.connectedNamespaces.set(namespace.name, namespace); return QSocketNamespace.addClient(namespace, this); } static leaveNamespace(interaction, namespace) { return interaction.leaveNamespace(namespace); } leaveNamespace(namespace) { this.connectedNamespaces.delete(namespace.name); return QSocketNamespace.deleteClient(namespace, this); } //#endregion }; var clientUUID = new QSocketUniqueGenerator(); var serverUUID = new QSocketUniqueGenerator(); var QSocketBase = class { constructor(type, config) { this.namespaces = /* @__PURE__ */ new Map(); this.interactions = /* @__PURE__ */ new Map(); this.timeout = { value: 6e4, actionAfrer: "none" }; this.middlewares = []; var _a, _b, _c, _d, _e; this.type = type; this.debuger = new QSocketDebuger(config == null ? void 0 : config.debug); this.protocol = new protocol.QSocketProtocol((_a = config == null ? void 0 : config.compression) == null ? void 0 : _a.compressor, (_c = (_b = config == null ? void 0 : config.compression) == null ? void 0 : _b.compressionFromSize) != null ? _c : 1024 * 100); if (((_d = config == null ? void 0 : config.timeout) == null ? void 0 : _d.value) !== void 0) this.timeout.value = config.timeout.value; if (((_e = config == null ? void 0 : config.timeout) == null ? void 0 : _e.actionAfrer) !== void 0) this.timeout.actionAfrer = config.timeout.actionAfrer; const prefix = type === "server" ? "S" : "C"; const generator = type === "server" ? serverUUID : clientUUID; this.id = `${prefix}${generator.next()}`; this.uuid = new QSocketUniqueGenerator(`${this.id}-SM`); this.interactionUUID = new QSocketUniqueGenerator(`${this.id}-I`); } connectionHandle(socket) { const interactionId = this.interactionUUID.next(); const interaction = new QSocketInteraction(interactionId, socket, this.namespaces, this.interactions, this.protocol, this.timeout, this.debuger); this.interactions.set(interactionId, interaction); socket.on("close", () => this.closeInteraction(interactionId, interaction)); } closeInteraction(interactionId, interaction) { QSocketInteraction.close(interaction); this.interactions.delete(interactionId); } /** * Создаёт новое пространство имён или возвращает существующее. * @param {string} name - Имя создаваемого пространства имён. * @returns {QSocketNamespace} Пространство имён QSocket. */ createNamespace(name) { if (this.namespaces.has(name)) { this.debuger.warn(`The namespace "${name}" already exists.`); return this.namespaces.get(name); } const namespace = new QSocketNamespace(name, this.type === "server", this.debuger); this.namespaces.set(name, namespace); this.namespaceControl(namespace, "join-namespace"); return namespace; } /** * Удаляет существующее пространство имён. * @param {string} name - Имя удаляемого пространства имён. * @returns {boolean} Возвращает `true`, если пространство имён было удалено, иначе `false`. */ deleteNamespace(name) { const namespace = this.namespaces.get(name); if (namespace === void 0) { this.debuger.warn(`The namespace '${name}' does not exist.`); return; } this.namespaces.delete(name); QSocketNamespace.destroy(namespace); this.namespaceControl(namespace, "leave-namespace"); return; } /** * @description Добавляет промежуточный обработчик сообщений * @param handler */ use(handler) { this.middlewares.push(handler); } //#region Методы, работающие ТОЛЬКО НА КЛИЕНТЕ async namespaceControl(namespace, command) { if (this.type !== "client") return true; const message = [ { meta: { type: protocol.EQSocketProtocolMessageType.CONTROL, uuid: this.uuid.next() }, payload: { data: { command, namespace: namespace.name }, "Content-Type": protocol.EQSocketProtocolContentType.JSON, "Content-Encoding": protocol.EQSocketProtocolContentEncoding.RAW } } ]; const promises = []; this.interactions.forEach((interaction) => { promises.push( interaction.sendCommand(message).then(() => { if (command === "join-namespace") { return QSocketInteraction.joinNamespace(interaction, namespace); } else if (command === "leave-namespace") { return QSocketInteraction.leaveNamespace(interaction, namespace); } return; }).then(() => { if (command === "join-namespace") { this.debuger.info(`The namespace "${namespace.name}" has been created.`); QSocketNamespace.activate(namespace); } else if (command === "leave-namespace") { this.debuger.info(`The namespace "${namespace.name}" has been removed.`); QSocketNamespace.diactivate(namespace); } return true; }).catch(() => { this.debuger.error(`Error while ${command === "join-namespace" ? "connecting to" : "disconnecting from"} the namespace "${namespace.name}".`); return false; }) ); }); return (await Promise.allSettled(promises)).every((item) => item.status === "fulfilled"); } //#endregion }; // src/interfaces/QSocketClient.ts var QSocketClient = class extends QSocketBase { constructor(socketBuilder, config) { super("client", config); this.isConnected = false; this.reconnectionAttempts = 0; this.reconnecting = false; this.reconnectionConfig = config == null ? void 0 : config.reconnection; this.transportBuilder = socketBuilder; } async connect() { if (this.isConnected) return; let isTimeout = false; let timeout; try { const transport = await Promise.race([ new Promise((resolve) => { const transport2 = this.transportBuilder(); const handleOpen = () => { if (isTimeout) { transport2.close(); transport2.off("open", handleOpen); return; } if (timeout !== void 0) { clearTimeout(timeout); } transport2.off("open", handleOpen); resolve(transport2); }; transport2.on("open", handleOpen); }), new Promise((_, reject) => { timeout = window.setTimeout(() => { isTimeout = true; reject(new Error("Connection timed out")); }, 1e4); }) ]); this.isConnected = true; this.reconnectionAttempts = 0; this.connectionHandle(transport); this.namespaces.forEach((namespace) => this.namespaceControl(namespace, "join-namespace")); transport.on("close", () => { this.isConnected = false; this.attemptReconnect(); }); } catch (error) { this.debuger.error("Connection failed:", error); this.isConnected = false; this.attemptReconnect(); } finally { if (timeout !== void 0) { clearTimeout(timeout); } } } /** * Метод для переподключения с учетом конфигурации. */ async attemptReconnect() { var _a; if (!((_a = this.reconnectionConfig) == null ? void 0 : _a.enabled) || this.reconnecting) return; this.reconnecting = true; while (this.reconnectionConfig.enabled && (this.reconnectionConfig.maxAttempts === void 0 || this.reconnectionAttempts < this.reconnectionConfig.maxAttempts)) { this.reconnectionAttempts++; const delay = this.calculateDelay(); this.debuger.log(`Attempting to reconnect... (attempt ${this.reconnectionAttempts})`); await new Promise((resolve) => setTimeout(resolve, delay)); try { await this.connect(); if (this.isConnected) { this.debuger.log("Reconnected successfully."); break; } } catch (error) { this.debuger.error("Reconnection attempt failed:", error); } } this.reconnecting = false; } /** * Вычисляет задержку для следующей попытки переподключения. * @returns {number} Задержка в миллисекундах */ calculateDelay() { var _a, _b, _c; const baseDelay = (_b = (_a = this.reconnectionConfig) == null ? void 0 : _a.delay) != null ? _b : 1e3; if ((_c = this.reconnectionConfig) == null ? void 0 : _c.exponentialBackoff) { return baseDelay * Math.pow(2, this.reconnectionAttempts - 1); } return baseDelay; } //#endregion }; // src/interfaces/QSocketServer.ts var QSocketServer = class extends QSocketBase { constructor(transport, config) { super("server", config); this.server = transport; this.server.on("connection", (socket) => this.connectionHandle(socket)); } }; exports.QSocketClient = QSocketClient; exports.QSocketServer = QSocketServer; //# sourceMappingURL=index.cjs.map //# sourceMappingURL=index.cjs.map