import type { VKBridgeSubscribeHandler, AnyRequestMethodName, RequestProps, RequestIdProp, ReceiveData, AnyReceiveMethodName, } from './types/bridge.ts'; /** * Creates counter interface. */ function createCounter() { return { current: 0, next() { return ++this.current; }, }; } /** * Creates interface for resolving request promises by request id's (or not). * * @param instanceId Uniq bridge instance ID. */ function createRequestResolver(instanceId: string) { /** * @prop resolve Resolve function. * @prop reject Reject function. */ type PromiseController = { resolve: (value: any) => any; reject: (reason: any) => any; }; const counter = createCounter(); const promiseControllers: Record = {}; return { /** * Adds new controller with resolve/reject methods. * * @param controller Object with `resolve` and `reject` functions * @param customId Custom `request_id` * @returns New request id of the added controller. */ add(controller: PromiseController, customId?: number | string): number | string { const id = customId != null ? customId : `${counter.next()}_${instanceId}`; promiseControllers[id] = controller; return id; }, /** * Resolves/rejects an added promise by request id and the `isSuccess` * predicate. * * @param requestId Request ID. * @param data Data to pass to the resolve- or reject-function. * @param isSuccess Predicate to select the desired function. */ resolve(requestId: number | string, data: T, isSuccess: (data: T) => boolean) { const requestPromise = promiseControllers[requestId]; if (requestPromise) { if (isSuccess(data)) { requestPromise.resolve(data); } else { requestPromise.reject(data); } promiseControllers[requestId] = null; } }, }; } /** * Returns send function that returns promises. * * @param sendEvent Send event function. * @param subscribe Subscribe event function. * @param instanceId Uniq bridge instance ID. * @returns Send function which returns the Promise object. */ export function promisifySend( sendEvent: ( method: K, props?: RequestProps & RequestIdProp, ) => void, subscribe: (fn: VKBridgeSubscribeHandler) => void, instanceId: string, ) { const requestResolver = createRequestResolver(instanceId); // Subscribe to receive a data subscribe((event) => { if (!event.detail?.data || typeof event.detail.data !== 'object') { return; } // There is no request_id in receive-only events, so we check its existence. if ('request_id' in event.detail.data) { const { request_id: requestId, ...data } = event.detail.data; if (requestId) { requestResolver.resolve(requestId, data, (data) => !('error_type' in data)); } } }); return function promisifiedSend( method: K, props: RequestProps & RequestIdProp = {} as RequestProps & RequestIdProp, // eslint-disable-line @typescript-eslint/consistent-type-assertions ): Promise : void> { return new Promise((resolve, reject) => { const requestId = requestResolver.add({ resolve, reject }, props.request_id); sendEvent(method, { ...props, request_id: requestId, }); }); }; }