aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/platform.js
diff options
context:
space:
mode:
authorChristophe Besson <cbesson@gmail.com>2026-09-30 12:57:58 +0200
committerChristophe Besson <cbesson@gmail.com>2026-09-30 12:57:58 +0200
commit2c6921aa2c35ffd41b6c453e6700574ef631ba2c (patch)
tree01c766e13607e4f957900bfd36b4f722e8c8b3c5 /packages/meshbay-hub/src/meshbay_hub/static/platform.js
parent8a4651e9d223de856ff085b329801998f95db138 (diff)
downloadmeshbay-2c6921aa2c35ffd41b6c453e6700574ef631ba2c.tar.gz
fix(client): the page names node operations, and the app confirms what widens the node
node:call is replaced by named operations with checked arguments; hosting a group, sharing an unpicked folder, key rotation, denylist clearing and a change of node account are confirmed by a native dialog. Every channel checks its sender, secrets:get/set/clear are gone, node:start writes the app's own hub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/platform.js')
-rw-r--r--packages/meshbay-hub/src/meshbay_hub/static/platform.js57
1 files changed, 29 insertions, 28 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/platform.js b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
index a3fb80b..8bf09b4 100644
--- a/packages/meshbay-hub/src/meshbay_hub/static/platform.js
+++ b/packages/meshbay-hub/src/meshbay_hub/static/platform.js
@@ -67,30 +67,15 @@ export const capabilities = {
};
/**
- * Where the identity keys live.
+ * The app's secret store, as far as the page may know it.
*
- * In a browser: exactly where they live today — IndexedDB and sessionStorage,
- * with the keypair bundle on the node as the way a second browser recovers
- * them, which is finding C4 and is the reason the app exists.
- *
- * In the app: the OS keychain, and no bundle is stored anywhere. That is what
- * closes C4 for a native device — unconditionally for that device, and for the
- * account only once it stops signing in from a browser too.
+ * The store is the main process's (the OS keychain through safeStorage) and it
+ * holds the device's hub key, which the page is never handed. The page used to
+ * be able to read and write it by name; nothing here did, and that was a way to
+ * both read the key and replace it. What is left is whether the OS protects it.
*/
export const secrets = {
available: Boolean(bridge && bridge.secrets),
- async get(name) {
- if (!bridge || !bridge.secrets) return null;
- return bridge.secrets.get(name);
- },
- async set(name, value) {
- if (!bridge || !bridge.secrets) return false;
- return bridge.secrets.set(name, value);
- },
- async clear(name) {
- if (!bridge || !bridge.secrets) return false;
- return bridge.secrets.clear(name);
- },
/**
* Whether the OS is really protecting them.
*
@@ -257,9 +242,15 @@ export const node = {
if (!bridge || !bridge.node) throw new Error('Node bridge not available');
return bridge.node.start(opts);
},
- async call(method, path, body) {
+ /**
+ * One of the local node's operations, by name (`NODE_OPS` in the desktop
+ * client's main.js). The page never names a route: the main process checks
+ * the arguments, builds the request, and asks the person itself before
+ * anything that widens what the node shares.
+ */
+ async op(name, args) {
if (!bridge || !bridge.node) throw new Error('Node bridge not available');
- return bridge.node.call(method, path, body);
+ return bridge.node.op(name, args);
},
async pairingCode() {
return bridge && bridge.node ? bridge.node.pairingCode() : null;
@@ -344,7 +335,7 @@ export async function watchIndexProgress(groupId, onUpdate, { intervalMs = 500 }
for (;;) {
let status;
try {
- status = await node.call('GET', `/api/groups/${groupId}/index-status`);
+ status = await node.op('groupIndexStatus', { groupId });
} catch {
// The node went away mid-poll — stop rather than spin forever; the
// caller's own connection-status handling already covers that case.
@@ -376,12 +367,12 @@ export async function waitForGroupHosted(groupId, onProgress,
const deadline = Date.now() + timeoutMs;
for (;;) {
try {
- const status = await node.call('GET', `/api/groups/${groupId}/index-status`);
+ const status = await node.op('groupIndexStatus', { groupId });
if (onProgress) onProgress(status);
} catch { /* keep waiting — the loopback API can be momentarily busy */ }
try {
- const list = await node.call('GET', '/api/groups');
+ const list = await node.op('groups');
if (Array.isArray(list.groups) && list.groups.some((g) => g.id === groupId)) return;
} catch { /* keep waiting */ }
@@ -420,7 +411,7 @@ export async function waitForRootsIndexed(groupId, onProgress,
for (;;) {
let status;
try {
- status = await node.call('GET', `/api/groups/${groupId}/index-status`);
+ status = await node.op('groupIndexStatus', { groupId });
} catch {
return; // the node went away mid-poll — same stance as watchIndexProgress
}
@@ -507,9 +498,19 @@ export async function setTrayLabels(labels) {
return bridge.setTrayLabels(labels);
}
+/**
+ * Tell the app which language the interface is in. The confirmations its main
+ * process draws for itself are worded from the same catalogues, which it reads
+ * from the packaged files -- only the code crosses the bridge.
+ */
+export async function setUiLocale(code) {
+ if (!bridge || !bridge.setLocale) return false;
+ return bridge.setLocale(code);
+}
+
export default { isNative, hubBase, capabilities, secrets, nativeSave,
apiFetch, device, bridgeMessage, folder, rootPicker, node,
- cast, minimizeToTray, setTrayLabels };
+ cast, minimizeToTray, setTrayLabels, setUiLocale };
// Also a global, because `transport.js` is loaded as a classic script — it
// predates the module graph and exposes `MeshBayTransport` the same way. The
@@ -519,5 +520,5 @@ if (typeof window !== 'undefined') {
window.MeshBayPlatform = { isNative, hubBase, capabilities, secrets,
nativeSave, apiFetch, device,
bridgeMessage, folder, rootPicker, node,
- cast, minimizeToTray, setTrayLabels };
+ cast, minimizeToTray, setTrayLabels, setUiLocale };
}