← Commit history
v0.1.9: pass profileDir/userDataDir to desktop_taskbar so AD composites the Adom mark onto the Chrome/Edge profile avatar (no longer evicts the profile face)
5 files changed
+43−11
bridge/BRIDGE_VERSION+1−1@@ -1 +1 @@-0.1.8+0.1.9
bridge/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "native-browser", "displayName": "Adom Native Browser (Chrome + Edge)",- "version": "0.1.8",+ "version": "0.1.9", "expectedExtensionVersion": "0.1.2", "description": "Drives the user's REAL signed-in Chrome/Edge via an installed extension (native-messaging transport). Mirrors the puppeteer bridge's verb surface as nbrowser_* but runs inside the actual logged-in profile, so cookies/SSO/saved-logins/captcha human-trust all work. The bridge binds an ephemeral loopback port (PORTS.md compliant) and writes a discovery file; the native-messaging host pumps stdio <-> this bridge; the extension never touches a TCP port.", "homepage": "https://wiki.adom.inc/adom/adom-browser-extension",
bridge/package.json+1−1@@ -1,6 +1,6 @@ { "name": "adom-native-browser-bridge",- "version": "0.1.8",+ "version": "0.1.9", "private": true, "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)", "main": "server.js",
bridge/server.js+35−3@@ -450,7 +450,39 @@ function armBadgeIdle(sid) { const prev = badgeIdle.get(sid); if (prev) clearTimeout(prev); badgeIdle.set(sid, setTimeout(() => { const h = sessionHwnd.get(sid); if (h) taskbar(h, { overlay: { badge: 'none' } }).catch(() => {}); badgeOn.delete(sid); badgeIdle.delete(sid); }, BADGE_IDLE_MS)); }-function applyBadge(sid, hwnd) { if (!sid || !hwnd) return; taskbar(hwnd, { overlay: { badge: 'adom', tooltip: 'Adom is driving this browser' } }); badgeOn.add(sid); armBadgeIdle(sid); }+// Map a driven profile → its Chrome/Edge profile DIRECTORY, so AD (>=1.9.110) can composite the Adom mark+// onto that profile's AVATAR instead of evicting it (the taskbar overlay has one slot = the avatar's slot).+// AD can't derive this itself: Chrome/Edge run ONE browser process for all profiles, so hwnd→PID→cmdline+// has no --profile-directory. We read the browser's Local State (profile.info_cache: dir → {user_name}).+const _avatarCache = new Map(); // profileKey -> {userDataDir, profileDir} | null+function avatarForProfile(profileKey) {+ if (!profileKey) return null;+ if (_avatarCache.has(profileKey)) return _avatarCache.get(profileKey);+ let result = null;+ try {+ const i = String(profileKey).indexOf(':');+ const browser = i >= 0 ? profileKey.slice(0, i) : 'chrome';+ const email = i >= 0 ? profileKey.slice(i + 1) : profileKey;+ const local = process.env.LOCALAPPDATA;+ if (local && email.includes('@')) {+ const root = browser === 'edge' ? path.join(local, 'Microsoft', 'Edge', 'User Data') : path.join(local, 'Google', 'Chrome', 'User Data');+ const j = JSON.parse(fs.readFileSync(path.join(root, 'Local State'), 'utf8'));+ const cache = (j.profile && j.profile.info_cache) || {};+ for (const [dir, info] of Object.entries(cache)) {+ if (String((info && info.user_name) || '').toLowerCase() === email.toLowerCase()) { result = { userDataDir: root, profileDir: dir }; break; }+ }+ }+ } catch {}+ _avatarCache.set(profileKey, result);+ return result;+}+function applyBadge(sid, hwnd, profileKey) {+ if (!sid || !hwnd) return;+ const overlay = { badge: 'adom', tooltip: 'Adom is driving this browser' };+ const av = avatarForProfile(profileKey || sessionProfile.get(sid)); // composite onto the profile avatar if we can find it+ if (av) { overlay.profileDir = av.profileDir; overlay.userDataDir = av.userDataDir; }+ taskbar(hwnd, { overlay }); badgeOn.add(sid); armBadgeIdle(sid);+} // Called on every DRIVE verb: the badge tracks ACTIVE driving, not mere ownership. On the first activity // (or after a bridge restart) it lazily correlates the driven profile's taskbar window; then it just // re-arms the idle timer. An IDLE owned window (a thread opened it then went quiet) is NEVER badged here,@@ -468,7 +500,7 @@ async function touchBadge(sid, profileKey) { try { const list = await chromeWindowsDetailed(); const m = list.find((w) => (w.title || '').includes(email)); if (m) { h = m.hwnd; sessionHwnd.set(sid, h); } } catch {} if (!h) return; }- if (!badgeOn.has(sid)) applyBadge(sid, h); else armBadgeIdle(sid);+ if (!badgeOn.has(sid)) applyBadge(sid, h, profileKey); else armBadgeIdle(sid); } function clearBadge(sid) { const t = badgeIdle.get(sid); if (t) clearTimeout(t); badgeIdle.delete(sid); badgeOn.delete(sid); badgeTry.delete(sid); } // Capture the driven window's hwnd by DIFFING the real-Chrome window list across the open (the new@@ -1373,7 +1405,7 @@ async function onHostFrame(frame) { if (!neu) neu = list.map((w) => w.hwnd).find((h) => !preOpenHwnds.has(h)) || null; // diff fallback (title empty while loading) if (!neu) { const email = emailOf(p.profileKey); if (email.includes('@')) { const m = list.find((w) => (w.title || '').includes(email)); if (m) neu = m.hwnd; } } // profile-level fallback } catch {}- if (neu) { lastHwnd = neu; if (sid) sessionHwnd.set(sid, neu); applyBadge(sid, neu); break; } // applyBadge = badge + arm idle-expiry+ if (neu) { lastHwnd = neu; if (sid) sessionHwnd.set(sid, neu); applyBadge(sid, neu, p.profileKey); break; } // applyBadge = badge (composited onto the profile avatar) + arm idle-expiry await new Promise((r) => setTimeout(r, 250)); } payload = { ...payload, ...(neu ? { hwnd: neu } : {}), _taskbarHint: flashHint(),
native-browser-bridge-manifest.json+5−5@@ -1,8 +1,8 @@ { "manifest_version": 1,- "version": "0.1.8",- "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.8/native-browser-bridge-v0.1.8.bin",- "sha256": "1ed081a1136b767e6136ec478fdcc0df5a4bc1c1916e46af631cd0d68b6f3350",- "size": 57125,- "released_at": "2026-07-14T00:00:00Z"+ "version": "0.1.9",+ "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.1.9/native-browser-bridge-v0.1.9.bin",+ "sha256": "c2753b101d02ea99a3d819a47e1967cb8088026b833746bf3a1d8e8bd48687c4",+ "size": 57677,+ "released_at": "2026-07-15T00:00:00Z" }