← Commit history

nb 0.4.2: reap zombie hosts on EVIDENCE (unanswered probes), never on age alone

John Lauer ·d381680efb ·2mo ago ·parent 60ccdfa
5 files changed +27−11
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.4.1+0.4.2
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser",-  "version": "0.4.1",+  "version": "0.4.2",   "expectedExtensionVersion": "0.2.5",   "description": "Drives the user's REAL signed-in Chrome/Edge, in two layers. Layer 1 (nb) needs NOTHING installed and works on any machine: nbrowser_open_os_window (open a URL in the user's real profile, background+flashing, the OAuth/consent path), nbrowser_os_windows, nbrowser_profiles, nbrowser_wake_profile, nbrowser_force_close. Layer 2 (nbe, the Adom Native Browser Extension) unlocks the rest: CDP driving (navigate/click/read/screenshot) inside their logged-in profile. The bridge binds an ephemeral loopback port and the native-messaging host pumps stdio <-> bridge.",   "homepage": "https://wiki.adom.inc/adom/nb-bridge",
bridge/package.json+1−1
@@ -1,6 +1,6 @@ {   "name": "adom-native-browser-bridge",-  "version": "0.4.1",+  "version": "0.4.2",   "private": true,   "description": "Native-browser bridge for Bridge (ab): layer 1 (nb) plus layer 2 via nbe, the Adom Native Browser Extension (Chrome + Edge).",   "main": "server.js",
bridge/server.js+19−3
@@ -734,8 +734,17 @@ let _lastResolveNudge = 0; // offering the install, and the user's other thread reported "nb opens windows but nbe says not // connected". A pending conn that has ignored several whoami nudges is not resolving, it is dead: say so // and reap it, so state falls through to the honest needs-extension.-const PENDING_GRACE_MS = 45000;-const isZombie = (c) => !!(c && c.provisional && c.since && (Date.now() - c.since) > PENDING_GRACE_MS);+// ⛔ REAP ON EVIDENCE, NOT ON AGE. The first cut of this reaper used age alone and immediately killed+// HEALTHY connections on John's laptop: after a bridge restart the native host reconnects its socket but+// the extension does not spontaneously re-announce, so a perfectly drivable profile sits at pending-N+// with no hello and looked exactly like a corpse. A pending conn is only dead if it has IGNORED repeated+// whoami probes - silence we actually asked for. Any frame at all on the socket (hello, reply, pong)+// clears the strike count, so a live-but-quiet extension is never reaped.+const PENDING_GRACE_MS = 120000;  // never reap inside the first 2 minutes, whatever the probes say+const PENDING_MAX_STRIKES = 4;    // 4 unanswered whoami nudges (~40s at the 10s cadence)+const isZombie = (c) => !!(c && c.provisional && c.since+  && (Date.now() - c.since) > PENDING_GRACE_MS+  && (c.probeStrikes || 0) >= PENDING_MAX_STRIKES); function resolvePendingProfiles() {   const now = Date.now(); if (_lastResolveNudge + 10000 > now) return; _lastResolveNudge = now;   for (const [k, c] of conns) {@@ -749,7 +758,12 @@ function resolvePendingProfiles() {       if (activeProfile === k) activeProfile = null;       continue;     }-    try { const id = ++seq; pending.set(id, { resolve: () => {}, timer: setTimeout(() => pending.delete(id), 4000), verb: 'nbrowser_whoami', profileKey: k, _internal: true }); c.sock.write(JSON.stringify({ id, verb: 'nbrowser_whoami', args: {} }) + '\n'); } catch {}+    try {+      const id = ++seq;+      pending.set(id, { resolve: () => {}, timer: setTimeout(() => pending.delete(id), 4000), verb: 'nbrowser_whoami', profileKey: k, _internal: true });+      c.sock.write(JSON.stringify({ id, verb: 'nbrowser_whoami', args: {} }) + '\n');+      c.probeStrikes = (c.probeStrikes || 0) + 1;   // cleared by ANY inbound frame on this socket+    } catch {}   } } // Paint the "Adom is driving" badge: our composite (circle avatar + favicon corner) or NOTHING yet.@@ -2560,6 +2574,8 @@ const hostSrv = net.createServer((sock) => {   registerProvisional(sock);   let buf = '';   sock.on('data', (chunk) => {+    // ANY inbound byte proves something is alive behind this socket - clear its zombie strikes.+    for (const [, c] of conns) if (c.sock === sock) { c.probeStrikes = 0; break; }     buf += chunk.toString('utf8');     let nl;     while ((nl = buf.indexOf('\n')) >= 0) {
native-browser-bridge-manifest.json+5−5
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.4.1",-  "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.4.1/native-browser-bridge-v0.4.1.zip",-  "sha256": "67ee216a27030a2a7985852d80dab2e63cee92631f50a12c2f44861e8b9b422b",-  "size": 104250,-  "released_at": "2026-08-22T14:03:25Z"+  "version": "0.4.2",+  "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.4.2/native-browser-bridge-v0.4.2.zip",+  "sha256": "7807d3166aae85c0f004f8f56511fcd7a560748560576999dfbe0916b11b5845",+  "size": 104737,+  "released_at": "2026-08-22T14:06:07Z" }