@open-game-system/app-bridge-native
Version:
React Native specific implementation of the app-bridge ecosystem
1 lines • 14.8 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import {\n State,\n Event,\n Store,\n CreateStore,\n StoreConfig,\n Producer,\n NativeBridge,\n StoreOnConfig,\n WebView as BridgeWebView,\n WebToNativeMessage,\n NativeToWebMessage,\n BridgeStores\n} from \"@open-game-system/app-bridge-types\";\nimport { produce } from \"immer\";\nimport { compare } from \"fast-json-patch\";\n\n// Re-export BridgeWebView as WebView for consistency within this package if needed\nexport type WebView = BridgeWebView;\n\n/**\n * Creates a new store with the given configuration according to Plan v4.\n */\nexport const createStore: CreateStore = <\n S extends State,\n E extends Event\n>(\n config: StoreConfig<S, E>\n): Store<S, E> => {\n let currentState = config.initialState;\n const stateListeners = new Set<(state: S) => void>();\n const eventListeners = new Map<string, Set<(event: E, store: Store<S, E>) => Promise<void> | void>>();\n\n let storeInstance: Store<S, E>;\n\n const notifyStateListeners = () => {\n stateListeners.forEach(listener => listener(currentState));\n };\n\n const notifyEventListeners = (eventType: E['type'], event: E) => {\n const listeners = eventListeners.get(eventType as string);\n if (listeners) {\n listeners.forEach(listener => {\n try {\n const result = listener(event, storeInstance);\n if (result instanceof Promise) {\n result.catch(error => {\n console.error(`[Native Store] Unhandled promise rejection in async event listener for type \"${eventType}\":`, error);\n });\n }\n } catch (error) {\n console.error(`[Native Store] Error in event listener for type \"${eventType}\":`, error);\n }\n });\n }\n };\n\n storeInstance = {\n getSnapshot: () => currentState,\n\n dispatch: (event: E): void => {\n let stateChanged = false;\n if (config.producer) {\n const nextState = produce(currentState, (draft: S) => {\n config.producer!(draft, event);\n });\n if (nextState !== currentState) {\n currentState = nextState;\n stateChanged = true;\n }\n }\n\n if (stateChanged) {\n notifyStateListeners();\n }\n\n notifyEventListeners(event.type as E['type'], event);\n },\n\n subscribe: (listener: (state: S) => void) => {\n stateListeners.add(listener);\n listener(currentState);\n return () => {\n stateListeners.delete(listener);\n };\n },\n\n on: <EventType extends E['type']>(\n eventType: EventType,\n listener: (event: Extract<E, { type: EventType }>, store: Store<S, E>) => Promise<void> | void\n ): (() => void) => {\n const eventTypeStr = eventType as string;\n if (!eventListeners.has(eventTypeStr)) {\n eventListeners.set(eventTypeStr, new Set());\n }\n const listeners = eventListeners.get(eventTypeStr)!;\n const typedListener = listener as (event: E, store: Store<S, E>) => Promise<void> | void;\n listeners.add(typedListener);\n\n return () => {\n listeners.delete(typedListener);\n if (listeners.size === 0) {\n eventListeners.delete(eventTypeStr);\n }\n };\n },\n\n reset: () => {\n currentState = config.initialState;\n notifyStateListeners();\n }\n };\n\n if (config.on) {\n for (const eventType in config.on) {\n if (Object.prototype.hasOwnProperty.call(config.on, eventType)) {\n const listener = config.on[eventType as E['type']];\n if (listener) {\n storeInstance.on(eventType as E['type'], listener);\n }\n }\n }\n }\n\n return storeInstance;\n};\n\n/**\n * Creates a native bridge instance using the BridgeWebView type from types package.\n */\nexport function createNativeBridge<TStores extends BridgeStores>(): NativeBridge<TStores> {\n const stores = new Map<keyof TStores, Store<TStores[keyof TStores][\"state\"], TStores[keyof TStores][\"events\"]>>();\n const webViews = new Set<BridgeWebView>();\n const readyWebViews = new Set<BridgeWebView>();\n const readyStateListeners = new Map<BridgeWebView, Set<(isReady: boolean) => void>>();\n const storeListeners = new Set<() => void>();\n\n const notifyStoreListeners = () => {\n storeListeners.forEach(listener => listener());\n };\n\n const notifyReadyStateListeners = (webView: BridgeWebView, isReady: boolean) => {\n const listeners = readyStateListeners.get(webView);\n if (listeners) {\n listeners.forEach((listener) => listener(isReady));\n }\n };\n\n const broadcastToWebViews = (message: NativeToWebMessage<TStores>) => {\n const messageString = JSON.stringify(message);\n webViews.forEach((webView) => {\n if (webView.postMessage) {\n webView.postMessage(messageString);\n } else {\n console.warn(\"[Native Bridge] WebView instance lacks postMessage method.\");\n }\n });\n };\n\n const processWebViewMessage = (\n data: string,\n sourceWebView?: BridgeWebView\n ): void => {\n let parsedData: WebToNativeMessage;\n try {\n parsedData = JSON.parse(data);\n } catch (e) {\n console.warn(\"[Native Bridge] Failed to parse message:\", data, e);\n return;\n }\n\n if (!parsedData || typeof parsedData !== 'object' || !('type' in parsedData)) {\n console.warn(\"[Native Bridge] Invalid message format:\", parsedData);\n return;\n }\n\n switch (parsedData.type) {\n case \"BRIDGE_READY\": {\n const targetWebViews = sourceWebView ? [sourceWebView] : Array.from(webViews);\n targetWebViews.forEach(webView => {\n if (!webView) return;\n readyWebViews.add(webView);\n notifyReadyStateListeners(webView, true);\n stores.forEach((store, key) => {\n const initMessage = JSON.stringify({\n type: \"STATE_INIT\",\n storeKey: key,\n data: store.getSnapshot(),\n });\n if (webView.postMessage) webView.postMessage(initMessage);\n });\n });\n break;\n }\n case \"EVENT\": {\n const { storeKey, event } = parsedData;\n const store = stores.get(storeKey as keyof TStores) as Store<any, typeof event> | undefined;\n if (store) {\n store.dispatch(event);\n }\n break;\n }\n }\n };\n\n return {\n isSupported: () => true,\n\n getStore: <K extends keyof TStores>(key: K) => {\n return stores.get(key) as Store<TStores[K][\"state\"], TStores[K][\"events\"]> | undefined;\n },\n\n setStore: <K extends keyof TStores>(\n key: K,\n store: Store<TStores[K][\"state\"], TStores[K][\"events\"]> | undefined\n ) => {\n if (store === undefined) {\n stores.delete(key);\n } else {\n let prevState = store.getSnapshot();\n stores.set(key, store as Store<any, any>);\n\n const initMessage = {\n type: \"STATE_INIT\" as const,\n storeKey: key,\n data: store.getSnapshot(),\n };\n broadcastToWebViews(initMessage);\n\n store.subscribe((currentState: TStores[K][\"state\"]) => {\n const operations = compare(prevState, currentState);\n if (operations.length > 0) {\n broadcastToWebViews({\n type: \"STATE_UPDATE\",\n storeKey: key,\n operations,\n });\n }\n prevState = currentState;\n });\n }\n notifyStoreListeners();\n },\n\n subscribe: (listener: () => void) => {\n storeListeners.add(listener);\n return () => {\n storeListeners.delete(listener);\n };\n },\n\n handleWebMessage: (message: string | { nativeEvent: { data: string } }) => {\n const messageData =\n typeof message === \"string\" ? message : message.nativeEvent.data;\n processWebViewMessage(messageData, undefined);\n },\n\n registerWebView: (webView: BridgeWebView | null | undefined) => {\n if (!webView) return () => {};\n webViews.add(webView);\n stores.forEach((store, key) => {\n const initMessage = JSON.stringify({\n type: \"STATE_INIT\",\n storeKey: key,\n data: store.getSnapshot(),\n });\n if (webView.postMessage) webView.postMessage(initMessage);\n });\n return () => {\n webViews.delete(webView);\n readyWebViews.delete(webView);\n readyStateListeners.delete(webView);\n };\n },\n\n unregisterWebView: (webView: BridgeWebView | null | undefined) => {\n if (!webView) return;\n webViews.delete(webView);\n readyWebViews.delete(webView);\n readyStateListeners.delete(webView);\n },\n\n subscribeToReadyState: (\n webView: BridgeWebView | null | undefined,\n callback: (isReady: boolean) => void\n ) => {\n if (!webView) {\n callback(false);\n return () => {};\n }\n let listeners = readyStateListeners.get(webView);\n if (!listeners) {\n listeners = new Set();\n readyStateListeners.set(webView, listeners);\n }\n listeners.add(callback);\n callback(readyWebViews.has(webView));\n return () => {\n const currentListeners = readyStateListeners.get(webView);\n if (currentListeners) {\n currentListeners.delete(callback);\n if (currentListeners.size === 0) {\n readyStateListeners.delete(webView);\n }\n }\n };\n },\n\n getReadyState: (webView: BridgeWebView | null | undefined) => {\n if (!webView) return false;\n return readyWebViews.has(webView);\n },\n };\n}\n"],"mappings":";AAcA,SAAS,eAAe;AACxB,SAAS,eAAe;AAQjB,IAAM,cAA2B,CAItC,WACgB;AAChB,MAAI,eAAe,OAAO;AAC1B,QAAM,iBAAiB,oBAAI,IAAwB;AACnD,QAAM,iBAAiB,oBAAI,IAAyE;AAEpG,MAAI;AAEJ,QAAM,uBAAuB,MAAM;AACjC,mBAAe,QAAQ,cAAY,SAAS,YAAY,CAAC;AAAA,EAC3D;AAEA,QAAM,uBAAuB,CAAC,WAAsB,UAAa;AAC/D,UAAM,YAAY,eAAe,IAAI,SAAmB;AACxD,QAAI,WAAW;AACb,gBAAU,QAAQ,cAAY;AAC1B,YAAI;AACA,gBAAM,SAAS,SAAS,OAAO,aAAa;AAC5C,cAAI,kBAAkB,SAAS;AAC3B,mBAAO,MAAM,WAAS;AACnB,sBAAQ,MAAM,gFAAgF,SAAS,MAAM,KAAK;AAAA,YACrH,CAAC;AAAA,UACL;AAAA,QACJ,SAAS,OAAO;AACZ,kBAAQ,MAAM,oDAAoD,SAAS,MAAM,KAAK;AAAA,QAC1F;AAAA,MACJ,CAAC;AAAA,IACH;AAAA,EACF;AAEA,kBAAgB;AAAA,IACd,aAAa,MAAM;AAAA,IAEnB,UAAU,CAAC,UAAmB;AAC5B,UAAI,eAAe;AACnB,UAAI,OAAO,UAAU;AACnB,cAAM,YAAY,QAAQ,cAAc,CAAC,UAAa;AACpD,iBAAO,SAAU,OAAO,KAAK;AAAA,QAC/B,CAAC;AACD,YAAI,cAAc,cAAc;AAC5B,yBAAe;AACf,yBAAe;AAAA,QACnB;AAAA,MACF;AAEA,UAAI,cAAc;AAChB,6BAAqB;AAAA,MACvB;AAEA,2BAAqB,MAAM,MAAmB,KAAK;AAAA,IACrD;AAAA,IAEA,WAAW,CAAC,aAAiC;AAC3C,qBAAe,IAAI,QAAQ;AAC3B,eAAS,YAAY;AACrB,aAAO,MAAM;AACX,uBAAe,OAAO,QAAQ;AAAA,MAChC;AAAA,IACF;AAAA,IAEA,IAAI,CACF,WACA,aACiB;AAChB,YAAM,eAAe;AACtB,UAAI,CAAC,eAAe,IAAI,YAAY,GAAG;AACrC,uBAAe,IAAI,cAAc,oBAAI,IAAI,CAAC;AAAA,MAC5C;AACA,YAAM,YAAY,eAAe,IAAI,YAAY;AACjD,YAAM,gBAAgB;AACtB,gBAAU,IAAI,aAAa;AAE3B,aAAO,MAAM;AACX,kBAAU,OAAO,aAAa;AAC9B,YAAI,UAAU,SAAS,GAAG;AACxB,yBAAe,OAAO,YAAY;AAAA,QACpC;AAAA,MACF;AAAA,IACF;AAAA,IAEA,OAAO,MAAM;AACX,qBAAe,OAAO;AACtB,2BAAqB;AAAA,IACvB;AAAA,EACF;AAEA,MAAI,OAAO,IAAI;AACb,eAAW,aAAa,OAAO,IAAI;AAChC,UAAI,OAAO,UAAU,eAAe,KAAK,OAAO,IAAI,SAAS,GAAG;AAC5D,cAAM,WAAW,OAAO,GAAG,SAAsB;AACjD,YAAI,UAAU;AACV,wBAAc,GAAG,WAAwB,QAAQ;AAAA,QACrD;AAAA,MACJ;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAKO,SAAS,qBAA0E;AACxF,QAAM,SAAS,oBAAI,IAA6F;AAChH,QAAM,WAAW,oBAAI,IAAmB;AACxC,QAAM,gBAAgB,oBAAI,IAAmB;AAC7C,QAAM,sBAAsB,oBAAI,IAAoD;AACpF,QAAM,iBAAiB,oBAAI,IAAgB;AAE3C,QAAM,uBAAuB,MAAM;AACjC,mBAAe,QAAQ,cAAY,SAAS,CAAC;AAAA,EAC/C;AAEA,QAAM,4BAA4B,CAAC,SAAwB,YAAqB;AAC9E,UAAM,YAAY,oBAAoB,IAAI,OAAO;AACjD,QAAI,WAAW;AACb,gBAAU,QAAQ,CAAC,aAAa,SAAS,OAAO,CAAC;AAAA,IACnD;AAAA,EACF;AAEA,QAAM,sBAAsB,CAAC,YAAyC;AACpE,UAAM,gBAAgB,KAAK,UAAU,OAAO;AAC5C,aAAS,QAAQ,CAAC,YAAY;AAC5B,UAAI,QAAQ,aAAa;AACtB,gBAAQ,YAAY,aAAa;AAAA,MACpC,OAAO;AACJ,gBAAQ,KAAK,4DAA4D;AAAA,MAC5E;AAAA,IACF,CAAC;AAAA,EACH;AAEA,QAAM,wBAAwB,CAC5B,MACA,kBACS;AACT,QAAI;AACJ,QAAI;AACF,mBAAa,KAAK,MAAM,IAAI;AAAA,IAC9B,SAAS,GAAG;AACV,cAAQ,KAAK,4CAA4C,MAAM,CAAC;AAChE;AAAA,IACF;AAEA,QAAI,CAAC,cAAc,OAAO,eAAe,YAAY,EAAE,UAAU,aAAa;AAC5E,cAAQ,KAAK,2CAA2C,UAAU;AAClE;AAAA,IACF;AAEA,YAAQ,WAAW,MAAM;AAAA,MACvB,KAAK,gBAAgB;AACnB,cAAM,iBAAiB,gBAAgB,CAAC,aAAa,IAAI,MAAM,KAAK,QAAQ;AAC5E,uBAAe,QAAQ,aAAW;AAC9B,cAAI,CAAC;AAAS;AACd,wBAAc,IAAI,OAAO;AACzB,oCAA0B,SAAS,IAAI;AACvC,iBAAO,QAAQ,CAAC,OAAO,QAAQ;AAC3B,kBAAM,cAAc,KAAK,UAAU;AAAA,cAC/B,MAAM;AAAA,cACN,UAAU;AAAA,cACV,MAAM,MAAM,YAAY;AAAA,YAC5B,CAAC;AACD,gBAAI,QAAQ;AAAa,sBAAQ,YAAY,WAAW;AAAA,UAC5D,CAAC;AAAA,QACL,CAAC;AACD;AAAA,MACF;AAAA,MACA,KAAK,SAAS;AACZ,cAAM,EAAE,UAAU,MAAM,IAAI;AAC5B,cAAM,QAAQ,OAAO,IAAI,QAAyB;AAClD,YAAI,OAAO;AACT,gBAAM,SAAS,KAAK;AAAA,QACtB;AACA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IAEnB,UAAU,CAA0B,QAAW;AAC7C,aAAO,OAAO,IAAI,GAAG;AAAA,IACvB;AAAA,IAEA,UAAU,CACR,KACA,UACG;AACH,UAAI,UAAU,QAAW;AACvB,eAAO,OAAO,GAAG;AAAA,MACnB,OAAO;AACL,YAAI,YAAY,MAAM,YAAY;AAClC,eAAO,IAAI,KAAK,KAAwB;AAExC,cAAM,cAAc;AAAA,UAClB,MAAM;AAAA,UACN,UAAU;AAAA,UACV,MAAM,MAAM,YAAY;AAAA,QAC1B;AACA,4BAAoB,WAAW;AAE/B,cAAM,UAAU,CAAC,iBAAsC;AACrD,gBAAM,aAAa,QAAQ,WAAW,YAAY;AAClD,cAAI,WAAW,SAAS,GAAG;AACzB,gCAAoB;AAAA,cAClB,MAAM;AAAA,cACN,UAAU;AAAA,cACV;AAAA,YACF,CAAC;AAAA,UACH;AACA,sBAAY;AAAA,QACd,CAAC;AAAA,MACH;AACA,2BAAqB;AAAA,IACvB;AAAA,IAEA,WAAW,CAAC,aAAyB;AACnC,qBAAe,IAAI,QAAQ;AAC3B,aAAO,MAAM;AACX,uBAAe,OAAO,QAAQ;AAAA,MAChC;AAAA,IACF;AAAA,IAEA,kBAAkB,CAAC,YAAwD;AACzE,YAAM,cACJ,OAAO,YAAY,WAAW,UAAU,QAAQ,YAAY;AAC9D,4BAAsB,aAAa,MAAS;AAAA,IAC9C;AAAA,IAEA,iBAAiB,CAAC,YAA8C;AAC9D,UAAI,CAAC;AAAS,eAAO,MAAM;AAAA,QAAC;AAC5B,eAAS,IAAI,OAAO;AACpB,aAAO,QAAQ,CAAC,OAAO,QAAQ;AAC5B,cAAM,cAAc,KAAK,UAAU;AAAA,UAChC,MAAM;AAAA,UACN,UAAU;AAAA,UACV,MAAM,MAAM,YAAY;AAAA,QAC3B,CAAC;AACD,YAAI,QAAQ;AAAa,kBAAQ,YAAY,WAAW;AAAA,MAC3D,CAAC;AACD,aAAO,MAAM;AACX,iBAAS,OAAO,OAAO;AACvB,sBAAc,OAAO,OAAO;AAC5B,4BAAoB,OAAO,OAAO;AAAA,MACpC;AAAA,IACF;AAAA,IAEA,mBAAmB,CAAC,YAA8C;AAChE,UAAI,CAAC;AAAS;AACd,eAAS,OAAO,OAAO;AACvB,oBAAc,OAAO,OAAO;AAC5B,0BAAoB,OAAO,OAAO;AAAA,IACpC;AAAA,IAEA,uBAAuB,CACrB,SACA,aACG;AACH,UAAI,CAAC,SAAS;AACZ,iBAAS,KAAK;AACd,eAAO,MAAM;AAAA,QAAC;AAAA,MAChB;AACA,UAAI,YAAY,oBAAoB,IAAI,OAAO;AAC/C,UAAI,CAAC,WAAW;AACd,oBAAY,oBAAI,IAAI;AACpB,4BAAoB,IAAI,SAAS,SAAS;AAAA,MAC5C;AACA,gBAAU,IAAI,QAAQ;AACtB,eAAS,cAAc,IAAI,OAAO,CAAC;AACnC,aAAO,MAAM;AACX,cAAM,mBAAmB,oBAAoB,IAAI,OAAO;AACxD,YAAI,kBAAkB;AACpB,2BAAiB,OAAO,QAAQ;AAChC,cAAI,iBAAiB,SAAS,GAAG;AAC/B,gCAAoB,OAAO,OAAO;AAAA,UACpC;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,IAEA,eAAe,CAAC,YAA8C;AAC5D,UAAI,CAAC;AAAS,eAAO;AACrB,aAAO,cAAc,IAAI,OAAO;AAAA,IAClC;AAAA,EACF;AACF;","names":[]}