@script-bridge/server
Version:
two-way communication system between ScriptAPI and backend server using http request
122 lines (121 loc) • 4.3 kB
JavaScript
"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;