master
John Lauer Every file the Rust build embeds from outside rust/ (pup-bridge#118): src/extension-nbe/**, src/extension-identity/iden… c7a8f33 1mo ago
// Adom Identity: the toolbar button IS the user. Chrome lets an extension set its action icon at any time
// (chrome.action.setIcon with imageData), so the button shows the adom-you avatar, drawn onto the same teal
// tile as its siblings, and refreshes on a timer. Before this the button was a static glyph and the avatar
// appeared only inside the popup (John, 2026-09-13).
const CANDIDATE_PORTS = [8851, 64230];
async function findPort() {
  let stored = null; try { stored = (await chrome.storage.local.get('pupPort')).pupPort; } catch (e) {}
  for (const port of [...new Set([stored, ...CANDIDATE_PORTS].filter(Boolean))]) {
    try {
      const r = await fetch(`http://127.0.0.1:${port}/health`, { signal: AbortSignal.timeout(1200) });
      const j = await r.json();
      if (j && j.bridge === 'puppeteer') { try { chrome.storage.local.set({ pupPort: port }); } catch (e) {} return port; }
    } catch (e) {}
  }
  return null;
}
function tile(ctx, size) {
  const r = Math.round(size * 22 / 128);
  ctx.fillStyle = '#00b8b1';
  ctx.beginPath(); ctx.roundRect(0, 0, size, size, r); ctx.fill();
}
async function paintAvatar() {
  const port = await findPort(); if (!port) return false;
  let bmp;
  try {
    const r = await fetch(`http://127.0.0.1:${port}/identity/avatar`, { cache: 'no-store', signal: AbortSignal.timeout(6000) });
    if (!r.ok) return false;
    bmp = await createImageBitmap(await r.blob());
  } catch (e) { return false; }
  const imageData = {};
  for (const size of [16, 32, 48, 128]) {
    const cv = new OffscreenCanvas(size, size); const cx = cv.getContext('2d');
    tile(cx, size);
    // the avatar, circle-cropped, inset so a ring of tile shows: the family look, with the person in it
    const inset = Math.max(1, Math.round(size * 10 / 128)); const d = size - inset * 2;
    cx.save(); cx.beginPath(); cx.arc(size / 2, size / 2, d / 2, 0, Math.PI * 2); cx.clip();
    const sc = Math.max(d / bmp.width, d / bmp.height);
    cx.drawImage(bmp, (size - bmp.width * sc) / 2, (size - bmp.height * sc) / 2, bmp.width * sc, bmp.height * sc);
    cx.restore();
    imageData[size] = cx.getImageData(0, 0, size, size);
  }
  try { await chrome.action.setIcon({ imageData }); await chrome.action.setTitle({ title: 'Surfing as adom-you (your avatar: this window is signed in as you)' }); } catch (e) { return false; }
  return true;
}
let _painted = false;
async function tick() { if (!_painted) _painted = await paintAvatar(); else await paintAvatar(); }
chrome.alarms.create('identity-avatar', { periodInMinutes: 10 });
chrome.alarms.onAlarm.addListener((a) => { if (a.name === 'identity-avatar') tick(); });
// until the first success (pup may still be starting), retry every 30 s
const retry = setInterval(async () => { if (_painted) { clearInterval(retry); return; } await tick(); }, 30000);
tick();