UNPKG

@script-bridge/server

Version:

two-way communication system between ScriptAPI and backend server using http request

122 lines (121 loc) 4.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Session = void 0; const crypto_1 = require("crypto"); const protocol_1 = require("@script-bridge/protocol"); const errors_1 = require("./errors"); class Session { server; /** session id */ id = (0, crypto_1.randomUUID)(); /** client-defined id */ clientId; _awaitingResponses = new Map(); sendQueue = []; deltaTimes = []; lastQueryReceivedAt = null; connectionCheckInterval = null; constructor(server) { this.server = server; this.server.sessions.set(this.id, this); this.server.emit('sessionCreate', this); } async disconnect(reason = protocol_1.DisconnectReason.Disconnect) { await this.send(protocol_1.InternalAction.Disconnect, { reason }, 5_000); this.server.emit('clientDisconnect', this, reason); this.destroy(); } destroy() { this.clearResponses(); this.stopConnectionCheck(); this.server.sessions.delete(this.id); this.server.emit('sessionDestroy', this); } send(channelId, data, timeout = 10_000) { if (!channelId.includes(':')) throw new errors_1.NamespaceRequiredError(channelId); const requestId = (0, crypto_1.randomUUID)(); this.sendQueue.push({ type: protocol_1.PayloadType.Request, channelId, requestId, data, }); const sentAt = Date.now(); return new Promise((resolve) => { const to = setTimeout(() => { this._awaitingResponses.delete(requestId); resolve({ type: protocol_1.PayloadType.Response, error: true, message: 'Request timed out', errorReason: protocol_1.ResponseErrorReason.Timeout, sessionId: this.id, requestId, }); }, timeout); this._awaitingResponses.set(requestId, (response) => { this._awaitingResponses.delete(requestId); clearTimeout(to); resolve(response); this.server.emit('responseReceive', response, this); if (this.deltaTimes.length >= 10) this.deltaTimes.shift(); this.deltaTimes.push(Date.now() - sentAt); }); }); } async sendPing() { const start = Date.now(); const res = await this.send(protocol_1.InternalAction.Ping, undefined, 20_000); if (res.error) throw new Error(res.message); return { roundTrip: Date.now() - start, toClient: res.data.receivedAt - start, }; } get averagePing() { if (this.deltaTimes.length === 0) return -1; return this.deltaTimes.reduce((a, b) => a + b, 0) / this.deltaTimes.length; } getQueue() { this.lastQueryReceivedAt = Date.now(); const queue = this.sendQueue.slice(); this.sendQueue.length = 0; return queue; } onConnect() { this.startConnectionCheck(); } clearResponses() { for (const [requestId, respond] of this._awaitingResponses) { respond({ type: protocol_1.PayloadType.Response, error: true, message: 'Session disconnected', errorReason: protocol_1.ResponseErrorReason.Abort, sessionId: this.id, requestId, }); } this._awaitingResponses.clear(); } startConnectionCheck() { this.connectionCheckInterval = setInterval(() => { if (this.lastQueryReceivedAt && Date.now() - this.lastQueryReceivedAt > this.server.requestIntervalTicks * 50 * this.server.timeoutThresholdMultiplier) { this.disconnect(protocol_1.DisconnectReason.ConnectionLost); this.stopConnectionCheck(); } }, 200); } stopConnectionCheck() { if (this.connectionCheckInterval) { clearInterval(this.connectionCheckInterval); this.connectionCheckInterval = null; } } } exports.Session = Session;