diff --git a/app/src/main/assets/extensions/bridge/background.js b/app/src/main/assets/extensions/bridge/background.js new file mode 100644 index 0000000..5971701 --- /dev/null +++ b/app/src/main/assets/extensions/bridge/background.js @@ -0,0 +1,94 @@ +/* + * StandAPP Bridge - background script + * + * Responsibilities: + * 1. Connect to native via browser.runtime.connectNative("standapp"). + * 2. Forward executeJs messages from native to every connected content port. + * 3. Accept content-script connections, and signal "ready" when both the + * native link and the content port are up. Content script uses this + * ready flag to decide whether to enable the WebExtension push channel + * or fall back to the legacy prompt() polling bridge. + */ + +"use strict"; + +const NATIVE_APP = "standapp"; +const CONTENT_PORT_NAME = "bridge"; +const RECONNECT_DELAYS_MS = [1000, 2000, 4000, 8000, 16000, 30000]; + +let nativePort = null; +let reconnectAttempt = 0; +let contentPorts = new Set(); + +function nextReconnectDelay() { + const idx = Math.min(reconnectAttempt, RECONNECT_DELAYS_MS.length - 1); + const delay = RECONNECT_DELAYS_MS[idx]; + reconnectAttempt++; + return delay; +} + +function broadcastReadyToContent() { + const ready = nativePort !== null; + const msg = { type: "bridgeStatus", ready: ready }; + contentPorts.forEach(function (p) { + try { p.postMessage(msg); } catch (e) {} + }); +} + +function handleNativeMessage(msg) { + if (!msg || typeof msg !== "object") return; + if (msg.type !== "executeJs") return; + if (typeof msg.script !== "string" || msg.script.length === 0) return; + contentPorts.forEach(function (p) { + try { p.postMessage({ type: "executeJs", script: msg.script }); } + catch (e) {} + }); +} + +function connectNative() { + try { + nativePort = browser.runtime.connectNative(NATIVE_APP); + } catch (e) { + console.log("[bridge-bg] connectNative threw", String(e)); + nativePort = null; + scheduleReconnect(); + broadcastReadyToContent(); + return; + } + + reconnectAttempt = 0; + console.log("[bridge-bg] native port connected"); + + nativePort.onMessage.addListener(handleNativeMessage); + + nativePort.onDisconnect.addListener(function () { + console.log("[bridge-bg] native port disconnected"); + nativePort = null; + broadcastReadyToContent(); + scheduleReconnect(); + }); + + broadcastReadyToContent(); +} + +function scheduleReconnect() { + const delay = nextReconnectDelay(); + setTimeout(function () { + if (nativePort === null) connectNative(); + }, delay); +} + +browser.runtime.onConnect.addListener(function (port) { + if (port.name !== CONTENT_PORT_NAME) return; + contentPorts.add(port); + console.log("[bridge-bg] content port added, total =", contentPorts.size); + + port.onDisconnect.addListener(function () { + contentPorts.delete(port); + console.log("[bridge-bg] content port removed, total =", contentPorts.size); + }); + + port.postMessage({ type: "bridgeStatus", ready: nativePort !== null }); +}); + +connectNative(); diff --git a/app/src/main/assets/extensions/bridge/bridge.js b/app/src/main/assets/extensions/bridge/bridge.js index 507d320..0d69633 100644 --- a/app/src/main/assets/extensions/bridge/bridge.js +++ b/app/src/main/assets/extensions/bridge/bridge.js @@ -1,5 +1,28 @@ -(function() { - var code = [ +/* + * StandAPP Bridge - content script + * + * Runs in the content-script isolated world, with manifest content_scripts + * settings { matches: "", run_at: "document_start" }. + * + * Two responsibilities: + * 1. Inject a page-context script that builds the window.android Proxy. + * The Proxy still routes every method call through prompt("bridge:...") + * so the page's synchronous calls (e.g. JSON.parse(window.android.xxx())) + * keep working without any front-end change. + * 2. Subscribe to a Port from background.js for native->page pushes. + * The push channel is the WebExtension Port; received scripts are + * injected into the page DOM (same