@pwa-platform/sw-runtime 0.1.0-beta.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -0
  3. package/dist/build/config.d.ts +10 -0
  4. package/dist/build/config.d.ts.map +1 -0
  5. package/dist/build/config.js +40 -0
  6. package/dist/build/config.js.map +1 -0
  7. package/dist/build/index.d.ts +6 -0
  8. package/dist/build/index.d.ts.map +1 -0
  9. package/dist/build/index.js +9 -0
  10. package/dist/build/index.js.map +1 -0
  11. package/dist/build/inject.d.ts +13 -0
  12. package/dist/build/inject.d.ts.map +1 -0
  13. package/dist/build/inject.js +29 -0
  14. package/dist/build/inject.js.map +1 -0
  15. package/dist/entries/platform-worker-entry.d.ts +2 -0
  16. package/dist/entries/platform-worker-entry.d.ts.map +1 -0
  17. package/dist/entries/platform-worker-entry.js +3 -0
  18. package/dist/entries/platform-worker-entry.js.map +1 -0
  19. package/dist/entries/recovery-worker-entry.d.ts +2 -0
  20. package/dist/entries/recovery-worker-entry.d.ts.map +1 -0
  21. package/dist/entries/recovery-worker-entry.js +4 -0
  22. package/dist/entries/recovery-worker-entry.js.map +1 -0
  23. package/dist/messages/index.d.ts +49 -0
  24. package/dist/messages/index.d.ts.map +1 -0
  25. package/dist/messages/index.js +90 -0
  26. package/dist/messages/index.js.map +1 -0
  27. package/dist/push-payload/index.d.ts +46 -0
  28. package/dist/push-payload/index.d.ts.map +1 -0
  29. package/dist/push-payload/index.js +115 -0
  30. package/dist/push-payload/index.js.map +1 -0
  31. package/dist/recovery-worker/index.d.ts +18 -0
  32. package/dist/recovery-worker/index.d.ts.map +1 -0
  33. package/dist/recovery-worker/index.js +60 -0
  34. package/dist/recovery-worker/index.js.map +1 -0
  35. package/dist/shared/config.d.ts +71 -0
  36. package/dist/shared/config.d.ts.map +1 -0
  37. package/dist/shared/config.js +216 -0
  38. package/dist/shared/config.js.map +1 -0
  39. package/dist/shared/path-match.d.ts +20 -0
  40. package/dist/shared/path-match.d.ts.map +1 -0
  41. package/dist/shared/path-match.js +56 -0
  42. package/dist/shared/path-match.js.map +1 -0
  43. package/dist/worker/decide.d.ts +65 -0
  44. package/dist/worker/decide.d.ts.map +1 -0
  45. package/dist/worker/decide.js +65 -0
  46. package/dist/worker/decide.js.map +1 -0
  47. package/dist/worker/handlers.d.ts +16 -0
  48. package/dist/worker/handlers.d.ts.map +1 -0
  49. package/dist/worker/handlers.js +196 -0
  50. package/dist/worker/handlers.js.map +1 -0
  51. package/dist/worker/index.d.ts +17 -0
  52. package/dist/worker/index.d.ts.map +1 -0
  53. package/dist/worker/index.js +16 -0
  54. package/dist/worker/index.js.map +1 -0
  55. package/dist/worker/notification-target.d.ts +9 -0
  56. package/dist/worker/notification-target.d.ts.map +1 -0
  57. package/dist/worker/notification-target.js +28 -0
  58. package/dist/worker/notification-target.js.map +1 -0
  59. package/dist/worker/offline-write-flush.d.ts +10 -0
  60. package/dist/worker/offline-write-flush.d.ts.map +1 -0
  61. package/dist/worker/offline-write-flush.js +32 -0
  62. package/dist/worker/offline-write-flush.js.map +1 -0
  63. package/dist/worker/offline-write-intent.d.ts +36 -0
  64. package/dist/worker/offline-write-intent.d.ts.map +1 -0
  65. package/dist/worker/offline-write-intent.js +84 -0
  66. package/dist/worker/offline-write-intent.js.map +1 -0
  67. package/dist/worker/offline-write-store.d.ts +27 -0
  68. package/dist/worker/offline-write-store.d.ts.map +1 -0
  69. package/dist/worker/offline-write-store.js +152 -0
  70. package/dist/worker/offline-write-store.js.map +1 -0
  71. package/package.json +65 -0
