import { CreateAnswerMessage, CreateMessage, OutgoingContext, } from '../server'; import { FailMessage } from '../messages'; import { createClientActionProxy, getActionBox, getInstanceId, destroy, } from './utils/createClientActionProxy'; import { ClientActionInstance } from './ClientActionInstance'; import { Instances } from './Instances'; import { ClientActionBox } from './ClientActionBox'; import { ActionBoxManager } from './ActionBoxManager'; import { ClientActionConfig } from '../messages/ClientActionConfig'; export interface InstanceManagerClient { isReady: boolean; getActionBoxManager(): ActionBoxManager; } export class InstanceManager { private unreadyQueue = new Set<(v: unknown) => void>(); private localInstanceIdSeq = 1; private data = new Map(); private actionBoxManager: ActionBoxManager; constructor(private client: InstanceManagerClient) { this.actionBoxManager = client.getActionBoxManager(); } public add(instance: ClientActionInstance) { const ab = instance[getActionBox](); const instanceId = instance[getInstanceId](); const { name } = ab; if (!this.data.has(name)) { this.data.set(name, new Instances()); } const pack = this.data.get(name)!; // if (pack.has(instanceId)) { // throw new Error(`Instance ${instanceId} of action ${name} is already exists.`); // } pack.set(instanceId, instance); } public get(actionName: string, instanceId: string): any & ClientActionInstance { const pack = this.data.get(actionName); if (pack && pack.has(instanceId)) { return pack.get(instanceId); } throw new Error(`Instance ${instanceId} of action ${actionName} not found.`); } public initUnready() { this.unreadyQueue.forEach((cb) => { this.unreadyQueue.delete(cb); cb(null); }); } public async create(actionName: string, contextData: Record = {}): Promise { if (actionName !== '#meta' && !this.client.isReady) { // eslint-disable-next-line no-promise-executor-return await new Promise((cb) => this.unreadyQueue.add(cb)); } if (this.actionBoxManager.meta) { await this.actionBoxManager.prepareActionBox(actionName); } const ab = this.actionBoxManager.getActionBox(actionName); const instance = await this.createClientActionInstance(ab, contextData); let proxy: any & ClientActionInstance; if (!this.actionBoxManager.meta || ab.isPermanent()) { const { meta, context, instanceId } = await this.getCreateActionData(actionName, contextData); if (meta) { ab.setConfig(meta); } proxy = await createClientActionProxy(ab, instance, instanceId, context); } else { proxy = await createClientActionProxy(ab, instance, this.getLocalInstanceId(ab.name), { ...ab.getContext(), ...contextData }); } this.add(proxy); return proxy; } public async destroy(instance: ClientActionInstance): Promise { if (!instance[destroy]) { throw new Error('You need to pass an instance of the action.'); } await instance[destroy](); this.delete(instance); } public delete(instance: ClientActionInstance): boolean { const ab = instance[getActionBox](); const instanceId = instance[getInstanceId](); const pack = this.data.get(ab.name); if (pack && pack.has(instanceId)) { return pack.delete(instanceId); } return false; } private getLocalInstanceId(actionName: string): string { const id = `clnt-${actionName}-inst-${this.localInstanceIdSeq}`; this.localInstanceIdSeq += 1; return id; } private async getCreateActionData(actionName: string, context: Record): Promise<{ meta: ClientActionConfig; context: OutgoingContext; instanceId: string; }> { const ab = this.actionBoxManager.getActionBox(actionName); if (this.actionBoxManager.meta && ab.config?.momentOfInit === 'connect') { return { ...this.actionBoxManager.meta.createdOnConnectActions[ab.name], instanceId: 'default', }; } const response = await this.createRequest(actionName, context); if (!response.success) { const err = new Error(); Object.assign(err, response.result); } if (response.type === 'fail') { throw response.result; } if (response.type !== 'create:answer') { throw new Error('Bad answer.'); } return { meta: response.meta!, // todo есть настройка позволяющая не отсылать мету при создании (нужно с ней разобраться) context: response.context || {}, instanceId: response.result, }; } private async createClientActionInstance(ab: ClientActionBox, context: Record): Promise { const factory = ab.actionFactory; const instance = factory(ab, context); await instance?.onInstanceCreate?.(ab, context); return instance; } private async createRequest(actionName: string, context: Record): Promise { const ab = this.actionBoxManager.getActionBox(actionName); const { client } = ab; const { adapter } = client; const transaction = client.createTransaction(); await adapter.send({ transactionId: transaction.id, type: 'create', actionName: ab.name, context: { ...ab.getContext(), ...context }, } as CreateMessage); return transaction.promise as Promise; } }