UNPKG

@script-bridge/server

Version:

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

204 lines (203 loc) 8.37 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ScriptBridgeServer = void 0; const http = __importStar(require("node:http")); const node_events_1 = require("node:events"); const express_1 = __importDefault(require("express")); const protocol_1 = require("@script-bridge/protocol"); const session_1 = require("./session"); const client_action_1 = require("./client-action"); const errors_1 = require("./errors"); const handlers_1 = require("./handlers"); class ScriptBridgeServer extends node_events_1.EventEmitter { static PROTOCOL_VERSION = protocol_1.PROTOCOL_VERSION; sessions = new Map(); port; requestIntervalTicks = 8; timeoutThresholdMultiplier = 20; server; app; actionHandlers = new Map(); constructor(options) { super(); this.app = (0, express_1.default)(); this.server = http.createServer(this.app); this.app.use(express_1.default.json()); this.app.use(express_1.default.urlencoded({ extended: true })); this.port = options.port; if (options.requestIntervalTicks !== undefined) this.requestIntervalTicks = options.requestIntervalTicks; if (options.timeoutThresholdMultiplier !== undefined) this.timeoutThresholdMultiplier = options.timeoutThresholdMultiplier; this.app.get('/new', (_, res) => { const session = new session_1.Session(this); res.json({ type: protocol_1.PayloadType.Response, data: { sessionId: session.id, requestIntervalTicks: this.requestIntervalTicks, }, }); }); this.app.get('/query', (req, res) => { const sessionId = req.headers['session-id']; const session = sessionId ? this.sessions.get(sessionId) : undefined; if (!session) { res.json({ type: protocol_1.PayloadType.Response, error: true, message: 'Session is invalid', errorReason: protocol_1.ResponseErrorReason.InvalidSession, }); return; } const requests = session.getQueue(); res.json(requests); for (const request of requests) this.emit('requestSend', request, session); this.emit('queryReceive', session); }); this.app.post('/query', (req, res) => { const body = req.body; const session = this.sessions.get(body.sessionId); if (!session) { res.json({ type: protocol_1.PayloadType.Response, error: true, errorReason: protocol_1.ResponseErrorReason.InvalidSession, message: 'Invalid session', }); return; } if (body.type === protocol_1.PayloadType.Request) { this.emit('requestReceive', body, session); this.handleRequest(session, body, (data) => { res.json(data); this.emit('responseSend', data, session); }); } else if (body.type === protocol_1.PayloadType.Response) { this.handleResponse(session, body); this.emit('responseReceive', body, session); res.sendStatus(200); } else { res.json({ type: protocol_1.PayloadType.Response, error: true, errorReason: protocol_1.ResponseErrorReason.InvalidPayload, message: 'Invalid payload type', }); } }); (0, handlers_1.registerHandlers)(this); } async start() { return new Promise(resolve => { this.server.listen(this.port, () => { this.emit('serverOpen'); resolve(); }); }); } async stop() { await Promise.all([...this.sessions.values()].map((session) => session.disconnect())); this.server.close(); this.emit('serverClose'); } broadcast(channelId, data, timeout = 10_000) { if (typeof channelId !== 'string') throw new TypeError('channelId must be a string'); if (!channelId.includes(':')) throw new errors_1.NamespaceRequiredError(channelId); return Promise.all([...this.sessions.values()].map((session) => session.send(channelId, data, timeout))); } registerHandler(channelId, handler) { if (typeof channelId !== 'string') throw new TypeError('channelId must be a string'); if (!channelId.includes(':')) throw new errors_1.NamespaceRequiredError(channelId); if (this.actionHandlers.has(channelId)) { process.emitWarning(`[ScriptBridge::registerHandler] Overwriting existing handler for channel: ${channelId}`); } this.actionHandlers.set(channelId, handler); } async handleRequest(session, request, respond) { const handler = this.actionHandlers.get(request.channelId); if (!handler) { this.emit('error', new errors_1.UnhandledRequestError(request.channelId)); respond({ type: protocol_1.PayloadType.Response, error: true, errorReason: protocol_1.ResponseErrorReason.UnhandledRequest, message: `No handler found for channel: ${request.channelId}`, }); return; } let isResponded = false; try { const action = new client_action_1.ClientAction(request.data, (data) => { if (isResponded) return; respond({ error: false, data, type: protocol_1.PayloadType.Response }); isResponded = true; }, session); await handler(action); } catch (error) { this.emit('error', error); if (!isResponded) respond({ type: protocol_1.PayloadType.Response, error: true, errorReason: protocol_1.ResponseErrorReason.InternalError, message: 'An error occurred while handling the request\n' + error.message, }); } } handleResponse(session, response) { const awaitingResponse = session._awaitingResponses.get(response.requestId); if (!awaitingResponse) { this.emit('error', new Error(`Received response for unknown request: ${response.requestId}`)); return; } awaitingResponse(response); } } exports.ScriptBridgeServer = ScriptBridgeServer;