Let browsers cache GetImage responses
What does this PR aim to accomplish?:
Images served by GetImage (e.g. https://assets.xmoj-script.uk/GetImage?ImageID=ā¦) load slowly on every page view: the response has no Cache-Control, ETag or Last-Modified, so browsers can't cache it, and each view goes Worker ā GitHub API again (measured ~0.7ā2s per view; a conditional request still returned the full 200).
It also fixes a latent bug: when GitHub fails (404, rate limit), GetImage returned 200 image/png with GitHub's error JSON as the body.
How does this PR accomplish the above?:
Image IDs are 32 random letters and never reused, so a found image is safe to cache forever.
- Found images:
Cache-Control: public, max-age=31536000, immutableandETag: "<ImageID>"; stillcontent-type: image/png. If-None-Matchmatching the ID returns304without calling GitHub.- The GitHub body is streamed through instead of buffered into a
Blob. - GitHub errors keep GitHub's status and are
no-store, and an unreachable GitHub returns502withno-store, so a transient failure is never pinned in browser caches. - Dropped the
?1=1query on the GitHub API URL: authenticated API responses aren't cached by the Worker'sfetch, so it had no effect.
Tests: 4 new cases in test/process.test.js (immutable + bytes, 304 without fetch, error passthrough uncached, 502 uncached), all through Process(). npm test: 152/152 pass; wrangler deploy --dry-run builds.
Does this PR need a documentation update?:
No.
By submitting this pull request, I confirm the following:
- I have read and understood the contributor's guide, as well as this entire template. I understand which branch to base my commits and Pull Requests against. (
devfor XMOJ-Script,masterfor everything else). - I have commented on my proposed changes within the code.
- I have tested my changes.
- I am willing to help maintain this change if there are issues with it later.
- It is compatible with the GNU General Public License v3.0
- I have squashed any insignificant commits. (
git rebase) - I have checked that another pull request for this purpose does not exist.
- I have considered and confirmed that this submission will be valuable to others.
- I accept that this submission may not be used, and the pull request can be closed at the will of the maintainer.
- I give this submission freely and claim no ownership to its content.
- I have verified that my changes work correctly in all four UI combinations:
- the script's monochrome UI on XMOJ's new UI (
/webpages) - the script's monochrome UI on XMOJ's old/classic pages
- the script's classic UI on XMOJ's new UI (
/webpages) - the script's classic UI on XMOJ's old/classic pages
- the script's monochrome UI on XMOJ's new UI (
- I have read the above and my PR is ready for review. Check this box to confirm
š¤ Generated with Claude Code
Summary by Sourcery
Improve GetImage caching, validation, and error handling so image delivery is faster and failures are not incorrectly cached.
New Features:
- Enable long-term browser caching and conditional requests for immutable uploaded images served by
GetImage. - Support serving the project logo through
GetImagewith replaceable, short-lived caching.
Bug Fixes:
- Preserve GitHub error statuses and prevent caching of errors, including returning 502 for unreachable GitHub services.
- Reject invalid image identifiers before accessing GitHub.
Enhancements:
- Stream image responses directly and handle ETag validation without unnecessary GitHub requests.
Tests:
- Add coverage for image caching, conditional responses, error handling, identifier validation, ETag matching, and logo behavior.
Summary by cubic
Lets browsers cache GetImage responses so images no longer take roughly 1ā2 seconds to load on every page view.
Found images are now marked cache-control: public, max-age=31536000, immutable with an ID-based ETag; a matching If-None-Match returns 304 without calling GitHub, and the image body is streamed instead of buffered into a Blob. Matching uses weak comparison over a tag list per RFC 9110, so W/"id" or "a", "id" still revalidate.
Also fixes a bug where GitHub errors were returned as 200 image/png with the error JSON as the body. Error responses now keep GitHub's status, and an unreachable GitHub returns 502; both are marked no-store so a transient failure is never pinned in browser caches. Image IDs that aren't 32 lowercase letters (the only format UploadImage issues) are rejected with 400 before reaching GitHub, where ../ segments could resolve to other repo paths. The project logo remains reachable via logo / logo.png, cached for a day without an ID-based ETag so it can be replaced in place.