@@ -0,0 +1,196 @@
1
+ import { isOfflineWriteMessage, isSkipWaitingMessage } from "../messages/index.js";
2
+ import { validatePushPayload } from "../push-payload/index.js";
3
+ import { createRouter } from "./decide.js";
4
+ import { validateOfflineWriteEnqueue, validateOfflineWriteFlush } from "./offline-write-intent.js";
5
+ import { flushOfflineWrites } from "./offline-write-flush.js";
6
+ import { createOfflineWriteStore } from "./offline-write-store.js";
7
+ import { resolveNotificationTarget } from "./notification-target.js";
8
+ /**
9
+ * Registers `install`, `activate`, `fetch`, `message`, `push` and `notificationclick`, and nothing else. The worker
10
+ * never calls `skipWaiting` on its own (updates are prompted) and never claims clients. `push` and
11
+ * `notificationclick` are always on: see ADR-0021 for why a fixed pair of listeners has no observable effect on an
12
+ * application that never subscribes to push.
13
+ */
14
+ export function attachPlatformWorker({ scope, config, engine }) {
15
+ const origin = new URL(scope.location.href).origin;
16
+ const router = createRouter({ config, manifestUrls: engine.urls(), origin });
17
+ const offlineWriteStore = config.offlineWrites.enabled ? createOfflineWriteStore(config.offlineWrites) : undefined;
18
+ scope.addEventListener("install", (event) => {
19
+ // The engine passes its work to event.waitUntil, so a failed download fails the install; the rejection is only
20
+ // silenced here to keep it out of the worker's unhandled-rejection log.
21
+ void engine.install(event).catch(() => undefined);
22
+ });
23
+ scope.addEventListener("activate", (event) => {
24
+ // The cleanup itself already reached event.waitUntil, so a rejection here carries only the engine's diagnostic
25
+ // signal — it fails loudly when a Workbox update changes the shape of its result. Let that surface.
26
+ void engine.activate(event);
27
+ });
28
+ scope.addEventListener("fetch", (event) => {
29
+ const decision = router.decide({
30
+ method: event.request.method,
31
+ url: event.request.url,
32
+ navigation: event.request.mode === "navigate",
33
+ // Any value counts, including "bytes=0-" (ADR-0023); decide() only acts on it for a manifest-hit request.
34
+ range: event.request.headers.has("range"),
35
+ });
36
+ if (decision.kind === "passthrough")
37
+ return;
38
+ if (decision.kind === "precache") {
39
+ event.respondWith(fromPrecache(scope, engine, decision.manifestUrl, event.request));
40
+ return;
41
+ }
42
+ event.respondWith(navigate(scope, engine, decision.fallbacks, event.request));
43
+ });
44
+ scope.addEventListener("message", (event) => {
45
+ if (!isSameOriginWindow(event.source, origin))
46
+ return;
47
+ if (isSkipWaitingMessage(event.data)) {
48
+ // Prompted updates: the new worker takes over only after a page confirmed it (ADR-0005).
49
+ event.waitUntil(scope.skipWaiting());
50
+ return;
51
+ }
52
+ if (!isOfflineWriteMessage(event.data))
53
+ return;
54
+ const [port, ...rest] = event.ports;
55
+ if (port === undefined || rest.length !== 0)
56
+ return;
57
+ if (event.data.type === "pwa:offline-write:clear") {
58
+ event.waitUntil(clear(event.data, offlineWriteStore, port));
59
+ return;
60
+ }
61
+ if (offlineWriteStore === undefined)
62
+ return;
63
+ if (event.data.type === "pwa:offline-write:enqueue")
64
+ event.waitUntil(enqueue(event.data, config, origin, offlineWriteStore, port));
65
+ if (event.data.type === "pwa:offline-write:flush")
66
+ event.waitUntil(flush(event.data, config, origin, offlineWriteStore, port, scope.fetch));
67
+ });
68
+ scope.addEventListener("push", (event) => {
69
+ const payload = event.data === null ? null : validatePushPayload(event.data.text());
70
+ // Not the platform's format, empty, or unparsable: show nothing and record nothing (spec "设计 / 2").
71
+ if (payload === null)
72
+ return;
73
+ const options = { data: { url: payload.url ?? null, data: payload.data ?? null } };
74
+ if (payload.body !== undefined)
75
+ options.body = payload.body;
76
+ if (payload.tag !== undefined)
77
+ options.tag = payload.tag;
78
+ event.waitUntil(scope.registration.showNotification(payload.title, options));
79
+ });
80
+ scope.addEventListener("notificationclick", (event) => {
81
+ event.notification.close();
82
+ const target = resolveNotificationTarget(readNotificationUrl(event.notification.data), scope.registration.scope);
83
+ event.waitUntil(focusOrOpen(scope, target));
84
+ });
85
+ }
86
+ async function clear(message, store, port) {
87
+ try {
88
+ await store?.clear();
89
+ replyClear(port, message.requestId);
90
+ }
91
+ catch {
92
+ reply(port, message.requestId, "rejected", "offline-write.storage");
93
+ }
94
+ }
95
+ async function flush(message, config, origin, store, port, send) {
96
+ const checked = validateOfflineWriteFlush(message, config);
97
+ if (!checked.ok)
98
+ return reply(port, message.requestId, "rejected", checked.code);
99
+ try {
100
+ const result = await flushOfflineWrites(store, checked.binding, origin, send);
101
+ replyFlush(port, message.requestId, result);
102
+ }
103
+ catch {
104
+ reply(port, message.requestId, "rejected", "offline-write.storage");
105
+ }
106
+ }
107
+ async function enqueue(message, config, origin, store, port) {
108
+ const checked = validateOfflineWriteEnqueue(message, config, origin);
109
+ if (!checked.ok)
110
+ return reply(port, message.requestId, "rejected", checked.code);
111
+ try {
112
+ reply(port, message.requestId, await store.enqueue(checked.value));
113
+ }
114
+ catch {
115
+ reply(port, message.requestId, "rejected", "offline-write.storage");
116
+ }
117
+ }
118
+ function reply(port, requestId, status, code) {
119
+ try {
120
+ port.postMessage(code === undefined ? { type: "pwa:offline-write:result", version: 1, requestId, status } : { type: "pwa:offline-write:result", version: 1, requestId, status, code });
121
+ }
122
+ catch {
123
+ // The page may have closed its port; queue admission remains authoritative and must not create an unhandled rejection.
124
+ }
125
+ }
126
+ function replyFlush(port, requestId, result) {
127
+ try {
128
+ port.postMessage({ type: "pwa:offline-write:result", version: 1, requestId, status: "flushed", ...result });
129
+ }
130
+ catch {
131
+ // The page may have closed its port after requesting a flush; replay outcomes remain durable in IndexedDB.
132
+ }
133
+ }
134
+ function replyClear(port, requestId) {
135
+ try {
136
+ port.postMessage({ type: "pwa:offline-write:result", version: 1, requestId, status: "cleared" });
137
+ }
138
+ catch {
139
+ // The page may have closed its port; a completed deletion must not surface as an unhandled worker rejection.
140
+ }
141
+ }
142
+ /** Reads `data.url` without ever invoking an accessor: anything other than a plain object with a string data property is undefined. */
143
+ function readNotificationUrl(data) {
144
+ if (typeof data !== "object" || data === null || Array.isArray(data))
145
+ return undefined;
146
+ const prototype = Object.getPrototypeOf(data);
147
+ if (prototype !== Object.prototype && prototype !== null)
148
+ return undefined;
149
+ const descriptor = Object.getOwnPropertyDescriptor(data, "url");
150
+ if (descriptor === undefined || !("value" in descriptor) || typeof descriptor.value !== "string")
151
+ return undefined;
152
+ return descriptor.value;
153
+ }
154
+ /** Focuses a same-origin, same-scope window already showing `target`, otherwise opens it. Never navigates an existing window. */
155
+ async function focusOrOpen(scope, target) {
156
+ try {
157
+ const windows = await scope.clients.matchAll({ type: "window", includeUncontrolled: true });
158
+ const existing = windows.find((client) => client.url === target);
159
+ if (existing !== undefined) {
160
+ await existing.focus();
161
+ return;
162
+ }
163
+ await scope.clients.openWindow(target);
164
+ }
165
+ catch {
166
+ // openWindow (and, in principle, focus) can reject — e.g. without user activation. There is nowhere useful to
167
+ // surface that, so it is swallowed here rather than left as an unhandled rejection on the waitUntil promise.
168
+ }
169
+ }
170
+ /** A precached URL: the cache answers; a missing entry falls back to the network rather than failing the request. */
171
+ async function fromPrecache(scope, engine, manifestUrl, request) {
172
+ return (await engine.match(manifestUrl)) ?? (await scope.fetch(request));
173
+ }
174
+ /** Navigation: the network answers whenever it responds at all, including 4xx and 5xx; offline uses the fallbacks. */
175
+ async function navigate(scope, engine, fallbacks, request) {
176
+ try {
177
+ const response = await scope.fetch(request);
178
+ if (response.type !== "error")
179
+ return response;
180
+ }
181
+ catch {
182
+ // A rejected fetch is the usual network-failure signal; an error response follows the same fallback path.
183
+ }
184
+ for (const candidate of fallbacks) {
185
+ const response = await engine.match(candidate);
186
+ if (response !== undefined)
187
+ return response;
188
+ }
189
+ return Response.error();
190
+ }
191
+ function isSameOriginWindow(source, origin) {
192
+ if (source === null || !("type" in source) || source.type !== "window")
193
+ return false;
194
+ return URL.canParse(source.url) && new URL(source.url).origin === origin;
195
+ }
196
+ //# sourceMappingURL=handlers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handlers.js","sourceRoot":"","sources":["../../src/worker/handlers.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ import { type PwaPrecacheManifestEntry } from "@pwa-platform/engine-workbox/worker";
2
+ import { type PwaPlatformWorkerConfig } from "../shared/config.js";
3
+ export type PwaPlatformWorkerOptions = {
4
+ readonly scope: ServiceWorkerGlobalScope;
5
+ /** The injected config; validated before the engine is created or any listener is registered. */
6
+ readonly config: PwaPlatformWorkerConfig;
7
+ /** The injected precache manifest (`self.__WB_MANIFEST`). */
8
+ readonly manifest: readonly PwaPrecacheManifestEntry[];
9
+ };
10
+ /**
11
+ * Registers the platform worker: it precaches the plan's entries through the engine port, serves exact precache
12
+ * hits, answers navigations network first with the plan's offline fallbacks, and leaves every other request to the
13
+ * network. It skips waiting only when a page confirms an update.
14
+ */
15
+ export declare function registerPlatformWorker({ scope, config, manifest }: PwaPlatformWorkerOptions): void;
16
+ export type { PwaPassthroughReason, PwaRequestDecision, PwaRequestInput } from "./decide.js";
17
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAwB,KAAK,wBAAwB,EAAE,MAAM,qCAAqC,CAAC;AAC1G,OAAO,EAAgC,KAAK,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAGjG,MAAM,MAAM,wBAAwB,GAAG;IACrC,QAAQ,CAAC,KAAK,EAAE,wBAAwB,CAAC;IACzC,iGAAiG;IACjG,QAAQ,CAAC,MAAM,EAAE,uBAAuB,CAAC;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,QAAQ,EAAE,SAAS,wBAAwB,EAAE,CAAC;CACxD,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,sBAAsB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,wBAAwB,GAAG,IAAI,CAIlG;AAED,YAAY,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,16 @@
1
+ // Platform worker (service worker scope): registers install, activate, fetch and message listeners.
2
+ // It imports only @pwa-platform/engine-workbox/worker.
3
+ import { createPrecacheEngine } from "@pwa-platform/engine-workbox/worker";
4
+ import { validatePlatformWorkerConfig } from "../shared/config.js";
5
+ import { attachPlatformWorker } from "./handlers.js";
6
+ /**
7
+ * Registers the platform worker: it precaches the plan's entries through the engine port, serves exact precache
8
+ * hits, answers navigations network first with the plan's offline fallbacks, and leaves every other request to the
9
+ * network. It skips waiting only when a page confirms an update.
10
+ */
11
+ export function registerPlatformWorker({ scope, config, manifest }) {
12
+ const validated = validatePlatformWorkerConfig(config);
13
+ const engine = createPrecacheEngine({ cacheName: validated.precacheCacheName, entries: manifest });
14
+ attachPlatformWorker({ scope, config: validated, engine });
15
+ }
16
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/worker/index.ts"],"names":[],"mappings":"AAAA,oGAAoG;AACpG,uDAAuD;AACvD,OAAO,EAAE,oBAAoB,EAAiC,MAAM,qCAAqC,CAAC;AAC1G,OAAO,EAAE,4BAA4B,EAAgC,MAAM,qBAAqB,CAAC;AACjG,OAAO,EAAE,oBAAoB,EAAE,MAAM,eAAe,CAAC;AAUrD;;;;GAIG;AACH,MAAM,UAAU,sBAAsB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAA4B;IAC1F,MAAM,SAAS,GAAG,4BAA4B,CAAC,MAAM,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,oBAAoB,CAAC,EAAE,SAAS,EAAE,SAAS,CAAC,iBAAiB,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,CAAC;IACnG,oBAAoB,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,CAAC,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Resolves `url` (as read from the notification's `data.url`, of unknown shape) against `scopeUrl`
3
+ * (`registration.scope`, always absolute and ending in "/"). Falls back to `scopeUrl` whenever `url` is missing,
4
+ * malformed, cross-origin, carries credentials, or resolves outside the scope — including `..` and percent-encoded
5
+ * traversal, and a same-origin address that merely shares the scope's prefix (e.g. `/application/` vs `/app/`),
6
+ * because the WHATWG URL parser normalises the target before this compares its serialized `href` against the scope.
7
+ */
8
+ export declare function resolveNotificationTarget(url: unknown, scopeUrl: string): string;
9
+ //# sourceMappingURL=notification-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-target.d.ts","sourceRoot":"","sources":["../../src/worker/notification-target.ts"],"names":[],"mappings":"AAGA;;;;;;GAMG;AACH,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAehF"}
@@ -0,0 +1,28 @@
1
+ // Resolves a `notificationclick` target to an address the platform worker is allowed to open or focus: inside the
2
+ // same origin and scope as the worker itself. Spec: spec/push-module.md "设计 / 2. 平台 worker 的两个新监听"; ADR-0021.
3
+ /**
4
+ * Resolves `url` (as read from the notification's `data.url`, of unknown shape) against `scopeUrl`
5
+ * (`registration.scope`, always absolute and ending in "/"). Falls back to `scopeUrl` whenever `url` is missing,
6
+ * malformed, cross-origin, carries credentials, or resolves outside the scope — including `..` and percent-encoded
7
+ * traversal, and a same-origin address that merely shares the scope's prefix (e.g. `/application/` vs `/app/`),
8
+ * because the WHATWG URL parser normalises the target before this compares its serialized `href` against the scope.
9
+ */
10
+ export function resolveNotificationTarget(url, scopeUrl) {
11
+ if (typeof url !== "string" || url.length === 0)
12
+ return scopeUrl;
13
+ let target;
14
+ try {
15
+ target = new URL(url, scopeUrl);
16
+ }
17
+ catch {
18
+ return scopeUrl;
19
+ }
20
+ if (target.origin !== new URL(scopeUrl).origin)
21
+ return scopeUrl;
22
+ if (target.username.length > 0 || target.password.length > 0)
23
+ return scopeUrl;
24
+ if (!target.href.startsWith(scopeUrl))
25
+ return scopeUrl;
26
+ return target.href;
27
+ }
28
+ //# sourceMappingURL=notification-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-target.js","sourceRoot":"","sources":["../../src/worker/notification-target.ts"],"names":[],"mappings":"AAAA,kHAAkH;AAClH,8GAA8G;AAE9G;;;;;;GAMG;AACH,MAAM,UAAU,yBAAyB,CAAC,GAAY,EAAE,QAAgB;IACtE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC;IAEjE,IAAI,MAAW,CAAC;IAChB,IAAI,CAAC;QACH,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IAClC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,QAAQ,CAAC;IAClB,CAAC;IAED,IAAI,MAAM,CAAC,MAAM,KAAK,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,MAAM;QAAE,OAAO,QAAQ,CAAC;IAChE,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,IAAI,MAAM,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC9E,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,QAAQ,CAAC;IAEvD,OAAO,MAAM,CAAC,IAAI,CAAC;AACrB,CAAC"}
@@ -0,0 +1,10 @@
1
+ import type { PwaOfflineWriteStore } from "./offline-write-store.js";
2
+ export type PwaOfflineWriteFlushResult = {
3
+ readonly sent: number;
4
+ readonly retained: number;
5
+ readonly failed: number;
6
+ readonly purged: number;
7
+ };
8
+ /** Explicit FIFO replay; it is never called by fetch, Sync, Push or worker activation. */
9
+ export declare function flushOfflineWrites(store: PwaOfflineWriteStore, binding: string, origin: string, send: typeof fetch): Promise<PwaOfflineWriteFlushResult>;
10
+ //# sourceMappingURL=offline-write-flush.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-write-flush.d.ts","sourceRoot":"","sources":["../../src/worker/offline-write-flush.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,0BAA0B,CAAC;AAErE,MAAM,MAAM,0BAA0B,GAAG;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEhJ,0FAA0F;AAC1F,wBAAsB,kBAAkB,CACtC,KAAK,EAAE,oBAAoB,EAC3B,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,OAAO,KAAK,GACjB,OAAO,CAAC,0BAA0B,CAAC,CA6BrC"}
@@ -0,0 +1,32 @@
1
+ /** Explicit FIFO replay; it is never called by fetch, Sync, Push or worker activation. */
2
+ export async function flushOfflineWrites(store, binding, origin, send) {
3
+ const prepared = await store.prepareFlush(binding);
4
+ let sent = 0;
5
+ let retained = 0;
6
+ let failed = 0;
7
+ for (const write of prepared.writes) {
8
+ try {
9
+ const response = await send(new URL(write.path, origin), {
10
+ method: "POST",
11
+ credentials: "same-origin",
12
+ headers: { "content-type": "application/json", "idempotency-key": write.idempotencyKey },
13
+ body: write.bodyJson,
14
+ });
15
+ if (response.ok) {
16
+ await store.remove(write.idempotencyKey);
17
+ sent += 1;
18
+ }
19
+ else if (response.status >= 500)
20
+ retained += 1;
21
+ else {
22
+ await store.markFailed(write.idempotencyKey, response.status === 401 || response.status === 403 ? "authorization-required" : "rejected");
23
+ failed += 1;
24
+ }
25
+ }
26
+ catch {
27
+ retained += 1;
28
+ }
29
+ }
30
+ return { sent, retained, failed, purged: prepared.purged };
31
+ }
32
+ //# sourceMappingURL=offline-write-flush.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-write-flush.js","sourceRoot":"","sources":["../../src/worker/offline-write-flush.ts"],"names":[],"mappings":"AAIA,0FAA0F;AAC1F,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,KAA2B,EAC3B,OAAe,EACf,MAAc,EACd,IAAkB;IAElB,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;IACnD,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,IAAI,QAAQ,GAAG,CAAC,CAAC;IACjB,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;QACpC,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,EAAE;gBACvD,MAAM,EAAE,MAAM;gBACd,WAAW,EAAE,aAAa;gBAC1B,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,iBAAiB,EAAE,KAAK,CAAC,cAAc,EAAE;gBACxF,IAAI,EAAE,KAAK,CAAC,QAAQ;aACrB,CAAC,CAAC;YACH,IAAI,QAAQ,CAAC,EAAE,EAAE,CAAC;gBAChB,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC;gBACzC,IAAI,IAAI,CAAC,CAAC;YACZ,CAAC;iBAAM,IAAI,QAAQ,CAAC,MAAM,IAAI,GAAG;gBAAE,QAAQ,IAAI,CAAC,CAAC;iBAC5C,CAAC;gBACJ,MAAM,KAAK,CAAC,UAAU,CACpB,KAAK,CAAC,cAAc,EACpB,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,UAAU,CAC3F,CAAC;gBACF,MAAM,IAAI,CAAC,CAAC;YACd,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,QAAQ,IAAI,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC7D,CAAC"}
@@ -0,0 +1,36 @@
1
+ import type { PwaOfflineWriteMessage } from "../messages/index.js";
2
+ import type { PwaPlatformWorkerConfig } from "../shared/config.js";
3
+ type EnqueueMessage = Extract<PwaOfflineWriteMessage, {
4
+ readonly type: "pwa:offline-write:enqueue";
5
+ }>;
6
+ type FlushMessage = Extract<PwaOfflineWriteMessage, {
7
+ readonly type: "pwa:offline-write:flush";
8
+ }>;
9
+ export type PwaOfflineWriteIntent = {
10
+ readonly targetId: string;
11
+ readonly path: string;
12
+ /** Canonical JSON text; the original object is deliberately not retained beyond this boundary. */
13
+ readonly bodyJson: string;
14
+ readonly idempotencyKey: string;
15
+ readonly sessionBinding: string;
16
+ };
17
+ export type PwaOfflineWriteIntentResult = {
18
+ readonly ok: true;
19
+ readonly value: PwaOfflineWriteIntent;
20
+ } | {
21
+ readonly ok: false;
22
+ readonly code: string;
23
+ };
24
+ export type PwaOfflineWriteFlushValidationResult = {
25
+ readonly ok: true;
26
+ readonly binding: string;
27
+ } | {
28
+ readonly ok: false;
29
+ readonly code: string;
30
+ };
31
+ /** Validates and canonicalizes one explicit enqueue request before any persistent storage is opened. */
32
+ export declare function validateOfflineWriteEnqueue(message: EnqueueMessage, config: PwaPlatformWorkerConfig, origin: string): PwaOfflineWriteIntentResult;
33
+ /** Checks the current page's opaque binding before a later flush may inspect stored records. */
34
+ export declare function validateOfflineWriteFlush(message: FlushMessage, config: PwaPlatformWorkerConfig): PwaOfflineWriteFlushValidationResult;
35
+ export {};
36
+ //# sourceMappingURL=offline-write-intent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-write-intent.d.ts","sourceRoot":"","sources":["../../src/worker/offline-write-intent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AAEnE,KAAK,cAAc,GAAG,OAAO,CAAC,sBAAsB,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,2BAA2B,CAAA;CAAE,CAAC,CAAC;AACtG,KAAK,YAAY,GAAG,OAAO,CAAC,sBAAsB,EAAE;IAAE,QAAQ,CAAC,IAAI,EAAE,yBAAyB,CAAA;CAAE,CAAC,CAAC;AAElG,MAAM,MAAM,qBAAqB,GAAG;IAClC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC,CAAC;AAEF,MAAM,MAAM,2BAA2B,GACnC;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,qBAAqB,CAAA;CAAE,GAC5D;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAClD,MAAM,MAAM,oCAAoC,GAC5C;IAAE,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAAC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAKlD,wGAAwG;AACxG,wBAAgB,2BAA2B,CACzC,OAAO,EAAE,cAAc,EACvB,MAAM,EAAE,uBAAuB,EAC/B,MAAM,EAAE,MAAM,GACb,2BAA2B,CAiB7B;AAED,gGAAgG;AAChG,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,uBAAuB,GAAG,oCAAoC,CAItI"}
@@ -0,0 +1,84 @@
1
+ const OPAQUE_ASCII = /^[\x21-\x7e]{1,128}$/;
2
+ const SESSION_BINDING = /^[\x21-\x7e]{16,128}$/;
3
+ /** Validates and canonicalizes one explicit enqueue request before any persistent storage is opened. */
4
+ export function validateOfflineWriteEnqueue(message, config, origin) {
5
+ if (!config.offlineWrites.enabled)
6
+ return { ok: false, code: "offline-write.disabled" };
7
+ const { intent } = message;
8
+ const target = config.offlineWrites.targets.find((candidate) => candidate.id === intent.targetId);
9
+ if (target === undefined)
10
+ return { ok: false, code: "offline-write.target" };
11
+ if (!OPAQUE_ASCII.test(intent.idempotencyKey))
12
+ return { ok: false, code: "offline-write.idempotency-key" };
13
+ if (!SESSION_BINDING.test(intent.sessionBinding))
14
+ return { ok: false, code: "offline-write.session-binding" };
15
+ const path = canonicalSameOriginPath(intent.path, origin);
16
+ if (path === undefined || !isWithin(path, config.scope) || !isWithin(path, target.pathPrefix)) {
17
+ return { ok: false, code: "offline-write.path" };
18
+ }
19
+ const bodyJson = jsonText(intent.body);
20
+ if (bodyJson === undefined || new TextEncoder().encode(bodyJson).length > target.maxBodyBytes) {
21
+ return { ok: false, code: "offline-write.body" };
22
+ }
23
+ return { ok: true, value: { targetId: target.id, path, bodyJson, idempotencyKey: intent.idempotencyKey, sessionBinding: intent.sessionBinding } };
24
+ }
25
+ /** Checks the current page's opaque binding before a later flush may inspect stored records. */
26
+ export function validateOfflineWriteFlush(message, config) {
27
+ if (!config.offlineWrites.enabled)
28
+ return { ok: false, code: "offline-write.disabled" };
29
+ if (!SESSION_BINDING.test(message.sessionBinding))
30
+ return { ok: false, code: "offline-write.session-binding" };
31
+ return { ok: true, binding: message.sessionBinding };
32
+ }
33
+ function canonicalSameOriginPath(value, origin) {
34
+ if (!value.startsWith("/") || !URL.canParse(value, origin))
35
+ return undefined;
36
+ const url = new URL(value, origin);
37
+ return url.origin === origin && url.search === "" && url.hash === "" && url.pathname === value ? value : undefined;
38
+ }
39
+ function isWithin(path, prefix) {
40
+ if (prefix === "/")
41
+ return true;
42
+ const directory = prefix.endsWith("/") ? prefix : `${prefix}/`;
43
+ return path === prefix || path === directory.slice(0, -1) || path.startsWith(directory);
44
+ }
45
+ function jsonText(value) {
46
+ try {
47
+ if (!isJsonValue(value, new WeakSet()))
48
+ return undefined;
49
+ const text = JSON.stringify(value);
50
+ return text === undefined ? undefined : text;
51
+ }
52
+ catch {
53
+ return undefined;
54
+ }
55
+ }
56
+ function isJsonValue(value, seen) {
57
+ if (value === null || typeof value === "string" || typeof value === "boolean")
58
+ return true;
59
+ if (typeof value === "number")
60
+ return Number.isFinite(value);
61
+ if (typeof value !== "object" || seen.has(value))
62
+ return false;
63
+ seen.add(value);
64
+ if (Array.isArray(value)) {
65
+ for (let index = 0; index < value.length; index += 1) {
66
+ const descriptor = Object.getOwnPropertyDescriptor(value, String(index));
67
+ if (descriptor === undefined || !("value" in descriptor) || !isJsonValue(descriptor.value, seen))
68
+ return false;
69
+ }
70
+ return true;
71
+ }
72
+ const prototype = Object.getPrototypeOf(value);
73
+ if (prototype !== Object.prototype && prototype !== null)
74
+ return false;
75
+ for (const key of Reflect.ownKeys(value)) {
76
+ if (typeof key !== "string")
77
+ return false;
78
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
79
+ if (descriptor === undefined || !("value" in descriptor) || !isJsonValue(descriptor.value, seen))
80
+ return false;
81
+ }
82
+ return true;
83
+ }
84
+ //# sourceMappingURL=offline-write-intent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-write-intent.js","sourceRoot":"","sources":["../../src/worker/offline-write-intent.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ import type { PwaPlatformWorkerConfig } from "../shared/config.js";
2
+ import type { PwaOfflineWriteIntent } from "./offline-write-intent.js";
3
+ export type PwaOfflineWriteState = "pending" | "authorization-required" | "rejected";
4
+ export type PwaStoredOfflineWrite = PwaOfflineWriteIntent & {
5
+ readonly createdAt: number;
6
+ readonly bodyBytes: number;
7
+ readonly deliveryState: PwaOfflineWriteState;
8
+ };
9
+ export type PwaPreparedOfflineWrites = {
10
+ readonly writes: readonly PwaStoredOfflineWrite[];
11
+ readonly purged: number;
12
+ };
13
+ export type PwaOfflineWriteStore = {
14
+ enqueue(intent: PwaOfflineWriteIntent): Promise<"queued" | "existing">;
15
+ prepareFlush(binding: string): Promise<PwaPreparedOfflineWrites>;
16
+ remove(idempotencyKey: string): Promise<void>;
17
+ markFailed(idempotencyKey: string, state: Exclude<PwaOfflineWriteState, "pending">): Promise<void>;
18
+ clear(): Promise<void>;
19
+ };
20
+ /**
21
+ * The worker-owned IndexedDB queue. Every admission decision and write shares one readwrite transaction, so a
22
+ * concurrent enqueue cannot overrun a quota or turn one idempotency key into two different stored intents.
23
+ */
24
+ export declare function createOfflineWriteStore(config: Extract<PwaPlatformWorkerConfig["offlineWrites"], {
25
+ readonly enabled: true;
26
+ }>): PwaOfflineWriteStore;
27
+ //# sourceMappingURL=offline-write-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"offline-write-store.d.ts","sourceRoot":"","sources":["../../src/worker/offline-write-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,2BAA2B,CAAC;AAIvE,MAAM,MAAM,oBAAoB,GAAG,SAAS,GAAG,wBAAwB,GAAG,UAAU,CAAC;AACrF,MAAM,MAAM,qBAAqB,GAAG,qBAAqB,GAAG;IAC1D,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,oBAAoB,CAAC;CAC9C,CAAC;AACF,MAAM,MAAM,wBAAwB,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,SAAS,qBAAqB,EAAE,CAAC;IAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtH,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,CAAC,MAAM,EAAE,qBAAqB,GAAG,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,CAAC;IACvE,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC,CAAC;IACjE,MAAM,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,UAAU,CAAC,cAAc,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,CAAC,oBAAoB,EAAE,SAAS,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnG,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,uBAAuB,CAAC,MAAM,EAAE,OAAO,CAAC,uBAAuB,CAAC,eAAe,CAAC,EAAE;IAAE,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAA;CAAE,CAAC,GAAG,oBAAoB,CAkHnJ"}