← Commit history
0.3.2: nbrowser_state must report only OUR sessions - it was leaking the user's own windows
5 files changed
+31−19
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.3.1+0.3.2
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser",- "version": "0.3.1",+ "version": "0.3.2", "expectedExtensionVersion": "0.2.0", "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.3.1",+ "version": "0.3.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+23−11@@ -1847,27 +1847,39 @@ function dispatch(verb, args) { // A caller must never title-guess a browser window: we OWN the windows we opened, so we return the // exact hwnd + tabId + live url/title per session, and optionally the page bitmap inline (base64), // so a WEB page or an AI can render it without a second round-trip or any window hunting.+ // nbrowser_state - "what am I driving right now, and what does it look like" in ONE call (issue #22).+ // SOURCE OF TRUTH = this bridge's OWN session registries. list_windows also returns the USER'S+ // windows (owner:"user"), and an early build leaked those into state - reporting the user's Gmail+ // as if nb were driving it. We now report ONLY sessions we opened, and only enrich them with live+ // url/title from rows we own. A caller must never title-guess a browser window: we own the ones we+ // opened, so we return the exact hwnd + tabId per session, and optionally the page bitmap inline. if (verb === 'nbrowser_state') { const sa = args || {}; guarded(resolve, 'nbrowser_state', async () => { const want = sa.sessionId;- const wins = await callSelf('nbrowser_list_windows', want ? { sessionId: want } : {});- const rows = (wins.windows || wins.sessions || []).filter((w) => !want || w.sessionId === want);- const sessions = rows.map((w) => {- const sid = w.sessionId;- return { sessionId: sid, hwnd: sessionHwnd.get(sid) || w.hwnd || null, windowId: w.windowId ?? null,- tabId: w.tabId ?? w.activeTabId ?? null, url: w.url || null, title: w.title || null,- profile: sessionProfile.get(sid) || w.profile || null, thread: sessionThread.get(sid) || null,+ const mine = [...new Set([...sessionHwnd.keys(), ...sessionProfile.keys(), ...sessionThread.keys()])]+ .filter((sid) => !want || sid === want);+ let live = [];+ try {+ const w = await callSelf('nbrowser_list_windows', { mine: true });+ live = (w.windows || w.sessions || []).filter((r) => r && r.sessionId && r.owner !== 'user');+ } catch {}+ const sessions = mine.map((sid) => {+ const r = live.find((x) => x.sessionId === sid) || {};+ return { sessionId: sid, hwnd: sessionHwnd.get(sid) || r.hwnd || null, windowId: r.windowId ?? null,+ tabId: r.tabId ?? r.activeTabId ?? null, url: r.url || null, title: r.title || null,+ profile: sessionProfile.get(sid) || r.profile || null, thread: sessionThread.get(sid) || null, purpose: sessionPurpose.get(sid) || null, osOpened: osOpened.has(sid) }; }); let shot = null;- if (sa.screenshot && sessions.length) {- const s = await callSelf('nbrowser_screenshot', { sessionId: want || sessions[0].sessionId });- if (s && s.base64) shot = { sessionId: want || sessions[0].sessionId, base64: s.base64, width: s.width, height: s.height };+ const shotSid = want || (sessions[0] && sessions[0].sessionId);+ if (sa.screenshot && shotSid) {+ const s = await callSelf('nbrowser_screenshot', { sessionId: shotSid });+ if (s && s.base64) shot = { sessionId: shotSid, base64: s.base64, width: s.width, height: s.height }; } resolve({ success: true, output: JSON.stringify({ ok: true, count: sessions.length, sessions, screenshot: shot, _hint: sessions.length- ? `The EXACT windows this bridge is driving - hwnd is authoritative, so never match a browser window by TITLE (a title is the active tab and changes while loading; several windows can share a substring - that is what broke the web-control demo, issue #22). Pass screenshot:true to get the page bitmap inline (base64) in the same call. Act on a window with its hwnd (ab desktop_screenshot_window {hwnd} for the frame + native popups) or its sessionId (nbrowser_* for the page).`+ ? `The EXACT windows THIS bridge opened - hwnd is authoritative, so never match a browser window by TITLE (a title is the active tab, it changes while loading, and several windows share substrings - that is what broke the web-control demo, issue #22). The user's OWN windows are deliberately excluded (nbrowser_os_windows shows every browser window on the machine, with ownership attribution). screenshot:true returns the page bitmap inline in this same call.` : 'This bridge is driving NO windows right now. Open one with nbrowser_open_window {sessionId, thread, purpose, profile, url} - it returns the hwnd + tabId of exactly that window.' }) }); }); return;
native-browser-bridge-manifest.json+5−5@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.3.1",- "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.3.1/native-browser-bridge-v0.3.1.zip",- "sha256": "de4a1252d3658cf71460a8bb6333c2a6c18613d588e7f7ee677dc60a4487e444",- "size": 106040,- "released_at": "2026-08-19T08:16:46Z"+ "version": "0.3.2",+ "url": "https://wiki.adom.inc/download/adom/nb-bridge/0.3.2/native-browser-bridge-v0.3.2.zip",+ "sha256": "1e4c38dc7a08130301db381609f62f9415efeeb064589be65756dfa3275be372",+ "size": 106310,+ "released_at": "2026-08-19T08:20:40Z" }