diff options
| author | Christophe Besson <cbesson@gmail.com> | 2026-10-02 12:14:01 +0200 |
|---|---|---|
| committer | Christophe Besson <cbesson@gmail.com> | 2026-10-03 14:24:54 +0200 |
| commit | 6ebfc86392a74dc0ae18f0d2703a3f91b8977d46 (patch) | |
| tree | 99cf5d8b2939a9cf3af16fe4258186e9d78ed7ce /packages/meshbay-android/app/src/main/assets/bridge | |
| parent | 4e33fca55e48bbf6233e950355d31c603d88a6e6 (diff) | |
| download | meshbay-6ebfc86392a74dc0ae18f0d2703a3f91b8977d46.tar.gz | |
feat(android): client shell with the interface from the package
WebView over the packaged UI (copied from hub/static at build time), the
desktop CSP as a header, a bridge answering our top-level document only,
hub calls from native to the signed-in hub. Keys stay in the page for now.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-android/app/src/main/assets/bridge')
| -rw-r--r-- | packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js | 84 |
1 files changed, 84 insertions, 0 deletions
diff --git a/packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js b/packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js new file mode 100644 index 0000000..350e087 --- /dev/null +++ b/packages/meshbay-android/app/src/main/assets/bridge/meshbay-bridge.js @@ -0,0 +1,84 @@ +/** + * The bridge, and the whole of it — the Android counterpart of + * meshbay-client/src/preload.js, with the same shape wherever it offers + * something at all. + * + * Injected at document start into documents of the packaged origin, before any + * page script. It takes the native port the listener injected, hides the + * global, and exposes `window.meshbay` frozen. There is no context isolation + * on Android: page script runs in the same world, so what this buys is that + * nothing can reach the raw port by name, not that this file is out of reach. + * The confinement that matters is native — the listener answers the packaged + * origin's top-level document only, and checks every argument. + * + * What the desktop offers and this build does not is ABSENT, not a function + * that refuses: `platform.js` decides what to show from whether an object + * exists (`platform.node.available`, `platform.folder.available`, …). + * + * `HUB_BASE` is prepended by the shell when it injects this file: the + * interface asks for it while its modules load, before anything can await. + */ +(function () { + 'use strict'; + const port = window.meshbayNative; + try { delete window.meshbayNative; } catch (e) { /* already gone */ } + // A same-origin child frame gets the port too; it gets no bridge, and native + // refuses whatever it sends anyway. + if (!port || window.top !== window) return; + + const pending = new Map(); + let seq = 0; + port.onmessage = (event) => { + let reply; + try { reply = JSON.parse(event.data); } catch (e) { return; } + const waiter = pending.get(reply.id); + if (!waiter) return; + pending.delete(reply.id); + if (reply.ok) waiter.resolve(reply.value); + else waiter.reject(new Error(reply.error)); + }; + const call = (channel, ...args) => new Promise((resolve, reject) => { + const id = ++seq; + pending.set(id, { resolve, reject }); + port.postMessage(JSON.stringify({ id, ch: channel, args })); + }); + + const meshbay = { + hubBase: () => HUB_BASE, + setHubBase: (base) => call('hub:set', base), + + capabilities: { + nodeAdmin: false, // no node runs on a phone (§11.3) + localFolders: false, + nativeSave: false, // phase 2 + lanCast: false, // phase 3 + tray: false, + }, + + setLocale: (code) => call('ui:locale', code), + + // The page's origin is refused by the hub's absent CORS, and is not a + // credential anyway: native goes, to the signed-in hub only. + fetch: (url, init) => call('hub:fetch', url, init), + + resolveStun: (urls) => call('ice:resolve-stun', urls), + }; + + const freeze = (o) => { + Object.freeze(o); + for (const v of Object.values(o)) if (v && typeof v === 'object' && !Object.isFrozen(v)) freeze(v); + return o; + }; + Object.defineProperty(window, 'meshbay', { + value: freeze(meshbay), writable: false, configurable: false, enumerable: false, + }); + + // The WebView exposes File System Access and cannot back it with anything a + // person can see. Left in place, `downloads.SUPPORTED` reads true and a + // download could take a path that fails — or reach the blob floor silently. + for (const name of ['showDirectoryPicker', 'showSaveFilePicker', 'showOpenFilePicker']) { + try { + Object.defineProperty(window, name, { value: undefined, writable: false, configurable: false }); + } catch (e) { /* not definable: leave it, native save comes first anyway */ } + } +})(); |