export type HostBridgeMessage = Record & { type?: unknown } export type HostBridgeSubscription = { remove: () => void } export type HostBridgeMessaging = { addMessageListener: (handler: (event: HostBridgeMessage) => void) => HostBridgeSubscription sendMessage: (message: HostBridgeMessage) => void } const createIdGenerator = () => { let counter = 0 return () => `req_${Date.now()}_${++counter}` } export const createHostBridge = < TRequest extends string = string, TEvent extends { type: string } = { type: string }, >( messaging: HostBridgeMessaging ) => { type Resolver = { resolve: (value: Record) => void reject: (error: Error) => void } const pending = new Map() const nextRequestId = createIdGenerator() messaging.addMessageListener(event => { const id = typeof event?.requestId === 'string' ? event.requestId : undefined if (!id) return const entry = pending.get(id) if (!entry) return pending.delete(id) if (event.error) entry.reject(new Error(String(event.error))) else entry.resolve(event) }) return { request: >( type: TRequest, payload: Record = {} ): Promise => { const requestId = nextRequestId() return new Promise((resolve, reject) => { pending.set(requestId, { resolve: value => resolve(value as T), reject, }) // Spread payload first so it can't override the routing keys. messaging.sendMessage({ ...payload, type, requestId }) }) }, send: (type: TRequest, payload: Record = {}): void => { // Spread payload first so it can't override the type. messaging.sendMessage({ ...payload, type }) }, subscribe: ( eventType: E['type'], handler: (event: E) => void ): (() => void) => { const sub = messaging.addMessageListener(event => { if (event?.type === eventType) handler(event as unknown as E) }) return () => sub.remove() }, } }