@demystify/play-packs
v0.2.0
Published
Demystify Play content packs — SRI-verified same-origin download, IndexedDB store, quota guard and LRU eviction.
Maintainers
Readme
@demystify/play-packs
Content packs for Demystify Play: SRI-verified same-origin download, IndexedDB storage, a quota guard and LRU eviction.
npm install @demystify/play-packsThe constraint this is shaped around
Capacity is not the problem. Eviction is. A modern browser offers roughly 60% of total disk per origin, which on any real phone is tens of gigabytes — far more than a whole game catalogue. What actually goes wrong is the browser reclaiming it: IndexedDB, Cache Storage and OPFS share one bucket and are evicted all-or-nothing together, so a single eviction takes every pack at once.
Everything here follows from that:
- 1.5× headroom before a write, not 1.0×. The store needs room for the incoming blob and the existing data while it writes, and filling the bucket to the brim is what triggers the eviction.
- The budget is checked before the download starts, so a user on a full device is told up front rather than after waiting for a transfer that cannot be stored.
- Reclaimed space is measured, not assumed. "12 MB freed" that does not move the number is the kind of small dishonesty that makes an entire offline claim untrustworthy.
persist()is requested after a real interaction, never on load. It does not increase quota — it only exempts the origin from eviction — and an unprompted permission is a permission most people decline.
Usage
import { installPack, getPack, storageState, canInstall, formatBytes } from "@demystify/play-packs";
const state = await storageState();
const check = canInstall(state, pack.sizeBytes);
if (!check.ok) return show(check.reason); // actionable, before any transfer
const { pack, evicted } = await installPack(
{ id, version, sizeBytes, integrity }, // integrity comes from the registry
`/play/packs/${id}.${version}.json`,
);The download uses fetch(url, { integrity }), so the browser verifies the SHA-256 and rejects a
mismatch before a byte reaches application code. Hashing it ourselves would mean buffering the whole
pack, calling crypto.subtle.digest (which cannot stream) and comparing — more code, more memory,
and a check that runs after the bytes are already in the heap.
Packs are same-origin, and not incidentally: an opaque cross-origin response inflates the reported storage usage by ~7 MB each, which would make every quota calculation here meaningless.
Licence
Apache-2.0 · Demystify Systems
