diff options
Diffstat (limited to 'packages/meshbay-client/src/main.js')
| -rw-r--r-- | packages/meshbay-client/src/main.js | 70 |
1 files changed, 69 insertions, 1 deletions
diff --git a/packages/meshbay-client/src/main.js b/packages/meshbay-client/src/main.js index 9ff0069..ab579a1 100644 --- a/packages/meshbay-client/src/main.js +++ b/packages/meshbay-client/src/main.js @@ -1701,6 +1701,71 @@ function describeUnreachable(url, error) { return `Could not reach ${url}: ${detail}`; } +// ── The version gate ──────────────────────────────────────────────────────── + +/** Compare two dotted versions. -1, 0 or 1; unreadable sorts as equal. */ +function compareVersions(a, b) { + const parse = (v) => String(v || '').split('.').map((n) => parseInt(n, 10)); + const [x, y] = [parse(a), parse(b)]; + if (x.some(Number.isNaN) || y.some(Number.isNaN)) return 0; + for (let i = 0; i < Math.max(x.length, y.length); i++) { + const d = (x[i] || 0) - (y[i] || 0); + if (d) return d < 0 ? -1 : 1; + } + return 0; +} + +/** + * Refuse to start when this build is older than the hub will talk to. + * + * The reason this exists rather than letting the handshake do it: the SPA is + * served by the hub and picks up a new client on reload, but **this + * application ships its own interface**. On the MNP 3.0 flag day an + * un-updated one can still sign in, still list groups, and then fail every + * connection with `version_too_old` — a refusal in a protocol vocabulary, + * surfacing as a node that will not talk, with nothing anyone can act on. + * + * So the question is asked once, up front, of `/v1/hub/version`, which has + * carried `client.minimum` since before there was a client to check it. + * + * **Unreachable is not too old.** A hub that is down, a laptop with no network, + * a captive portal: none of those are a reason to refuse to open the + * application, and treating them as one would make an offline start impossible + * for ever. Only a definite answer, saying in so many words that this version + * is below the minimum, stops anything. + */ +async function refuseIfTooOld() { + const base = String(config.hubBase || '').replace(/\/+$/, ''); + if (!base) return false; // First run: there is no hub to ask yet. + let info; + try { + const r = await fetch(`${base}/v1/hub/version`, + { signal: AbortSignal.timeout(10000) }); + if (!r.ok) return false; + info = await r.json(); + } catch { + return false; + } + const minimum = info && info.client && info.client.minimum; + if (!minimum) return false; + const mine = app.getVersion(); + if (compareVersions(mine, minimum) >= 0) return false; + + const { response } = await dialog.showMessageBox({ + type: 'warning', + title: 'Update required', + message: 'This version of MeshBay can no longer connect', + detail: `This application is version ${mine}, and ${base} now requires ` + + `${minimum} or later.\n\nDownload the current version and install it ` + + 'over this one — your groups, keys and settings are kept.', + buttons: ['Download the update', 'Quit'], + defaultId: 0, + cancelId: 1, + }); + if (response === 0) await shell.openExternal(base); + return true; +} + // ── Lifecycle ─────────────────────────────────────────────────────────────── // One instance. Two would fight over the config file and the secrets blob, and @@ -1712,7 +1777,10 @@ if (!app.requestSingleInstanceLock()) { showFromTray(); }); - app.whenReady().then(() => { + app.whenReady().then(async () => { + // Before anything else is built. A window that opens and then cannot + // connect is the failure this replaces. + if (await refuseIfTooOld()) { app.quit(); return; } registerUiProtocol(); // Before ensureTray: buildTrayMenu reads `nodeService`, which registerBridge // assigns, so creating the tray after it means the Start/Stop entry is on |