fix(webmail): a new profile photo shows up everywhere at once
The photo endpoint was cached for five minutes with no revalidation, and only the settings page added a version to the URL, so the header, the message list and the reader kept the old photo until it expired. - every <Avatar> reads a per-address version (and refetches when it changes); saving or removing a photo bumps it, in this tab and the other open tabs - the server answers private, max-age=60, must-revalidate, so a stale copy lives for a minute at most and then costs one 304 Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
d82cf71f5b
commit
c0f556b190
6 files changed
+146
-4
No files matched your search
@@ -946,6 +946,14 @@ describeDb("auth API against a real database", () => {
|
||||
expect(got.headers.get("content-type")).toBe("image/png");
|
||||
expect(Buffer.from(await got.arrayBuffer())).toEqual(png);
|
||||
|
||||
// A new photo has to show up quickly: the browser may reuse the old one
|
||||
// for at most a minute, and must ask again after that. It was five minutes
|
||||
// with no revalidation, which is what made a changed photo "take forever".
|
||||
const cacheControl = got.headers.get("cache-control") ?? "";
|
||||
expect(cacheControl).toContain("private");
|
||||
expect(cacheControl).toContain("must-revalidate");
|
||||
expect(Number(/max-age=(\d+)/.exec(cacheControl)?.[1])).toBeLessThanOrEqual(60);
|
||||
|
||||
// A photo appears beside every message from that person; without the
|
||||
// conditional answer it would be re-sent on every one of them.
|
||||
const etag = got.headers.get("etag")!;
|
||||
|
||||
@@ -343,7 +343,10 @@ async function getAvatar(
|
||||
res.setHeader("ETag", `"${avatar.etag}"`);
|
||||
// Private: this is served to a signed-in reader, and a shared cache must not
|
||||
// hand one person's photo to the next request that comes along.
|
||||
res.setHeader("Cache-Control", "private, max-age=300");
|
||||
// One minute, then revalidate with the ETag (a 304 costs no body). It was five
|
||||
// minutes with no revalidation, so a new photo took that long to show up
|
||||
// wherever the page did not add a version to the URL.
|
||||
res.setHeader("Cache-Control", "private, max-age=60, must-revalidate");
|
||||
// The bytes were sniffed on the way in, but a browser that ignores the type
|
||||
// and sniffs for itself must not be allowed to find something else.
|
||||
res.setHeader("X-Content-Type-Options", "nosniff");
|
||||
|
||||
Reference in new issue
Block a user