import type { IncomingRoute } from '../messages/IncomingRoute'; import type { Server } from './Server'; import type { ConnectionController } from './ConnectionController'; import type { ActionBox } from './ActionBox'; import { Context } from './Context'; import { createActionProxy, getContext } from './utils/createActionProxy'; import { ActionInstance } from './ActionInstance'; import { fireOnAfterDestroyInstance } from './utils/fireOnAfterDestroyInstance'; import { fireOnAfterCreateInstance } from './utils/fireOnAfterCreateInstance'; export class ActionProxyManager extends Map> { private readonly server: Server; private readonly staticContextMap = new Map(); constructor(private readonly connectionController: ConnectionController) { super(); this.server = connectionController.server; } public async init() { this.server.actions.forEach((ab) => this.set(ab.name, new Map())); await this.createOnConnect(); } public async onClose() { try { await this.each((instance) => { const context = getContext(instance); return context.onClose(); }); await this.each((instance) => fireOnAfterDestroyInstance(instance)); } catch (e) { // shit happens } } public getActionProxy(message: IncomingRoute) { const proxy = this.getActiveActionProxyInstance(message); if (!proxy) { const actionBox = this.server.getActionBox(message.actionName); const { config } = actionBox; const { momentOfInit, placeOfInit } = config; if (placeOfInit === 'client' && momentOfInit === 'call') { return this.createActionProxy(actionBox, message.instanceId.toString()); } throw new Error(`An instance of the "${message.actionName}" action cannot be created during a method call.`); } return proxy; } public async createActionProxy(actionBox: ActionBox, instanceId: string): Promise { this.checkLimit(actionBox); const ctx = new Context(this.connectionController, actionBox, this.getStaticContext(actionBox)); ctx.setInstanceId(instanceId); const newProxy = await createActionProxy(ctx); if (actionBox.config.permanent) { this.addActionProxy({ instanceId, actionName: actionBox.name }, newProxy); } return newProxy; } public addActionProxy(message: IncomingRoute, proxy: any) { const pack = this.getPack(message.actionName); return pack.set(message.instanceId, proxy); } public removeActionProxy(message: IncomingRoute) { const pack = this.getPack(message.actionName); return pack.delete(message.instanceId); } public getActiveActionProxyInstance(message: IncomingRoute) { const pack = this.getPack(message.actionName); return pack.get(message.instanceId); } protected getStaticContext(actionBox: ActionBox): Context { const { name } = actionBox; const map = this.staticContextMap; if (!map.has(name)) { map.set(name, new Context(this.connectionController, actionBox)); } return map.get(name)!; } protected async createOnConnect() { const all: Promise[] = []; this.server.actions.forEach((ab) => { const { config } = ab; if (config.momentOfInit === 'connect') { all.push(this.createActionProxy(ab, 'default')); } }); const instances = await Promise.all(all); await Promise.all( instances.map(async (proxy) => { await getContext(proxy).setIncomingContext({}, true); await fireOnAfterCreateInstance(proxy); }), ); } protected getPack(actionName: string) { const pack = this.get(actionName); if (!pack) { throw new Error(`The "${actionName}" action is not available for this connection.`); } return pack; } protected checkLimit(actionBox: ActionBox) { const pack = this.getPack(actionBox.name); if (pack.size >= actionBox.config.maxInstances) { throw new Error(`The instance limit for the "${actionBox.name}" action has been exceeded.`); } } protected async each(cb: (instance: ActionInstance) => Promise): Promise { const all: Promise[] = []; this.forEach((pack) => { pack.forEach((proxy) => all.push(cb(proxy))); }); await Promise.all(all); } }