fix: bound Service Worker marker reads and attribute native effects
SW-RR-01. The activation marker was read with response.text() whenever no Content-Length was present, so a large or non-terminating body could consume the whole activation step. It now reads through a bounded reader that stops one byte past the ceiling, cancels its reader, applies a read deadline and decodes UTF-8 fatally. SW-RR-02. A matching nonce is not identity. An activation or reset result whose event.source is null can no longer stand in for the expected worker; only a strict identity match is admitted. SW-RR-03. The build generator and the shared manifest decoder now read one exported extension table, so .mjs and .png stop being emitted-then-refused. .json is deliberately outside it: every JSON file in a build output is a control document the generator already excludes, not a cacheable asset. SW-RR-04. Both caches.open and cache.match are closed as a miss. Letting a match rejection propagate rejected respondWith itself, so the entry never reached its network fallback. WP-RR-01. focus and openWindow now carry the certainty phase showNotification already had — NOT_APPLIED, MAYBE_APPLIED, CONFIRMED — and an effect that lands after the handler deadline is observed exactly once. The evidence never authorizes a retry. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
bd90e0c983
commit
efc577de63
@@ -0,0 +1,69 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
import {
|
||||
CACHEABLE_ASSET_CONTENT_TYPES,
|
||||
decodeStaticAssetManifest,
|
||||
} from "../../src/contracts/service-worker-static-manifest.ts";
|
||||
|
||||
/**
|
||||
* SW-RR-03. The build generator and the shared runtime decoder must agree on
|
||||
* exactly which asset kinds exist. A generator that emits `.mjs` or `.png` while
|
||||
* the decoder refuses them turns a correct build into a runtime contract
|
||||
* failure, and the reverse admits a kind no build produces.
|
||||
*/
|
||||
describe("SW-RR-03 one authoritative cacheable asset table", () => {
|
||||
it("covers every extension the generator emits", async () => {
|
||||
const generator = await import(
|
||||
"../../scripts/generate-service-worker-assets.ts"
|
||||
);
|
||||
expect(generator).toBeDefined();
|
||||
for (const extension of [
|
||||
".js",
|
||||
".mjs",
|
||||
".css",
|
||||
".woff2",
|
||||
".svg",
|
||||
".png",
|
||||
".webp",
|
||||
]) {
|
||||
expect(CACHEABLE_ASSET_CONTENT_TYPES[extension]).toBeDefined();
|
||||
}
|
||||
});
|
||||
|
||||
it("decodes a manifest row for every table entry", () => {
|
||||
const assets = Object.entries(CACHEABLE_ASSET_CONTENT_TYPES).map(
|
||||
([extension, contentType], index) => ({
|
||||
url: `/assets/name-abcdefgh${index}${extension}`,
|
||||
sha256: `sha256:${"a".repeat(64)}`,
|
||||
bytes: 16,
|
||||
contentType,
|
||||
}),
|
||||
);
|
||||
const decoded = decodeStaticAssetManifest({
|
||||
schemaVersion: 1,
|
||||
buildId: "build-1",
|
||||
releaseId: "release-1",
|
||||
assets,
|
||||
setDigest: `sha256:${"b".repeat(64)}`,
|
||||
});
|
||||
expect(decoded).toMatchObject({ ok: true });
|
||||
});
|
||||
|
||||
it("refuses a row whose extension is not in the table", () => {
|
||||
const decoded = decodeStaticAssetManifest({
|
||||
schemaVersion: 1,
|
||||
buildId: "build-1",
|
||||
releaseId: "release-1",
|
||||
setDigest: `sha256:${"b".repeat(64)}`,
|
||||
assets: [
|
||||
{
|
||||
url: "/assets/control-abcdefgh.json",
|
||||
sha256: `sha256:${"a".repeat(64)}`,
|
||||
bytes: 16,
|
||||
contentType: "application/json",
|
||||
},
|
||||
],
|
||||
});
|
||||
expect(decoded.ok).toBe(false);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user