Less traffic: compressed td downloads, symbol table only when changed, no update retry loop

- /download serves a gzip copy (made when binaries are published) to
  clients that accept it: td's self-update and the Linux installer
  (curl --compressed). 86 MB -> 32 MB for Windows, 123 MB -> 41 MB for Linux.
- /api/symbols carries an ETag; td keeps it with its snapshot and gets 304
  when nothing changed (it fetched 1.2 MB on every sync).
- td's self-update gave up after 10 minutes, so a slow connection downloaded
  most of the binary every hour forever. It now only gives up on a download
  that stalls for a minute.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RNwtXPYqFu3oKaaXwYwNWW
This commit is contained in:
Saty 2026-10-18 18:21:31 +02:00
commit 994cbb36d3
7 changed files with 89 additions and 13 deletions

View file

@ -554,6 +554,32 @@ test('a function that no longer passes at merge goes back alone; the rest merges
}
});
test('the symbol table is only sent when it changed; downloads come compressed when accepted', async () => {
const token = await activeUser('trafficker');
const auth = { authorization: `Bearer ${token}` };
const first = await fetch(base + '/api/symbols', { headers: auth });
assert.equal(first.status, 200);
const etag = first.headers.get('etag');
assert.ok(etag);
await first.json();
const again = await fetch(base + '/api/symbols', { headers: { ...auth, 'if-none-match': etag } });
assert.equal(again.status, 304, 'unchanged: nothing sent');
const dir = path.join(ctx.config.dataDir, 'downloads');
fs.mkdirSync(dir, { recursive: true });
const bin = Buffer.alloc(200_000, 7);
fs.writeFileSync(path.join(dir, 'td-linux-x64'), bin);
fs.writeFileSync(path.join(dir, 'td-linux-x64.gz'), (await import('node:zlib')).gzipSync(bin));
const zipped = await fetch(base + '/download/td-linux-x64', { headers: { 'accept-encoding': 'gzip' } });
assert.equal(zipped.headers.get('content-encoding'), 'gzip');
assert.ok(Number(zipped.headers.get('content-length')) < 10_000);
assert.deepEqual(Buffer.from(await zipped.arrayBuffer()), bin, 'fetch unpacks it to the same bytes');
const plain = await fetch(base + '/download/td-linux-x64', { headers: { 'accept-encoding': 'identity' } });
assert.equal(plain.headers.get('content-encoding'), null);
assert.equal(Number(plain.headers.get('content-length')), bin.length);
await plain.arrayBuffer();
});
test('the crew view is public and shows no account details', async () => {
const token = await activeUser('crewview');
await call('GET', '/api/tasks', null, token); // the agent checks in