← Commit history
nb 0.4.7: register a host on first DATA not on connect (kills pending-N churn from port probers); battery guard matches 0.4.6 own-window semantics
6 files changed
+21−12
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.4.6+0.4.7
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser",- "version": "0.4.6",+ "version": "0.4.7", "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.6",+ "version": "0.4.7", "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+8−3@@ -2597,11 +2597,16 @@ const hostSrv = net.createServer((sock) => { try { sock.destroy(); } catch {} return; }- // Register provisionally so this host is drivable immediately; the `hello` re-keys it to the real- // profile. Several profiles can be connected at once.- registerProvisional(sock);+ // REGISTER ON FIRST DATA, not on connect (2026-08-22). A bare TCP connect is NOT a native host:+ // ab's health checks / port probes open-and-close this port constantly, and registering each one+ // provisionally produced endless pending-N churn (ConfRoomROG reached provSeq 15749 with NO host+ // installed, and readiness read the corpses as "resolving"). A real host talks - its hello re-announce+ // goes out the moment it connects - so the first inbound byte is the earliest honest registration+ // point, and a silent prober now never enters conns at all.+ let registered = false; let buf = ''; sock.on('data', (chunk) => {+ if (!registered) { registered = true; registerProvisional(sock); } // 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');
native-browser-bridge-manifest.json+5−5@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.4.6",- "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.4.6/native-browser-bridge-v0.4.6.zip",- "sha256": "1c1200b898c6806e9daf933e82e6baa0ad5c681eaf1e4fba2206b04d19899fdf",- "size": 105694,- "released_at": "2026-08-22T14:26:07Z"+ "version": "0.4.7",+ "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.4.7/native-browser-bridge-v0.4.7.zip",+ "sha256": "bbcfedbc5240102ab83b6e53783e79f2f69f97fff2c47daeabe8a7766f23a034",+ "size": 105906,+ "released_at": "2026-08-22T14:30:58Z" }
tools/verb-battery.mjs+5−1@@ -205,7 +205,11 @@ const T = { guard_autologin: () => { const r = call('nbrowser_autologin', { sessionId: SID, host: 'wiki.adom.inc' }); return A(r.ok !== true && r.loggedIn !== true, 'refused without confirm:true', 'AUTOLOGIN RAN WITHOUT CONFIRM: ' + JSON.stringify(r).slice(0, 120)); }, guard_close_all: () => { const r = call('nbrowser_close'); return A(r.ok !== true || /confirm/i.test(JSON.stringify(r)), 'close-all demands confirmCloseAll', 'CLOSE-ALL RAN UNCONFIRMED: ' + JSON.stringify(r).slice(0, 120)); }, guard_record_fallback: () => { const r = call('nbrowser_record_start', { method: 'getdisplaymedia', sessionId: SID }); return A(r.ok !== true, 'picker fallback refused without fallbackConfirmed', 'FALLBACK RAN UNCONFIRMED: ' + JSON.stringify(r).slice(0, 140)); },- guard_force_close: () => { const r = call('nbrowser_force_close', { hwnd: ctx.hwnd }); const refused = r.errorCode === 'owned_by_other' || /owned_by_other|Only that thread/i.test(JSON.stringify(r)); return A(refused && r.closed !== true, 'live-session hwnd refused (owned_by_other) - ownership absolute even via force path', 'FORCE_CLOSE DID NOT REFUSE A LIVE SESSION WINDOW: ' + JSON.stringify(r).slice(0, 140)); },+ // Since 0.4.6: force_close on YOUR OWN live session's hwnd yields the two-step attestation ticket+ // (the owner may clean up their own OS window - the open_os_window cleanup path depends on it), while+ // ANOTHER thread's window still refuses owned_by_other. The battery runs as the owner, so the pass is:+ // ticket offered, nothing closed by a single call. An outright closed:true here is the failure.+ guard_force_close: () => { const r = call('nbrowser_force_close', { hwnd: ctx.hwnd }); const refused = r.errorCode === 'owned_by_other' || /owned_by_other|Only that thread/i.test(JSON.stringify(r)); const ticketed = r.errorCode === 'attestation_required' || /needsAttestation|overrideTicket/i.test(JSON.stringify(r)); return A((refused || ticketed) && r.closed !== true, ticketed ? 'own-window ticket offered, nothing closed without attestation' : 'live-session hwnd refused (owned_by_other)', 'FORCE_CLOSE CLOSED (or mishandled) A LIVE SESSION WINDOW IN ONE CALL: ' + JSON.stringify(r).slice(0, 140)); }, profile_block: () => { const b = call('nbrowser_profile_block', { profile: PROFILE }); const g = call('nbrowser_open_tab', { sessionId: SID, url: `${HARNESS}?blocked=1` }); const u = call('nbrowser_profile_unblock', { profile: PROFILE }); const blockedWorked = g.ok !== true || /block/i.test(JSON.stringify(g)); return A(b.ok !== false && u.ok !== false && blockedWorked, 'block refused a drive; unblock restored', `block:${JSON.stringify(b).slice(0, 80)} drive-under-block:${JSON.stringify(g).slice(0, 80)}`); }, profile_unblock: () => { const r = call('nbrowser_open_tab', { sessionId: SID, url: `${HARNESS}?after-unblock=1&cb=${CB}` }); if (r.tabId) call('nbrowser_close_tab', { sessionId: SID, tabId: r.tabId }); return A(r.ok !== false, 'driving works again after unblock', JSON.stringify(r).slice(0, 140)); },