← Commit history

Brand a pup profile with the user's Adom avatar so the Chrome toolbar profile pill shows their real photo instead of a generic circle (John's request). Chrome only shows a custom photo for a signed-in Google account (the gaia picture), and pup suppresses sign-in — but the gaia picture is ultimately just a local PNG named in the profile's Local State info_cache, so pup seeds that file plus the info_cache fields (gaia_picture_file_name, use_gaia_picture, name) itself, no account needed. New browser_brand_profile verb {profile?, avatarUrl|avatarPath, displayName} downloads the image, seeds the profile, and persists the branding to pup-profile-branding.json; prepareCleanProfile re-applies it on EVERY launch so Chrome's own Local State rewrites can't erase it. Chrome reads branding only at launch, so a live profile must relaunch to show it (the verb reports relaunchNeeded).

John Lauer ·73596f8350 ·3mo ago ·parent b7bbacf
4 files changed +105−6
adom-bridge-puppeteer-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "puppeteer",-  "version": "1.9.125",-  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.125/adom-bridge-puppeteer-v1.9.125.zip",-  "sha256": "45125f30759fe5c30985e76413008eddbffa8f1acedcb35838cf46f86824423a",-  "size": 392491,+  "version": "1.9.126",+  "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.126/adom-bridge-puppeteer-v1.9.126.zip",+  "sha256": "22012725be62dc644b1cf771e36e484f803e7ac613058e28f53bf3c4cdb1f0a5",+  "size": 394012,   "verbPrefixes": [     "browser_",     "desktop_recorder_",
src/BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.125+1.9.126
src/bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "puppeteer",   "displayName": "Puppeteer (Chrome for Testing + Native)",-  "version": "1.9.125",+  "version": "1.9.126",   "description": "Drives a browser ALREADY on the machine \u2014 installed Chrome, else Microsoft Edge (every Windows PC ships with Edge) \u2014 launched with a FRESH isolated profile, so the common case has NO ~150 MB Chrome-for-Testing download and it never touches the user's real tabs/logins. Binds its local server to LOOPBACK (127.0.0.1) only, so no Windows Firewall 'allow Node through the firewall' prompt ever pops. Chrome for Testing is fetched only as a LAST RESORT if the machine has no Chromium browser at all. SPAWN-VERIFIED fallback: pup tries each detected browser (Chrome \u2192 Edge \u2192 CfT) and actually launches it, falling through to the next if one won't start (and handling browsers like Edge that relaunch onto another PID), so a corrupt/partial install never hard-fails an open. The browser that launches is cached as the default (persisted across restarts) so it isn't re-probed each time. On spawn the bridge SELF-WARMS: detects browsers + disk, and only if the box has no Chrome/Edge and no cached CfT does it background-fetch CfT so the first open is instant. browser_use pins a specific browser (chrome | edge | cft | auto); {browser:'chrome',install:true} downloads & installs REAL Google Chrome (user-scoped, no admin) on an Edge-only box. browser_readiness (READ-ONLY) reports which browser will be used + the full candidate list + the default + free disk. Every failure returns a specific errorCode + _hint (chrome_install_no_disk, chrome_download_network, chrome_install_permission, chrome_stable_not_detected, \u2026). Launches with FULL browser permissions by default for full debug (strictPermissions:true opts out). Headful automation: open/close windows+tabs, navigate, screenshot, eval JS, record. pup is a GENERIC, non-profile browser only \u2014 every session is a fresh isolated Chrome/Edge process, never the user's real signed-in browser; profile defaults to sessionId. To drive the user's REAL Chrome/Edge (their profile, cookies, SSO), use the Adom browser EXTENSION's nbrowser_* verbs \u2014 a separate bridge, not pup's job. macOS (v1.9.74+): full parity with mac-native mechanics \u2014 background-by-default is the app-level hide (windows stay drivable/screenshottable while hidden), raise/lower map to unhide+activate/hide, browser_alert_window posts a native toast whose click focuses the window, browser_use {browser:'chrome',install:true} installs Chrome from the official dmg, and taskbar identity/badging remains Windows-only.",   "homepage": "https://wiki.adom.inc/adom/adom-desktop",   "author": "Adom Inc.",
src/server.js+99
@@ -1648,6 +1648,66 @@ async function killBrowserProcess(browser, profileName) { // We only run this when LAUNCHING (not when reconnecting to a running // Chrome via DevToolsActivePort). Reconnect path means Chrome is already // alive and we shouldn't touch its profile files.+// v1.9.126 (John) — brand a pup profile with the user's ADOM AVATAR, so the Chrome+// profile pill in the toolbar shows their real photo instead of a generic circle.+// Chrome normally only shows a custom photo for a SIGNED-IN Google account (the "gaia+// picture"), and pup suppresses sign-in — but the gaia picture is ultimately just a local+// PNG named in the profile's Local State. So we seed that file + the info_cache fields+// ourselves, no account needed. Chrome reads profile branding at LAUNCH, so this must be+// applied while Chrome isn't running the profile; prepareCleanProfile re-applies it on every+// launch (below) so Chrome's own rewrites can't erase it.+const PROFILE_BRANDING_PATH = path.join(ADOM_STATE_DIR, 'pup-profile-branding.json');+function readProfileBranding() {+  try { return JSON.parse(fs.readFileSync(PROFILE_BRANDING_PATH, 'utf8')) || {}; } catch (e) { return {}; }+}+function saveProfileBranding(profileName, entry) {+  try {+    const all = readProfileBranding();+    all[profileName] = { ...(all[profileName] || {}), ...entry };+    fs.writeFileSync(PROFILE_BRANDING_PATH, JSON.stringify(all, null, 2));+    return true;+  } catch (e) { console.log(`saveProfileBranding warning: ${e.message}`); return false; }+}+function brandProfileAvatar(userDataDir, avatarSrcPath, displayName) {+  try {+    if (!userDataDir || !avatarSrcPath || !fs.existsSync(avatarSrcPath)) return false;+    const defaultDir = path.join(userDataDir, 'Default');+    fs.mkdirSync(defaultDir, { recursive: true });+    const picName = 'Google Profile Picture.png';   // the filename Chrome expects for a gaia picture+    fs.copyFileSync(avatarSrcPath, path.join(defaultDir, picName));+    const name = displayName || 'Adom';+    // Default/Preferences+    const prefsPath = path.join(defaultDir, 'Preferences');+    let prefs = {}; try { prefs = JSON.parse(fs.readFileSync(prefsPath, 'utf8')); } catch (e) {}+    prefs.profile = prefs.profile || {};+    prefs.profile.name = name;+    prefs.profile.gaia_picture_file_name = picName;+    prefs.profile.using_gaia_picture = true;+    fs.writeFileSync(prefsPath, JSON.stringify(prefs));+    // Local State (user-data-dir root) info_cache.Default — this drives the toolbar pill.+    const lsPath = path.join(userDataDir, 'Local State');+    let ls = {}; try { ls = JSON.parse(fs.readFileSync(lsPath, 'utf8')); } catch (e) {}+    ls.profile = ls.profile || {};+    ls.profile.info_cache = ls.profile.info_cache || {};+    const cur = ls.profile.info_cache.Default || {};+    ls.profile.info_cache.Default = {+      ...cur,+      name,+      gaia_name: name,+      gaia_given_name: name.split(' ')[0],+      gaia_id: cur.gaia_id || 'adom-pup',+      gaia_picture_file_name: picName,+      use_gaia_picture: true,+      is_using_default_name: false,+      is_using_default_avatar: false,+      background_apps: false,+    };+    ls.profile.last_used = 'Default';+    if (!Array.isArray(ls.profile.profiles_order) || !ls.profile.profiles_order.length) ls.profile.profiles_order = ['Default'];+    fs.writeFileSync(lsPath, JSON.stringify(ls));+    return true;+  } catch (e) { console.log(`brandProfileAvatar("${userDataDir}") warning: ${e.message}`); return false; }+} function prepareCleanProfile(userDataDir) {   if (!userDataDir) return;   try {@@ -1683,6 +1743,13 @@ function prepareCleanProfile(userDataDir) {       const p = path.join(defaultDir, name);       try { fs.rmSync(p, { recursive: true, force: true }); } catch {}     }+    // v1.9.126: re-apply avatar branding if this profile has any (Chrome rewrites Local State+    // on exit, so a one-time seed would be lost — re-seed at every launch).+    try {+      const pn = path.basename(userDataDir);+      const b = readProfileBranding()[pn];+      if (b && b.avatarFile) brandProfileAvatar(userDataDir, b.avatarFile, b.displayName);+    } catch (e) {}   } catch (e) {     // Profile cleanup is defense-in-depth — don't fail the launch if it errors     console.log(`prepareCleanProfile("${userDataDir}") warning: ${e.message}`);@@ -9061,6 +9128,38 @@ const server = http.createServer(async (req, res) => {           }           return;         }+        // v1.9.126 — brand a pup profile with the user's Adom avatar (or any image/displayName).+        case 'browser_brand_profile': {+          try {+            const profileName = args.profile || DEFAULT_PROFILE;+            const displayName = args.displayName || 'Adom';+            let avatarSrc = null;+            if (args.avatarUrl) {+              const resp = await fetch(args.avatarUrl);+              if (!resp || !resp.ok) throw new Error(`avatar fetch failed (${resp && resp.status})`);+              const buf = Buffer.from(await resp.arrayBuffer());+              const dir = path.join(ADOM_STATE_DIR, 'pup-avatars');+              fs.mkdirSync(dir, { recursive: true });+              avatarSrc = path.join(dir, profileName.replace(/[^a-zA-Z0-9_-]/g, '_') + '.png');+              fs.writeFileSync(avatarSrc, buf);+            } else if (args.avatarPath && fs.existsSync(args.avatarPath)) {+              avatarSrc = args.avatarPath;+            }+            if (!avatarSrc) { sendJSON(res, { success: false, error: 'avatarUrl (or a local avatarPath) is required' }); return; }+            // Persist so every future launch of this profile re-applies the brand.+            saveProfileBranding(profileName, { avatarFile: avatarSrc, displayName });+            const userDataDir = path.join(PROFILES_DIR, profileName);+            const applied = brandProfileAvatar(userDataDir, avatarSrc, displayName);+            const live = browsers.has(profileName);+            sendJSON(res, { success: true, output: JSON.stringify({ ok: true, profile: profileName, displayName, applied, relaunchNeeded: live }),+              _hint: live+                ? `Avatar seeded for profile "${profileName}", but it has a LIVE browser — Chrome reads profile branding only at LAUNCH, so close and reopen its window(s) for the new photo to appear in the toolbar pill.`+                : `Avatar seeded for profile "${profileName}" — it will show in the Chrome toolbar pill on the next window opened on this profile.` });+          } catch (e) {+            sendJSON(res, { success: false, error: e.message });+          }+          return;+        }         // v1.9.124 — fill (and optionally submit) an HTML login FORM in the BACKGROUND, from the         // vault or explicit creds, with NO Chrome autofill dropdown and NO foregrounding. This is         // the answer to Q4: pup owns the creds, so it fills the fields over CDP directly instead of