aboutsummaryrefslogtreecommitdiffstats
path: root/packages/meshbay-client/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/meshbay-client/src')
-rw-r--r--packages/meshbay-client/src/main.js70
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