app
Pup - the AI's own browser
Public Made by Adomby adom
pup gives your AI a real browser on your desktop that it drives completely, in the background, signed in as you: windows that never jump in front of your work, one shared profile that learns your logins, and every window labeled on the taskbar with its AI thread's name.
← Commit history
Favicon acquisition rebuilt into a real multi-state resolver + per-tab state in the dashboard (John: 'favicons these days can be svg or png, dark mode or light mode, transparent... you seem to not be doing much logic there... create all of the states of how to get a favicon... and place that info with each tab's info row... then determine which favicon to retrieve to have it show the best/nicest in the windows taskbar, light vs dark vs transparent'). NEW resolveFaviconRich: (1) enumerates EVERY declared icon with its type/sizes/media/rel (link rel=icon/shortcut/apple-touch/mask-icon + synthetic /favicon.ico); (2) SCORES for a DARK taskbar - a media='(prefers-color-scheme: dark)' variant is the art a site designed to sit on dark so it wins (+100); a light-mode variant is usually dark ink that vanishes on the taskbar so it loses (-40); then SVG > sized-PNG > apple-touch > ICO for crispness; mask-icon deprioritised; (3) fetches best-first with the page's cookies (authed icons), falling back to bridge fetch then disk; (4) rasterizes via sharp and MEASURES transparency (stats.isOpaque) + luminance, and if the top pick is transparent AND dark-dominant (would vanish on the dark taskbar) it keeps scanning for a lighter sibling before settling. Full acquisition STATE recorded per resolution: format (SVG/PNG/ICO/JPEG), color-scheme mode (dark/light/default), transparent+darkDominant+luminance, kind (page-favicon / sibling-tab / favicon.ico / app-provided / generic-web / generic-pdf / none), how it was fetched, and candidate count. The dashboard now shows this as compact chips on EACH TAB's row (kind, format, x-mode, transparent/opaque) with a full tooltip, so it's visible per tab exactly how every favicon was obtained - including whether a generic web or generic PDF icon had to be synthesized because none truly existed. The active tab's live overlay state feeds its row; other tabs' state comes from the rich resolver's per-URL cache populated by the dashboard favicon fetch.
4 files changed
+179−18
adom-bridge-puppeteer-manifest.json+4−4@@ -1,10 +1,10 @@ { "manifest_version": 1, "name": "puppeteer",- "version": "1.9.237",- "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.237/adom-bridge-puppeteer-v1.9.237.zip",- "sha256": "281b4416874e57d3b295a2852f6304a5123cdd8ad512249c50451ba87ef63458",- "size": 460221,+ "version": "1.9.238",+ "url": "https://wiki.adom.inc/download/adom/adom-desktop-puppeteer-bridge/1.9.238/adom-bridge-puppeteer-v1.9.238.zip",+ "sha256": "07b6c95adc3b49c43aee9b1bfec87e7d00b036d22014030520977ce93728f13c",+ "size": 463951, "verbPrefixes": [ "browser_", "desktop_recorder_",
src/BRIDGE_VERSION+1−1@@ -1 +1 @@-1.9.237+1.9.238
src/bridge.json+1−1@@ -2,7 +2,7 @@ "manifest_version": 1, "name": "puppeteer", "displayName": "Puppeteer (Chrome for Testing + Native)",- "version": "1.9.237",+ "version": "1.9.238", "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+173−12@@ -3971,6 +3971,119 @@ function icoLargestPng(buf) { } catch (e) { return null; } } +// v1.9.238 (John: "favicons these days can be svg or png, dark mode or light mode, transparent... you+// seem to not be doing much logic there... determine which favicon to retrieve to have it show the+// best/nicest in the windows taskbar"). A proper resolver that (1) enumerates EVERY declared icon with+// its type/sizes/media, (2) picks the best for a DARK taskbar — a site's dark-mode variant is the art+// designed to sit on dark, so it wins; a light-mode variant is usually dark ink that vanishes on the+// taskbar, so it loses — then SVG > sized PNG > apple-touch > ICO for crispness, (3) fetches with the+// page's cookies (authed icons), (4) rasterizes and MEASURES transparency + luminance so a dark-on-dark+// icon can be detected and skipped for a lighter sibling. Returns the full acquisition STATE so the+// dashboard can show, per tab, exactly how the icon was obtained.+const _tabFavMeta = new Map(); // page-url -> { meta, at }+async function enumerateFaviconCandidates(page) {+ try {+ return await withTimeout(page.evaluate(() => {+ const q = 'link[rel~="icon"], link[rel="shortcut icon"], link[rel="apple-touch-icon"], link[rel="mask-icon"]';+ const links = Array.from(document.querySelectorAll(q));+ const out = links.map(l => ({+ href: l.href, type: (l.type || '').toLowerCase(), sizes: (l.getAttribute('sizes') || '').toLowerCase(),+ media: (l.media || l.getAttribute('media') || '').toLowerCase(), rel: (l.getAttribute('rel') || '').toLowerCase(),+ })).filter(c => c.href && c.href !== 'data:,' && !/^data:,?$/.test(c.href));+ try { out.push({ href: new URL('/favicon.ico', location.origin).href, type: '', sizes: '', media: '', rel: 'icon', synthetic: true }); } catch (e) {}+ return out;+ }), 2500, 'fav-enum').catch(() => []);+ } catch (e) { return []; }+}+function scoreFaviconCandidate(c) {+ let s = 0;+ const m = c.media || '';+ if (/prefers-color-scheme:\s*dark/.test(m)) s += 100; // taskbar is dark → its dark-mode art+ else if (/prefers-color-scheme:\s*light/.test(m)) s -= 40; // light-mode art = dark ink, dies on dark+ const base = (c.href || '').split('?')[0];+ const ext = (base.split('.').pop() || '').toLowerCase();+ const isSvg = c.type.includes('svg') || ext === 'svg';+ const isPng = c.type.includes('png') || ext === 'png';+ if (isSvg) s += 50; // crispest at every size+ else if (c.rel.includes('apple-touch')) s += 35; // usually a large clean PNG+ else if (isPng) { s += 30; const big = parseInt((c.sizes.match(/(\d+)x/) || [])[1] || '0', 10); s += Math.min(big, 256) / 16; }+ else if (ext === 'ico' || c.type.includes('icon')) s += 10;+ if (c.rel.includes('mask-icon')) s -= 60; // monochrome Safari pin, not a real favicon+ if (c.synthetic) s -= 5;+ return s;+}+function classifyMode(media) {+ if (/prefers-color-scheme:\s*dark/.test(media || '')) return 'dark';+ if (/prefers-color-scheme:\s*light/.test(media || '')) return 'light';+ return media ? 'media' : 'default';+}+async function fetchIconBytes(page, href) {+ // in-page (cookies) → bridge fetch → local file, mirroring fetchPageFavicon's ladder.+ if (/^data:/i.test(href)) {+ const m = href.match(/^data:([^;,]*)(;base64)?,(.*)$/);+ if (m && m[3]) { const b64 = m[2] ? m[3] : Buffer.from(decodeURIComponent(m[3])).toString('base64'); if (b64) return { b64, mime: m[1] || 'image/png', via: 'inline' }; }+ return null;+ }+ if (/^file:/i.test(href)) {+ try { const fp = decodeURIComponent(new URL(href).pathname).replace(/^\/([A-Za-z]:)/, '$1'); const buf = fs.readFileSync(fp); if (buf && buf.length) return { b64: buf.toString('base64'), mime: '', via: 'disk' }; } catch (e) {}+ return null;+ }+ try {+ const inPage = await withTimeout(page.evaluate(async (u) => {+ try { const r = await fetch(u, { credentials: 'include', cache: 'force-cache' }); if (!r.ok) return null; const b = await r.blob(); if (!b.size) return null;+ const b64 = await new Promise((res, rej) => { const fr = new FileReader(); fr.onload = () => res(String(fr.result).split(',')[1]); fr.onerror = rej; fr.readAsDataURL(b); });+ return b64 ? { b64, mime: b.type || '' } : null; } catch (e) { return null; }+ }, href), 4000, 'fav-inpage').catch(() => null);+ if (inPage && inPage.b64) return { b64: inPage.b64, mime: inPage.mime || '', via: 'in-page (cookies)' };+ } catch (e) {}+ try {+ const r = await fetch(href, { redirect: 'follow', signal: AbortSignal.timeout(4000) });+ if (r && r.ok) { const buf = Buffer.from(await r.arrayBuffer()); if (buf.length) return { b64: buf.toString('base64'), mime: r.headers.get('content-type') || '', via: 'bridge fetch' }; }+ } catch (e) {}+ return null;+}+async function analyzeIconBytes(b64, mime, href) {+ // Rasterize to PNG and measure transparency + luminance. Returns null if truly undecodable.+ const buf = Buffer.from(b64, 'base64');+ const ext = ((href || '').split('?')[0].split('.').pop() || '').toLowerCase();+ let format = 'png', input = buf;+ if (mime.includes('svg') || ext === 'svg') format = 'svg';+ else if (mime.includes('png') || ext === 'png') format = 'png';+ else if (mime.includes('jpeg') || ext === 'jpg' || ext === 'jpeg') format = 'jpeg';+ else if (mime.includes('icon') || mime.includes('ico') || ext === 'ico') { format = 'ico'; const inner = icoLargestPng(buf); if (inner) input = inner; }+ try {+ const img = sharpMod(input);+ const stats = await img.stats();+ const ch = stats.channels || [];+ const lum = ch.length >= 3 ? (0.299 * ch[0].mean + 0.587 * ch[1].mean + 0.114 * ch[2].mean) : (ch[0] ? ch[0].mean : 128);+ const pngBuf = await img.png().toBuffer();+ return { pngBuf, b64: pngBuf.toString('base64'), format, transparent: !stats.isOpaque, darkDominant: lum < 80, lum: Math.round(lum) };+ } catch (e) { return { pngBuf: null, b64: null, format, transparent: false, darkDominant: false, lum: null, unreadable: true }; }+}+async function resolveFaviconRich(page) {+ const cands = await enumerateFaviconCandidates(page);+ if (!cands.length) return null;+ const ranked = cands.map(c => ({ ...c, score: scoreFaviconCandidate(c) })).sort((a, b) => b.score - a.score);+ let firstReadable = null;+ for (const c of ranked) {+ const bytes = await fetchIconBytes(page, c.href).catch(() => null);+ if (!bytes || !bytes.b64) continue;+ const an = await analyzeIconBytes(bytes.b64, bytes.mime || c.type, c.href);+ if (!an || an.unreadable || !an.b64) continue;+ const meta = {+ b64: an.b64, mime: 'image/png', href: c.href, format: an.format, mode: classifyMode(c.media),+ transparent: an.transparent, darkDominant: an.darkDominant, luminance: an.lum, sizes: c.sizes || null,+ via: bytes.via, kind: c.synthetic ? 'favicon.ico' : 'page-favicon', candidates: cands.length,+ note: an.darkDominant && an.transparent ? 'dark + transparent (risk of vanishing on the dark taskbar)' : null,+ };+ if (!firstReadable) firstReadable = meta;+ // A dark-on-dark transparent icon vanishes on the dark taskbar — keep scanning for a lighter sibling+ // (typically the dark-mode media variant). If none is better, we still return the first readable one.+ if (an.darkDominant && an.transparent) continue;+ return meta;+ }+ return firstReadable; // best-effort: readable but possibly dark-on-dark+} async function applyAppOverlay(session, sessionId) { // v1.9.191 (John): overlays are the GENTLE branding path and are ALLOWED in minimal-touch. // ITaskbarList3::SetOverlayIcon paints a badge onto the EXISTING taskbar button; it never@@ -3987,8 +4100,13 @@ async function applyAppOverlay(session, sessionId) { if (activeTab && activeTab.tabId && session.activeTabId !== activeTab.tabId) { session.activeTabId = activeTab.tabId; // keep bookkeeping honest for everything downstream }- let src = session._appIconB64 ? { b64: session._appIconB64, mime: session._appIconMime || 'image/png' } : null;- if (!src) src = await fetchPageFavicon(activePage);+ let src = session._appIconB64 ? { b64: session._appIconB64, mime: session._appIconMime || 'image/png', href: 'app-provided' } : null;+ if (src) session._overlayFav = { kind: 'app-provided', format: 'png', mode: 'default', transparent: null, via: 'browser_open_window appIconB64', href: 'app-provided' };+ if (!src) {+ // v1.9.238: rich, dark-taskbar-aware resolution + full state capture.+ const rich = await resolveFaviconRich(activePage).catch(() => null);+ if (rich && rich.b64) { src = { b64: rich.b64, mime: rich.mime, href: rich.href }; session._overlayFav = rich; }+ } if (!src || !src.b64) { // v1.9.43: RETRY once shortly after. The old code gave up silently on the first miss, so a // favicon that simply had not loaded yet meant NO overlay for the life of the window. That@@ -4011,10 +4129,11 @@ async function applyAppOverlay(session, sessionId) { try { for (const t of (session.tabs || [])) { if (!t || !t.page || t === activeTab || t._crashed) continue;- const sib = await fetchPageFavicon(t.page).catch(() => null);+ const sib = await resolveFaviconRich(t.page).catch(() => null); if (sib && sib.b64) { console.log(`[overlay] "${sessionId}" using sibling tab ${t.tabId}'s favicon (active tab has none)`);- src = sib;+ src = { b64: sib.b64, mime: sib.mime, href: sib.href };+ session._overlayFav = { ...sib, kind: 'sibling-tab-favicon' }; break; } }@@ -4050,6 +4169,7 @@ async function applyAppOverlay(session, sessionId) { + '<path d="M11.4 24 H36.6" stroke="#5f6368" stroke-width="2.6"/>' + '<path d="M13.6 16.4 H34.4 M13.6 31.6 H34.4" stroke="#5f6368" stroke-width="2"/></svg>'; src = { b64: Buffer.from(_svg2).toString('base64'), mime: 'image/svg+xml', href: _isPdf2 ? 'generic:pdf' : 'generic:page' };+ session._overlayFav = { kind: _isPdf2 ? 'generic-pdf' : 'generic-web', format: 'svg', mode: 'default', transparent: false, via: 'generated (no favicon existed anywhere in this window)', href: src.href, candidates: 0 }; console.log(`[overlay] "${sessionId}" no favicon in this session — generic ${_isPdf2 ? 'PDF' : 'webpage'} icon + Adom mark`); console.log(`[overlay] "${sessionId}" no favicon anywhere in this window — using the generic glyph so the badge is never bare`); } catch (e) {@@ -4143,6 +4263,7 @@ async function applyAppOverlay(session, sessionId) { + '<path d="M11.4 24 H36.6" stroke="#5f6368" stroke-width="2.6"/>' + '<path d="M13.6 16.4 H34.4 M13.6 31.6 H34.4" stroke="#5f6368" stroke-width="2"/></svg>'; src = { b64: Buffer.from(svg).toString('base64'), mime: 'image/svg+xml', href: isPdf ? 'generic:pdf' : 'generic:page' };+ session._overlayFav = { kind: isPdf ? 'generic-pdf' : 'generic-web', format: 'svg', mode: 'default', transparent: false, via: 'generated (favicon fetch failed / none declared)', href: src.href, candidates: 0 }; console.log(`[overlay] "${sessionId}" no favicon — using the generic ${isPdf ? 'PDF' : 'webpage'} icon (Chrome-style) + Adom mark`); } catch (e) { console.log(`[overlay] "${sessionId}" generic icon build FAILED: ${e.message}`);@@ -7780,12 +7901,19 @@ async function dashboardSnapshot() { let u = null, ttl = null; try { u = t.page && t.page.url(); } catch (e) {} try { ttl = t._lastTitle || null; } catch (e) {}+ // v1.9.238: attach how THIS tab's favicon was obtained (format/mode/transparent/kind). Comes+ // from the rich resolver's cache, keyed by the tab's URL; the active tab also has the live+ // overlay state. Shown in each tab's dashboard row so the favicon logic is fully transparent.+ let fav = null;+ try { const fm = u && _tabFavMeta.get(u); if (fm) fav = fm.meta; } catch (e) {}+ if (t.tabId === sess.activeTabId && sess._overlayFav) { const { b64, ...m } = sess._overlayFav; fav = m; } return { tabId: t.tabId, url: u, title: ttl, active: t.tabId === sess.activeTabId, crashed: !!t._crashed, crashReason: t._crashReason || null, opener: t.opener || null,+ fav, }; }); const hf = sess._highFpsUntil && sess._highFpsUntil > now;@@ -7934,6 +8062,15 @@ main{padding:14px 18px} .tog.leaf{visibility:hidden} .ico{width:22px;height:22px;flex:0 0 22px;object-fit:contain;border-radius:5px;background:#0f1720} .ico.sm{width:16px;height:16px;flex:0 0 16px}+.fttray{display:inline-flex;gap:3px;align-items:center;flex-wrap:wrap}+.ft{font-size:9px;line-height:1;padding:2px 5px;border-radius:4px;background:#161f29;color:#9fb2c2;border:1px solid #24303c;white-space:nowrap}+.ft.dim{opacity:.55}+.ft.k-ok{background:#0f2a1c;color:#5fe0a0;border-color:#1c4a33}+.ft.k-gen{background:#2a230f;color:#e0c15f;border-color:#4a3d1c}+.ft.k-red{background:#2a0f14;color:#ff8a95;border-color:#4a1c24}+.ft.k-dark{background:#12202e;color:#7fc0ff;border-color:#1c3a52}+.ft.k-light{background:#26240f;color:#e8e07a;border-color:#48431c}+.ft.k-warn{background:#2a1a0f;color:#ffab6a;border-color:#4a2f1c} .led{display:inline-block;width:9px;height:9px;border-radius:50%;flex:0 0 9px;vertical-align:middle;box-shadow:0 0 0 1px rgba(0,0,0,.4) inset} .led.green{background:#22e07a;box-shadow:0 0 6px 1px rgba(34,224,122,.65)} .led.amber{background:#f0b429;box-shadow:0 0 6px 1px rgba(240,180,41,.5)}@@ -7987,6 +8124,20 @@ const ago=ms=>{if(ms==null)return'-';const s=Math.round(ms/1000);if(s<60)return const esc=t=>String(t==null?'':t).replace(/[&<>"]/g,c=>({'&':'&','<':'<','>':'>','"':'"'}[c])); const badge=sid=>'/dashboard/badge?session='+encodeURIComponent(sid)+'&t='+(SNAP?SNAP.ts:0); const fav=(sid,tid)=>'/dashboard/favicon?session='+encodeURIComponent(sid)+'&tab='+encodeURIComponent(tid);+// v1.9.238: per-tab favicon acquisition state as compact chips + a full tooltip.+function favTags(f){+ if(!f) return '<span class="ft dim" title="favicon not resolved yet">fav ?</span>';+ const K={'page-favicon':['favicon','k-ok'],'sibling-tab-favicon':['sibling','k-ok'],'favicon.ico':['favicon.ico','k-ok'],'app-provided':['app icon','k-ok'],'generic-web':['generic web','k-gen'],'generic-pdf':['generic pdf','k-gen'],'none':['none','k-red']};+ const kd=K[f.kind]||[f.kind||'?','k-gen'];+ const bits=[];+ bits.push('<span class="ft '+kd[1]+'">'+esc(kd[0])+'</span>');+ if(f.format) bits.push('<span class="ft">'+esc(String(f.format).toUpperCase())+'</span>');+ if(f.mode&&f.mode!=='default') bits.push('<span class="ft '+(f.mode==='dark'?'k-dark':f.mode==='light'?'k-light':'')+'">'+esc(f.mode)+'-mode</span>');+ if(f.transparent===true) bits.push('<span class="ft'+(f.darkDominant?' k-warn':'')+'">transparent'+(f.darkDominant?' · dark':'')+'</span>');+ else if(f.transparent===false) bits.push('<span class="ft">opaque</span>');+ const tip=['kind: '+(f.kind||'?'),'format: '+(f.format||'?'),'color-scheme: '+(f.mode||'?'),'transparent: '+(f.transparent===null?'n/a':f.transparent),f.luminance!=null?'luminance: '+f.luminance+(f.darkDominant?' (dark)':''):'',f.via?'fetched via: '+f.via:'',f.candidates!=null?'candidates seen: '+f.candidates:'',f.href&&!/^generic|^app-provided/.test(f.href)?'src: '+f.href:'',f.note?'⚠ '+f.note:''].filter(Boolean).join('\n');+ return '<span class="fttray" title="'+esc(tip)+'">'+bits.join('')+'</span>';+} function render(){ if(!SNAP)return; const d=SNAP;@@ -8060,6 +8211,7 @@ function render(){ +'<span class="nm dim'+(t.active?'"':'"')+'">'+esc(t.title||t.url||'-')+'</span>' +(t.active?'<span class="tag fg">active</span>':'') +(t.crashed?'<span class="tag warn">crashed</span>':'')+ +favTags(t.fav) +'<span class="acts"><button class="btn xs danger" data-act="close-tab" data-sid="'+esc(w.sessionId)+'" data-tid="'+esc(t.tabId)+'">close</button></span></div>' ).join('')+'</div></div></div>'; }).join('')+'</div></div>';@@ -8224,14 +8376,23 @@ const server = http.createServer(async (req, res) => { res.writeHead(200, { 'Content-Type': c.mime || 'image/png', 'Cache-Control': 'no-store' }); res.end(c.buf); return; }- const src = await Promise.race([- fetchPageFavicon(tab.page),- new Promise(r => setTimeout(() => r(null), 3000)),- ]);- if (!src || !src.b64) { res.writeHead(404); res.end(); return; }- const buf = Buffer.from(src.b64, 'base64');- if (key) { _tabFavCache.set(key, { buf, mime: src.mime || 'image/png' }); if (_tabFavCache.size > 200) _tabFavCache.delete(_tabFavCache.keys().next().value); }- res.writeHead(200, { 'Content-Type': src.mime || 'image/png', 'Cache-Control': 'no-store' });+ // v1.9.238: the rich resolver returns a rasterized PNG + the full acquisition STATE (format,+ // color-scheme mode, transparency, how it was fetched). Cache both — the meta feeds each tab's+ // dashboard row so John can see per tab exactly how its favicon was obtained.+ const rich = await Promise.race([+ resolveFaviconRich(tab.page),+ new Promise(r => setTimeout(() => r(null), 3500)),+ ]).catch(() => null);+ if (!rich || !rich.b64) {+ if (key) _tabFavMeta.set(key, { meta: { kind: 'none', note: 'no favicon resolved' }, at: Date.now() });+ res.writeHead(404); res.end(); return;+ }+ const buf = Buffer.from(rich.b64, 'base64');+ if (key) {+ _tabFavCache.set(key, { buf, mime: 'image/png' }); if (_tabFavCache.size > 200) _tabFavCache.delete(_tabFavCache.keys().next().value);+ const { b64, ...metaNoBytes } = rich; _tabFavMeta.set(key, { meta: metaNoBytes, at: Date.now() }); if (_tabFavMeta.size > 300) _tabFavMeta.delete(_tabFavMeta.keys().next().value);+ }+ res.writeHead(200, { 'Content-Type': 'image/png', 'Cache-Control': 'no-store' }); res.end(buf); return; } catch (e) { res.writeHead(404); res.end(); return; } }