From 0076c591df9432e2bb5b2281afc1fb4106500297 Mon Sep 17 00:00:00 2001 From: Adam Soltys Date: Wed, 30 Sep 2026 10:23:20 -0700 Subject: A picture or video on its way shows a bar with its percentage — shrinking, then uploading; the pencil reads "New post". MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ui.mediaProgress { stage, pct } (a render per whole percent) drawn as a labelled bar under the reply and post composers, instead of only the paperclip turning into a spinner. Upload progress comes from XMLHttpRequest upload events (fetch() has none) in uploadPublicMedia's new onProgress; injected fetchers (tests) keep the fetch path. upload-progress-test: 1.2 MB at 400 KB/s climbs 0→100 in steps. video-compress-test: the bar counts the shrink, then the upload. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01Du1qT1wbkYwYK6zkGH79Rp --- tools/upload-progress-test.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 tools/upload-progress-test.js (limited to 'tools/upload-progress-test.js') diff --git a/tools/upload-progress-test.js b/tools/upload-progress-test.js new file mode 100644 index 0000000..25bf91c --- /dev/null +++ b/tools/upload-progress-test.js @@ -0,0 +1,37 @@ +// uploadPublicMedia reports how much of the file has gone out (XMLHttpRequest +// upload events — fetch() can't), from 0 to 1, on a slow uplink. +// +// Run: bun tools/upload-progress-test.js +import puppeteer from 'puppeteer-core'; +let ok = true; +const check = (n, c, d = '') => { console.log(` ${c ? '✓' : '✗'} ${n}${d ? ' — ' + d : ''}`); if (!c) ok = false; }; +const js = await Bun.build({ entrypoints: ['src/media-upload.js'], target: 'browser' }).then((b) => b.outputs[0].text()); +let got = 0; +const server = Bun.serve({ port: 5289, async fetch(req) { + const cors = { 'access-control-allow-origin': '*', 'access-control-allow-methods': 'PUT, HEAD', 'access-control-allow-headers': '*', 'access-control-expose-headers': 'x-reason' }; + const p = new URL(req.url).pathname; + if (req.method === 'OPTIONS') return new Response(null, { status: 204, headers: cors }); + if (p === '/upload' && req.method === 'PUT') { got = (await req.arrayBuffer()).byteLength; return Response.json({ url: 'http://localhost:5289/' + 'a'.repeat(64) }, { status: 201, headers: cors }); } + if (p === '/m.js') return new Response(js, { headers: { 'content-type': 'text/javascript' } }); + return new Response('', { headers: { 'content-type': 'text/html' } }); +} }); +const browser = await puppeteer.launch({ executablePath: '/usr/bin/google-chrome', headless: 'new', args: ['--no-sandbox'] }); +const page = await browser.newPage(); +try { + await page.goto('http://localhost:5289/'); + await page.waitForFunction(() => !!window.m); + const cdp = await page.createCDPSession(); + await cdp.send('Network.emulateNetworkConditions', { offline: false, latency: 20, downloadThroughput: 5e6, uploadThroughput: 400e3 }); + const r = await page.evaluate(async () => { + const seen = []; + const file = new File([new Uint8Array(1_200_000)], 'v.mp4', { type: 'video/mp4' }); + const sign = async (e) => ({ ...e, id: '1'.repeat(64), pubkey: '2'.repeat(64), sig: '3'.repeat(128) }); + const url = await window.m.uploadPublicMedia(file, sign, { servers: ['http://localhost:5289'], onProgress: (p) => seen.push(Math.round(p * 100)) }); + return { url, seen }; + }); + check('the upload lands', r.url.startsWith('http://localhost:5289/') && got === 1_200_000, got + ' bytes'); + check('progress climbs from 0 to 100 as the bytes go', r.seen[0] === 0 && r.seen.at(-1) === 100 && r.seen.filter((p) => p > 0 && p < 100).length >= 3 + && r.seen.every((p, i) => !i || p >= r.seen[i - 1]), JSON.stringify(r.seen.slice(0, 15)) + (r.seen.length > 15 ? '…' : '')); +} finally { await browser.close(); server.stop(true); } +console.log(ok ? '\n✅ uploads report their progress' : '\n❌ failed'); +process.exit(ok ? 0 : 1); -- cgit v1.2.3