UNPKG

@open-game-system/app-bridge-native

Version:

React Native specific implementation of the app-bridge ecosystem

255 lines 7.68 kB
// src/index.ts import { produce } from "immer"; import { compare } from "fast-json-patch"; var createStore = (config) => { let currentState = config.initialState; const stateListeners = /* @__PURE__ */ new Set(); const eventListeners = /* @__PURE__ */ new Map(); let storeInstance; const notifyStateListeners = () => { stateListeners.forEach((listener) => listener(currentState)); }; const notifyEventListeners = (eventType, event) => { const listeners = eventListeners.get(eventType); if (listeners) { listeners.forEach((listener) => { try { const result = listener(event, storeInstance); if (result instanceof Promise) { result.catch((error) => { console.error(`[Native Store] Unhandled promise rejection in async event listener for type "${eventType}":`, error); }); } } catch (error) { console.error(`[Native Store] Error in event listener for type "${eventType}":`, error); } }); } }; storeInstance = { getSnapshot: () => currentState, dispatch: (event) => { let stateChanged = false; if (config.producer) { const nextState = produce(currentState, (draft) => { config.producer(draft, event); }); if (nextState !== currentState) { currentState = nextState; stateChanged = true; } } if (stateChanged) { notifyStateListeners(); } notifyEventListeners(event.type, event); }, subscribe: (listener) => { stateListeners.add(listener); listener(currentState); return () => { stateListeners.delete(listener); }; }, on: (eventType, listener) => { const eventTypeStr = eventType; if (!eventListeners.has(eventTypeStr)) { eventListeners.set(eventTypeStr, /* @__PURE__ */ new Set()); } const listeners = eventListeners.get(eventTypeStr); const typedListener = listener; listeners.add(typedListener); return () => { listeners.delete(typedListener); if (listeners.size === 0) { eventListeners.delete(eventTypeStr); } }; }, reset: () => { currentState = config.initialState; notifyStateListeners(); } }; if (config.on) { for (const eventType in config.on) { if (Object.prototype.hasOwnProperty.call(config.on, eventType)) { const listener = config.on[eventType]; if (listener) { storeInstance.on(eventType, listener); } } } } return storeInstance; }; function createNativeBridge() { const stores = /* @__PURE__ */ new Map(); const webViews = /* @__PURE__ */ new Set(); const readyWebViews = /* @__PURE__ */ new Set(); const readyStateListeners = /* @__PURE__ */ new Map(); const storeListeners = /* @__PURE__ */ new Set(); const notifyStoreListeners = () => { storeListeners.forEach((listener) => listener()); }; const notifyReadyStateListeners = (webView, isReady) => { const listeners = readyStateListeners.get(webView); if (listeners) { listeners.forEach((listener) => listener(isReady)); } }; const broadcastToWebViews = (message) => { const messageString = JSON.stringify(message); webViews.forEach((webView) => { if (webView.postMessage) { webView.postMessage(messageString); } else { console.warn("[Native Bridge] WebView instance lacks postMessage method."); } }); }; const processWebViewMessage = (data, sourceWebView) => { let parsedData; try { parsedData = JSON.parse(data); } catch (e) { console.warn("[Native Bridge] Failed to parse message:", data, e); return; } if (!parsedData || typeof parsedData !== "object" || !("type" in parsedData)) { console.warn("[Native Bridge] Invalid message format:", parsedData); return; } switch (parsedData.type) { case "BRIDGE_READY": { const targetWebViews = sourceWebView ? [sourceWebView] : Array.from(webViews); targetWebViews.forEach((webView) => { if (!webView) return; readyWebViews.add(webView); notifyReadyStateListeners(webView, true); stores.forEach((store, key) => { const initMessage = JSON.stringify({ type: "STATE_INIT", storeKey: key, data: store.getSnapshot() }); if (webView.postMessage) webView.postMessage(initMessage); }); }); break; } case "EVENT": { const { storeKey, event } = parsedData; const store = stores.get(storeKey); if (store) { store.dispatch(event); } break; } } }; return { isSupported: () => true, getStore: (key) => { return stores.get(key); }, setStore: (key, store) => { if (store === void 0) { stores.delete(key); } else { let prevState = store.getSnapshot(); stores.set(key, store); const initMessage = { type: "STATE_INIT", storeKey: key, data: store.getSnapshot() }; broadcastToWebViews(initMessage); store.subscribe((currentState) => { const operations = compare(prevState, currentState); if (operations.length > 0) { broadcastToWebViews({ type: "STATE_UPDATE", storeKey: key, operations }); } prevState = currentState; }); } notifyStoreListeners(); }, subscribe: (listener) => { storeListeners.add(listener); return () => { storeListeners.delete(listener); }; }, handleWebMessage: (message) => { const messageData = typeof message === "string" ? message : message.nativeEvent.data; processWebViewMessage(messageData, void 0); }, registerWebView: (webView) => { if (!webView) return () => { }; webViews.add(webView); stores.forEach((store, key) => { const initMessage = JSON.stringify({ type: "STATE_INIT", storeKey: key, data: store.getSnapshot() }); if (webView.postMessage) webView.postMessage(initMessage); }); return () => { webViews.delete(webView); readyWebViews.delete(webView); readyStateListeners.delete(webView); }; }, unregisterWebView: (webView) => { if (!webView) return; webViews.delete(webView); readyWebViews.delete(webView); readyStateListeners.delete(webView); }, subscribeToReadyState: (webView, callback) => { if (!webView) { callback(false); return () => { }; } let listeners = readyStateListeners.get(webView); if (!listeners) { listeners = /* @__PURE__ */ new Set(); readyStateListeners.set(webView, listeners); } listeners.add(callback); callback(readyWebViews.has(webView)); return () => { const currentListeners = readyStateListeners.get(webView); if (currentListeners) { currentListeners.delete(callback); if (currentListeners.size === 0) { readyStateListeners.delete(webView); } } }; }, getReadyState: (webView) => { if (!webView) return false; return readyWebViews.has(webView); } }; } export { createNativeBridge, createStore }; //# sourceMappingURL=index.mjs.map