/** * 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), // The device's hub key: generated, held and used natively. The page asks // for a signature and never sees a key — it parses hostile input. device: { ensure: () => call('device:ensure'), publicKey: () => call('device:public'), sign: (username) => call('device:sign', username), forget: () => call('device:forget'), }, // The bundle key and the identity on every node, held natively: the page // is told public keys and handed signatures and agreements. A signature is // asked for by kind and fields, never by bytes. keys: { available: () => call('keys:available'), deriveSession: (o) => call('keys:derive-session', o), commitPending: (u) => call('keys:commit-pending', u), dropPending: (u) => call('keys:drop-pending', u), hasSession: (u) => call('keys:has-session', u), forgetSession: (u) => call('keys:forget-session', u), identity: (u, n) => call('keys:identity', u, n), openBundle: (u, n, o) => call('keys:open-bundle', u, n, o), mint: (u, n) => call('keys:mint', u, n), sealBundle: (u, n, o) => call('keys:seal-bundle', u, n, o), sealRecovery: (u, n, m, name) => call('keys:seal-recovery', u, n, m, name), markSealed: (u, n, fp) => call('keys:mark-sealed', u, n, fp), fingerprint: (u) => call('keys:fingerprint', u), sign: (u, n, kind, fields) => call('keys:sign', u, n, kind, fields), shared: (u, n, peer) => call('keys:shared', u, n, peer), playlistKey: (u) => call('keys:playlist-key', u), browserAccess: (u) => call('keys:browser-access', u), setBrowserAccess: (u, on) => call('keys:set-browser-access', u, on), createdHere: (u) => call('keys:created-here', u), }, // Whether the OS protects what is stored. The store itself is not // reachable from here. secrets: { backend: () => call('secrets:backend'), }, }; 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 */ } } })();