chore: initialize from frontend template 4dc033c
This commit is contained in:
@@ -0,0 +1,170 @@
|
||||
/// <reference lib="webworker" />
|
||||
import { OFFLINE_SYNC_TAG } from "../../contracts/offline-command.ts";
|
||||
import type {
|
||||
ServiceWorkerHandlerId,
|
||||
ServiceWorkerProtocolIdentity,
|
||||
StaticAssetManifestV1,
|
||||
} from "../../contracts/service-worker.ts";
|
||||
import {
|
||||
createServiceWorkerRuntime,
|
||||
type WorkerScopeLike,
|
||||
} from "./service-worker-lifecycle.ts";
|
||||
import { parseServiceWorkerMessage } from "./service-worker-protocol.ts";
|
||||
|
||||
/**
|
||||
* §17.1. The one physical worker entry for this scope.
|
||||
*
|
||||
* PWA lifecycle, verified static asset fetch, Web Push and the optional sync
|
||||
* wake-up are all handler factories inside this single entry. A second
|
||||
* registration for any of them is prohibited.
|
||||
*
|
||||
* This module is compiled only by `vite.service-worker.config.ts` when the
|
||||
* static selection is `ACTIVE`; it is never part of the page bundle.
|
||||
*/
|
||||
|
||||
declare const self: ServiceWorkerGlobalScope;
|
||||
|
||||
// Build-time virtual modules (§18.3). They resolve through the Service Worker
|
||||
// Vite config only, so the page bundle can never import a worker asset list.
|
||||
declare const __CA_SERVICE_WORKER_BUILD_INFO__: ServiceWorkerProtocolIdentity;
|
||||
declare const __CA_SERVICE_WORKER_ASSETS__: StaticAssetManifestV1 | null;
|
||||
declare const __CA_SERVICE_WORKER_HANDLERS__: readonly ServiceWorkerHandlerId[];
|
||||
declare const __CA_RUNTIME_CONFIG_URL__: string;
|
||||
declare const __CA_RELEASE_MANIFEST_URL__: string;
|
||||
|
||||
const identity = __CA_SERVICE_WORKER_BUILD_INFO__;
|
||||
const handlers = __CA_SERVICE_WORKER_HANDLERS__;
|
||||
|
||||
const scope: WorkerScopeLike = {
|
||||
caches: {
|
||||
open: (name) => caches.open(name),
|
||||
keys: () => caches.keys(),
|
||||
delete: (name) => caches.delete(name),
|
||||
match: (request) => caches.match(request),
|
||||
},
|
||||
clients: {
|
||||
matchAll: (options) =>
|
||||
self.clients.matchAll(
|
||||
options as { type?: "window"; includeUncontrolled?: boolean },
|
||||
) as Promise<
|
||||
readonly {
|
||||
id: string;
|
||||
url: string;
|
||||
postMessage(m: unknown): void;
|
||||
}[]
|
||||
>,
|
||||
},
|
||||
registrationScope: self.registration.scope,
|
||||
skipWaiting: () => self.skipWaiting(),
|
||||
fetcher: (input: RequestInfo | URL, init?: RequestInit) => fetch(input, init),
|
||||
async digest(bytes) {
|
||||
const buffer = await crypto.subtle.digest(
|
||||
"SHA-256",
|
||||
bytes.slice().buffer as ArrayBuffer,
|
||||
);
|
||||
let hex = "";
|
||||
for (const byte of new Uint8Array(buffer)) {
|
||||
hex += byte.toString(16).padStart(2, "0");
|
||||
}
|
||||
return `sha256:${hex}`;
|
||||
},
|
||||
};
|
||||
|
||||
const runtime = createServiceWorkerRuntime(scope, {
|
||||
identity,
|
||||
handlers,
|
||||
manifest: __CA_SERVICE_WORKER_ASSETS__,
|
||||
runtimeConfigUrl: __CA_RUNTIME_CONFIG_URL__,
|
||||
releaseManifestUrl: __CA_RELEASE_MANIFEST_URL__,
|
||||
});
|
||||
|
||||
self.addEventListener("install", (event) => {
|
||||
// §17.10. Install never calls skipWaiting(); activation is a page handshake.
|
||||
event.waitUntil(runtime.onInstall());
|
||||
});
|
||||
|
||||
self.addEventListener("activate", (event) => {
|
||||
// §17.12. No clients.claim() in the baseline.
|
||||
event.waitUntil(runtime.onActivate());
|
||||
});
|
||||
|
||||
self.addEventListener("fetch", (event) => {
|
||||
const request = event.request;
|
||||
event.respondWith(
|
||||
runtime
|
||||
.onFetch({
|
||||
method: request.method,
|
||||
url: request.url,
|
||||
mode: request.mode,
|
||||
})
|
||||
.then((cached) => cached ?? fetch(request)),
|
||||
);
|
||||
});
|
||||
|
||||
self.addEventListener("message", (event) => {
|
||||
const parsed = parseServiceWorkerMessage(event.data);
|
||||
if (!parsed.ok) return;
|
||||
if (parsed.message.kind === "ACTIVATE_REQUEST") {
|
||||
event.waitUntil(runtime.onActivateRequest(event.data));
|
||||
return;
|
||||
}
|
||||
if (
|
||||
parsed.message.kind === "CLIENT_DRAINED" ||
|
||||
parsed.message.kind === "ACTIVATE_REJECTED"
|
||||
) {
|
||||
const source = event.source;
|
||||
if (source && "id" in source && typeof source.id === "string") {
|
||||
runtime.onClientMessage(event.data, source.id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (parsed.message.kind === "CACHE_RESET_REQUEST") {
|
||||
const source = event.source;
|
||||
if (
|
||||
source &&
|
||||
"id" in source &&
|
||||
typeof source.id === "string" &&
|
||||
"postMessage" in source &&
|
||||
typeof source.postMessage === "function"
|
||||
) {
|
||||
event.waitUntil(runtime.onCacheResetRequest(event.data, source));
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// §17.1 WEB_PUSH composition point.
|
||||
//
|
||||
// The Web Push runtime is a handler factory inside this one entry, never a
|
||||
// second registration. It is not wired here because the template cannot supply
|
||||
// the two product-owned inputs it needs: a PushAssociationFenceStore over the
|
||||
// product push control repository, and a WebPushNotificationRegistry of exact
|
||||
// notification types with the same-origin routes their clicks may open
|
||||
// (§21.11). Selecting WEB_PUSH means adding, inside a
|
||||
// `handlers.includes("WEB_PUSH")` guard: import
|
||||
// createWebPushServiceWorkerRuntime and createServiceWorkerScopeHost from the
|
||||
// sibling web-push adapter, then call the runtime with the scope host built
|
||||
// from `self` plus the product fence store and notification registry.
|
||||
//
|
||||
// Keeping the import out of the baseline entry is also what lets the realtime
|
||||
// and Web Push runtime be removed as a pure file deletion (§24.12).
|
||||
|
||||
if (handlers.includes("OFFLINE_SYNC_WAKEUP")) {
|
||||
// §19.18. Wake-up only: the handler records that a sync fired and notifies
|
||||
// controlled clients. It never sends an authenticated command (§19.20).
|
||||
self.addEventListener("sync", (rawEvent: Event) => {
|
||||
const event = rawEvent as ExtendableEvent & { tag?: string };
|
||||
if (event.tag !== OFFLINE_SYNC_TAG) return;
|
||||
event.waitUntil(
|
||||
self.clients.matchAll({ type: "window" }).then((clients) => {
|
||||
for (const client of clients) {
|
||||
client.postMessage({
|
||||
protocolVersion: 1,
|
||||
kind: "SYNC_WAKE_OBSERVED",
|
||||
messageId: crypto.randomUUID(),
|
||||
sourceBuildId: identity.buildId,
|
||||
});
|
||||
}
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user