@open-game-system/app-bridge-web
Version:
Web-specific implementation of the app-bridge ecosystem
151 lines • 5.25 kB
JavaScript
// src/index.ts
import { applyPatch } from "fast-json-patch";
function createWebBridge() {
const stateByStore = /* @__PURE__ */ new Map();
const stores = /* @__PURE__ */ new Map();
const stateListeners = /* @__PURE__ */ new Map();
const storeListeners = /* @__PURE__ */ new Set();
const notifyStateListeners = (storeKey) => {
const listeners = stateListeners.get(storeKey);
if (listeners && stateByStore.has(storeKey)) {
const state = stateByStore.get(storeKey);
listeners.forEach((listener) => listener(state));
}
};
const notifyStoreListeners = () => {
storeListeners.forEach((listener) => listener());
};
if (typeof window !== "undefined" && window.ReactNativeWebView) {
console.log("[Web Bridge] ReactNativeWebView detected. Adding message listener and sending BRIDGE_READY.");
window.ReactNativeWebView.postMessage(JSON.stringify({ type: "BRIDGE_READY" }));
const messageHandler = (event) => {
try {
const message = JSON.parse(event.data);
if (message.type === "STATE_INIT") {
if (message.data === null) {
stateByStore.delete(message.storeKey);
} else {
stateByStore.set(message.storeKey, message.data);
}
notifyStateListeners(message.storeKey);
notifyStoreListeners();
} else if (message.type === "STATE_UPDATE") {
if (message.data === null) {
stateByStore.delete(message.storeKey);
notifyStateListeners(message.storeKey);
notifyStoreListeners();
} else if (message.operations) {
const currentState = stateByStore.get(
message.storeKey
);
if (currentState) {
const result = applyPatch(currentState, message.operations);
stateByStore.set(
message.storeKey,
result.newDocument
);
notifyStateListeners(message.storeKey);
}
}
}
} catch (error) {
console.error("[Web Bridge] Error handling message:", error);
}
};
window.addEventListener("message", messageHandler);
} else {
console.warn("[Web Bridge] ReactNativeWebView NOT detected.");
}
return {
/**
* Check if the bridge is supported
* For web bridge, this checks if ReactNativeWebView is available
*/
isSupported: () => typeof window !== "undefined" && !!window.ReactNativeWebView,
/**
* Get a store by its key
* Returns undefined if the store doesn't exist
*/
getStore: (storeKey) => {
if (!stateByStore.has(storeKey))
return void 0;
let store = stores.get(storeKey);
if (!store) {
const storeImpl = {
getSnapshot: () => stateByStore.get(storeKey),
subscribe: (listener) => {
if (!stateListeners.has(storeKey)) {
stateListeners.set(storeKey, /* @__PURE__ */ new Set());
}
const listeners = stateListeners.get(storeKey);
listeners.add(listener);
if (stateByStore.has(storeKey)) {
listener(stateByStore.get(storeKey));
}
return () => {
listeners.delete(listener);
};
},
dispatch: async (event) => {
console.log(`[Web Bridge] Dispatching event for store ${String(storeKey)}:`, event);
if (!window.ReactNativeWebView) {
console.warn(
"[Web Bridge] Cannot dispatch events: ReactNativeWebView not available"
);
return;
}
const message = {
type: "EVENT",
storeKey,
event
};
console.log("[Web Bridge] Sending message to native:", message);
window.ReactNativeWebView.postMessage(JSON.stringify(message));
},
reset: () => {
console.warn("[Web Bridge] Reset operation not supported in web bridge");
},
// Add 'on' method to satisfy the interface
on: (eventType, _listener) => {
console.warn(`[Web Bridge] store.on("${eventType}", ...) was called, but listeners added on the web side are not executed. Add listeners on the native side or via the store's 'on' config.`);
return () => {
};
}
};
stores.set(storeKey, storeImpl);
store = storeImpl;
}
return store;
},
/**
* Set or remove a store for a given key
*/
setStore: (key, store) => {
if (store === void 0) {
stores.delete(key);
stateByStore.delete(key);
} else {
stores.set(key, store);
const snapshot = store.getSnapshot();
if (snapshot !== void 0) {
stateByStore.set(key, snapshot);
}
}
notifyStoreListeners();
},
/**
* Subscribe to store availability changes
* Returns an unsubscribe function
*/
subscribe: (listener) => {
storeListeners.add(listener);
return () => {
storeListeners.delete(listener);
};
}
};
}
export {
createWebBridge
};
//# sourceMappingURL=index.mjs.map