diff options
Diffstat (limited to 'packages/meshbay-client/src/preload.js')
| -rw-r--r-- | packages/meshbay-client/src/preload.js | 66 |
1 files changed, 66 insertions, 0 deletions
diff --git a/packages/meshbay-client/src/preload.js b/packages/meshbay-client/src/preload.js new file mode 100644 index 0000000..9c273e2 --- /dev/null +++ b/packages/meshbay-client/src/preload.js @@ -0,0 +1,66 @@ +/** + * The bridge, and the whole of it. + * + * `contextIsolation` puts this in its own world, so what is exposed here is all + * the page can reach — page script cannot read the closure, cannot replace + * these functions for other code, and cannot call an IPC channel that is not + * named below. That is what makes the enumeration meaningful rather than + * decorative. + * + * The rule for anything added here: **the renderer never names a path, a file + * handle or a process.** It asks for a dialog and receives an opaque id. The + * renderer parses decrypted content from nodes — video, images, filenames — + * which is attacker-controlled input, so it is treated as hostile even though + * it is our own code. + */ + +'use strict'; + +const { contextBridge, ipcRenderer } = require('electron'); + +const HUB_BASE = (process.argv.find(a => a.startsWith('--meshbay-hub=')) || '') + .slice('--meshbay-hub='.length); + +contextBridge.exposeInMainWorld('meshbay', { + // Where the hub is. The interface prefixes every API path with this; in a + // browser the same function returns '' and relative paths go to the origin + // that served the page. + // + // Read from a process argument, not over IPC: the interface asks for this + // while its modules are still loading, before anything can await, and + // synchronous IPC would block the renderer for a value that cannot change + // within a run. + hubBase: () => HUB_BASE, + setHubBase: (base) => ipcRenderer.invoke('hub:set', base), + + // What this build can do that a browser cannot. The interface renders + // features gated on these nowhere at all in a browser, rather than offering + // something that fails when clicked. + capabilities: { + nodeAdmin: true, + localFolders: true, + nativeSave: true, + }, + + secrets: { + get: (name) => ipcRenderer.invoke('secrets:get', name), + set: (name, value) => ipcRenderer.invoke('secrets:set', name, value), + clear: (name) => ipcRenderer.invoke('secrets:clear', name), + // 'unprotected_fallback' means safeStorage found no keyring and is using a + // fixed key. Encrypted on disk, by a key that is not a secret — the + // interface says so rather than letting someone believe otherwise. + backend: () => ipcRenderer.invoke('secrets:backend'), + }, + + // A save dialog and a write that never passes back through the page. The + // renderer holds an id, not a path. + saveFile: async (suggestedName) => { + const handle = await ipcRenderer.invoke('save:begin', suggestedName); + if (!handle) return null; + return { + name: handle.name, + write: (chunk) => ipcRenderer.invoke('save:write', handle.id, chunk), + close: () => ipcRenderer.invoke('save:end', handle.id), + }; + }, +}); |