mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 16:02:26 +00:00
Verified first that event caching needs nothing: allEvents (incl. ingested NIP-71) already persists to IndexedDB and every grid renders cache-first + merge-new. The real gap was MEDIA: thumbnails/avatars/banners re-downloaded across visits. Part 1 — Cache-Control on uploads (js/storage/s3-multipart.js): computeCacheControl already stamped most media immutable but missed webp/avif/ico and .torrent files (which fell to the 1h default). Added them — safe because buildR2Key namespaces keys by infohash (content-addressed, a key's bytes never change). Playlists (m3u8/mpd) stay short-lived (live manifests mutate in place). Exported for tests. Part 2 — service-worker image cache (sw.min.js): Cross-origin images (the hosts bitvid does NOT control headers for) are now served stale-while-revalidate from a versioned Cache-API cache (bitvid-images-v1): - instant on revisit; background refresh self-heals changed images (and where part 1's immutable headers apply, that refresh is served by the HTTP cache — free) - opaque (no-cors) responses cached — exactly the external-thumbnail case - LRU-capped (300); quota failures drop the cache and fail open; every handler error falls back to plain fetch so a caching bug can never break images - the activate handler's wipe-all now PRESERVES the current image cache (old versions + other caches still wiped); same-origin assets untouched (they ride the host HTTP cache + dist cache-purge pipeline) - app._initServiceWorker registers the SW at boot when none exists, same path/scope/options as webtorrent.js's setupServiceWorker so playback's later register() resolves to the same registration (playback path untouched) - SW_VERSION bumped Tests: storage-cache-control (3, exported policy) + sw-image-cache (6 — evaluates the REAL sw.min.js in a VM sandbox and drives its handlers: cold/warm/offline/ opaque flows, same-origin + non-image non-interception, activate preserves the image cache). Lint + build green. Co-Authored-By: Claude Opus 4.8 <[email protected]>
64 lines
2.4 KiB
JavaScript
64 lines
2.4 KiB
JavaScript
// Cache-Control policy for uploaded media (TODO #55, part 1). Upload keys are
|
|
// content-addressed (buildR2Key namespaces by infohash), so media should get a
|
|
// long immutable Cache-Control — browsers then keep thumbnails/videos/torrents
|
|
// across visits. Mutable playlists (m3u8/mpd) must stay short-lived.
|
|
//
|
|
// test_integrity_note:
|
|
// change_type: ["new_tests"]
|
|
// scenarios:
|
|
// - id: SCN-upload-cache-control
|
|
// given: "an object key by extension"
|
|
// when: "computeCacheControl derives the Cache-Control header"
|
|
// then: "media/torrents are immutable-year; playlists revalidate; unknown gets the default"
|
|
// observable_outcomes:
|
|
// - "video + image (incl webp/avif) + .torrent keys -> public, max-age=31536000, immutable"
|
|
// - "m3u8/mpd -> public, max-age=60, must-revalidate"
|
|
// - "unknown/empty keys -> the 1h default (never immutable)"
|
|
// determinism_controls:
|
|
// - "pure function; explicit inputs"
|
|
// anti_cheat_rationale:
|
|
// prevents: ["hard-coded return value"]
|
|
// relaxation:
|
|
// did_relax_any_assertion: false
|
|
|
|
import "./test-helpers/setup-localstorage.mjs";
|
|
import test from "node:test";
|
|
import { strict as assert } from "node:assert";
|
|
import { computeCacheControl } from "../js/storage/s3-multipart.js";
|
|
|
|
const IMMUTABLE = "public, max-age=31536000, immutable";
|
|
|
|
test("videos, images (incl. webp/avif), and torrents are immutable for a year", () => {
|
|
for (const key of [
|
|
"u/npub1x/abc123/video.mp4",
|
|
"u/npub1x/abc123/clip.webm",
|
|
"u/npub1x/abc123/thumb.jpg",
|
|
"u/npub1x/abc123/thumb.webp",
|
|
"u/npub1x/abc123/poster.avif",
|
|
"u/npub1x/abc123/video.torrent",
|
|
"u/npub1x/external/deadbeef.torrent",
|
|
"SHOUTY/PATH/THUMB.PNG",
|
|
]) {
|
|
assert.equal(computeCacheControl(key), IMMUTABLE, key);
|
|
}
|
|
});
|
|
|
|
test("mutable playlists stay short-lived (live manifests change in place)", () => {
|
|
assert.equal(
|
|
computeCacheControl("u/npub1x/live/stream.m3u8"),
|
|
"public, max-age=60, must-revalidate",
|
|
);
|
|
assert.equal(
|
|
computeCacheControl("u/npub1x/live/stream.mpd"),
|
|
"public, max-age=60, must-revalidate",
|
|
);
|
|
});
|
|
|
|
test("unknown or missing keys fall back to the 1h default, never immutable", () => {
|
|
for (const key of ["u/npub1x/uploads/file.bin", "no-extension", "", null, undefined]) {
|
|
const value = computeCacheControl(key);
|
|
assert.equal(value, "public, max-age=3600", String(key));
|
|
assert.ok(!value.includes("immutable"));
|
|
}
|
|
});
|