← Commit history

bridge 0.0.37: cross-AD notify - user UAC/notify toasts route to the user's laptop (target:all) instead of the local VM desktop; dev skill documents it + the winvm/RDP testing assumption

John Lauer ·6b0a4163ad ·4mo ago ·parent f42107a
6 files changed +55−12
bridge/BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.0.36\ No newline at end of file+0.0.37\ No newline at end of file
bridge/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "native-browser",   "displayName": "Adom Native Browser (Chrome + Edge)",-  "version": "0.0.36",+  "version": "0.0.37",   "expectedExtensionVersion": "0.0.20",   "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.0.36",+  "version": "0.0.37",   "private": true,   "description": "adom-desktop bridge for the Adom native-browser extension (Chrome + Edge)",   "main": "server.js",
bridge/server.js+29−5
@@ -308,13 +308,16 @@ function discoverAdBase() {   } catch {}   return 'http://127.0.0.1:47200'; }-function adCommand(app, command, args) {+function adCommand(app, command, args, target) {   return new Promise((resolve) => {     let u; try { u = new URL('/command', discoverAdBase()); } catch { u = new URL('http://127.0.0.1:47200/command'); }     // app is OPTIONAL on AD 1.9.84+ (inferred) - omit it for top-level verbs like notify_user, keep it     // when a caller passes one (e.g. desktop_* recording delegation).     const payload = { command, args: args || {} };     if (app) payload.app = app;+    // CROSS-AD routing: `target` (a peer clientName from `targets`, or "all") routes this call to a PEER+    // AD via the relay - so a bridge running on a VM reaches the user on their LAPTOP. Omitted = local AD.+    if (target) payload.target = target;     const data = Buffer.from(JSON.stringify(payload));     // X-Adom-Bridge-Token is ATTRIBUTION only (AD 1.9.84) - AD injects ADOM_BRIDGE_TOKEN into our spawn     // env; sending it badges our outbound calls as "bridge" in the Activity Log. It is NOT an approval@@ -491,8 +494,28 @@ function _downloadChrome(url, destPath, redirects = 0) { }  // notify helpers via the AD direct-API (app-less on AD 1.9.84). Best-effort: null on any failure.-const _notify = (args) => adCommand(null, 'notify_user', args);-const _notifyResp = (id) => adCommand(null, 'notify_response', { id });+// WHERE the toast lands: a bridge on a VM must reach the user on their LAPTOP, not fire on the VM's own+// (unwatched) desktop. So if the relay shows any PEER desktop, we notify `target:"all"` (the SDK cross-AD+// pattern) - the user sees it wherever they're attended. No peers (we're on the user's own machine) =+// local toast. Cached ~30s so we don't hit `targets` on every poll. (Users keep an RDP session open to+// their VMs, so a laptop toast that says "approve the UAC on <host>" is actionable immediately.)+let _ntCache = { at: 0, val: null };+async function _notifyTarget() {+  const now = Date.now();+  if (now - _ntCache.at < 30000) return _ntCache.val;+  let target = null;+  try {+    const r = await adCommand(null, 'targets', {});+    const list = (r && r.body && (r.body.targets || (r.body.data && r.body.data.targets) || [])) || [];+    let self = ''; try { self = os.hostname().toLowerCase(); } catch {}+    const peers = list.filter((t) => String((t && (t.name || t.hostname)) || '').toLowerCase() !== self);+    if (peers.length) target = 'all';+  } catch {}+  _ntCache = { at: now, val: target };+  return target;+}+const _notify = async (args) => adCommand(null, 'notify_user', args, await _notifyTarget());+const _notifyResp = async (id) => adCommand(null, 'notify_response', { id }, await _notifyTarget()); async function _adCanNotify() {   const r = await adCommand(null, 'notify_user', { id: '_probe_', title: '', body: '', level: 'info', _probe: true }).catch(() => null);   // if AD accepted (any 2xx-ish body) treat notify as available; unknown-verb → unavailable@@ -540,8 +563,9 @@ async function _elevatedChromeWithNotify(msiPath) {   // Fire ONE sticky "Approve now" toast, then poll QUIETLY for the tap (or chrome appearing) for the   // whole window. Do NOT re-fire on a timer - that stacked toasts up and stormed the user. Re-notify   // ONLY after an actual tap whose UAC didn't complete, so they can retry. scenario:reminder keeps the-  // single toast sticky. (Note: also no peer pings from here - we only ever notify the LOCAL desktop-  // where the UAC actually is, so we never spam the user's other machines.)+  // single toast sticky. The toast is routed by _notify to the user's desktop even when this bridge runs+  // on a VM (the UAC is on THIS host, but the toast must reach the user's LAPTOP) - the body names ${host}+  // so they know where to approve (they keep an RDP session open to the VM).   const fireToast = (again) => _notify({ id, level: 'warning', scenario: 'reminder', buttons: [{ label: 'Approve now' }],     title: again ? 'Chrome still needs approval' : 'Chrome needs your approval',     body: `Tap 'Approve now', then click YES on the Windows security prompt to install Google Chrome on ${host}.` });
dev-skills/developing-the-bridge/SKILL.md+19
@@ -61,6 +61,25 @@ a job id + `statusVerb`, don't block. Declare per-verb budgets >60s in `bridge.j discovery file; `GET 127.0.0.1:<port>/status` → led/summary/tooltip + `version`. End-to-end: `nbrowser_readiness` through AD (`--target <laptop>`), then open bg window → screenshot → close. +**Testing on John's VMs (winvm / ADOMBASELINE):** John ALWAYS keeps an RDP session open to his VMs —+**assume it; don't pause to confirm his RDP view before driving a VM over the `--target <vm>` AD path.**+Full fresh-VM onboarding = install bridge (`bridge_install {manifestUrl}`), deploy `extension/`+`host/`,+write `host/host-manifest.json` (path→`native-host.bat`, `allowed_origins`→`chrome-extension://<keyed-id>/`),+register the native host in HKCU (`…\Microsoft\Edge\NativeMessagingHosts` / `…\Google\Chrome\…`), then load+the extension (Edge honors `--load-extension`; Chrome 150 is flaky with it - GUI Load-unpacked is the+fallback). The keyed ext id (`nncjgceobmlenfliamnibncjojpedbcc`) is stable, so the host `allowed_origins`+matches regardless of load method.++## Reaching the user: NEVER notify only the local desktop+The bridge can run on a **VM** while the user is on their **laptop** - a `notify_user` that fires on the+LOCAL AD then lands on an unwatched VM desktop and the user never sees it (real bug: Chrome-install UAC+toast fired on the VM, John on his laptop saw nothing). **Route user notifications cross-AD:** query+`targets` via the direct-API; if any PEER desktop exists, send `notify_user`/`notify_response` with+`target:"all"` (top-level field on the `/command` payload) so it reaches the user wherever they're+attended; no peers (bridge on the user's own machine) = a local toast. `adCommand(app,cmd,args,target)`+adds `payload.target`. The toast BODY names the host (`…on ${host}`) since the action (e.g. a UAC) is on+the bridge's host but the alert is on the user's laptop - they keep an RDP session open to approve it.+ ## The boundary with AD (don't file bridge bugs as AD bugs) AD owns: spawn/reap (`refresh_bridges` force-restarts a running bridge as of v1.9.76; `restart_bridge` is the explicit primitive), stable port, supervisor, cache install + auto-update, status
native-browser-bridge-manifest.json+4−4
@@ -1,8 +1,8 @@ {   "manifest_version": 1,-  "version": "0.0.36",-  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.0.36/native-browser-bridge-v0.0.36.bin",-  "sha256": "df7c7006b6de9efedbd4409df3767306d2c971b3717515e9dfc999c44005946e",-  "size": 43378,+  "version": "0.0.37",+  "url": "https://wiki.adom.inc/download/adom/adom-browser-extension/0.0.37/native-browser-bridge-v0.0.37.bin",+  "sha256": "b1da66ff2ec964804756ea2c43c98ae72bea3392d7d836af467089aae3325582",+  "size": 43980,   "released_at": "2026-07-06T00:00:00Z" }\ No newline at end of file