/** * 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 */ } } })();