aboutsummaryrefslogtreecommitdiffstats
path: root/tools
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2026-09-10 23:15:15 -0700
committerAdam Soltys <[email protected]>2026-09-10 23:15:15 -0700
commit25c8f34c98f997371616c12f83687561117ea3b5 (patch)
treeb1882b850b50a5eba1ad04f099b0251b288e7727 /tools
parent7539975beed48ed7aa9c35495d34f5b8b547cc93 (diff)
downloadcoinosv3-25c8f34c98f997371616c12f83687561117ea3b5.tar.gz
coinosv3-25c8f34c98f997371616c12f83687561117ea3b5.zip
The thumbnail fetches the bytes instead of trusting a crossOrigin <img>.
Pinguimdarelva's avatar still flashed, and the reason was one I'd have kept guessing at: from the live browser, `new Image()` with crossOrigin set FAILS for that picture, while `fetch(url, {mode:'cors', cache:'reload'})` for the very same URL returns 200. The host allows us perfectly well. The picture sits behind a 302, and the avatar is painted as an ordinary background-image — so the bytes are already in the HTTP cache under the redirect's target as a no-CORS entry, and the crossOrigin <img> reuses that entry and fails the CORS check. The image loads, the CORS copy "errors", and the face gets written off. So the thumbnailer fetches the bytes itself and draws from a blob: no crossOrigin element, no cache collision, and an untainted canvas without depending on the element's CORS bookkeeping. With no taint left to detect, failures no longer split into hard and soft — each one just backs off, from six hours up to a fortnight, so a host that truly won't have us is asked twice a month instead of every boot. The test now serves a picture behind a redirect and loads it plainly before thumbnailing it, which is the shape that broke; it fails on the old code. Co-Authored-By: Claude Opus 5 (1M context) <[email protected]> Claude-Session: https://claude.ai/code/session_019mrt228Upn9nNCwGixzSLx
Diffstat (limited to 'tools')
-rw-r--r--tools/avatar-thumb-test.js25
1 files changed, 25 insertions, 0 deletions
diff --git a/tools/avatar-thumb-test.js b/tools/avatar-thumb-test.js
index 73277bc..b73d4d3 100644
--- a/tools/avatar-thumb-test.js
+++ b/tools/avatar-thumb-test.js
@@ -36,6 +36,11 @@ const server = Bun.serve({
if (await f.exists()) return new Response(await f.arrayBuffer(), { headers: { 'content-type': 'image/webp' } });
return new Response('no', { status: 404 });
}
+ // the shape that broke a real avatar: the picture is behind a redirect,
+ // so the bytes land in the HTTP cache under the FINAL url when the plain
+ // background-image loads them — and a crossOrigin <img> for the same
+ // picture then reuses that no-CORS entry and fails the CORS check
+ if (u.pathname === '/redir.png') return new Response(null, { status: 302, headers: { location: '/big.png', 'access-control-allow-origin': '*' } });
if (u.pathname === '/big.png') {
originalHits++;
const bytes = await Bun.file('/tmp/claude-1000/-home-adam-coinosv3/61dea255-d26d-411d-be8b-92dfb0a75887/scratchpad/big.png').arrayBuffer();
@@ -152,6 +157,26 @@ try {
return i ? i.getAttribute('src') : '';
});
check('an uncached face is drawn straight away, not left blank', /^punks-sm\//.test(strangerSrc), strangerSrc || 'blank circle');
+
+ // A picture behind a redirect, loaded plainly first (as the avatar always
+ // is) and only then thumbnailed.
+ await page.evaluate((pkHex) => {
+ for (const k of Object.keys(localStorage).filter((x) => /^btc-wallet-cache:[0-9a-f]+$/.test(x)))
+ localStorage.setItem(k + ':profiles', JSON.stringify({
+ [pkHex]: { name: 'Redirect Test', picture: 'http://localhost:5235/redir.png', t: Date.now() },
+ }));
+ }, pk);
+ await page.reload({ waitUntil: 'domcontentloaded' });
+ await waitText('receive', 20000);
+ await sleep(3000);
+ const redirThumb = await page.evaluate(() => {
+ const rows = Object.keys(localStorage).filter((k) => /^btc-wallet-cache:[0-9a-f]+:profiles$/.test(k))
+ .flatMap((k) => Object.values(JSON.parse(localStorage.getItem(k) || '{}')));
+ const r = rows.find((x) => x && /redir/.test(x.picture || '')) || {};
+ return { thumb: (r.thumb || '').length, fail: !!r.thumbFail };
+ });
+ check('a picture behind a redirect still gets a thumbnail', redirThumb.thumb > 0 && !redirThumb.fail,
+ redirThumb.thumb ? redirThumb.thumb + ' chars' : 'failed');
} finally { await browser.close(); server.stop(true); }
console.log(ok ? '\n✅ faces come back instantly' : '\n❌ failed');
process.exit(ok ? 0 : 1);