@open-game-system/app-bridge-native
Version:
React Native specific implementation of the app-bridge ecosystem
255 lines • 7.68 kB
JavaScript
// 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