tempest-react-sdk 0.81.0 → 0.83.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.
- package/README.md +9 -10
- package/bin/create-tempest-app.e2e.test.mjs +31 -0
- package/bin/create-tempest-app.mjs +11 -1
- package/bin/lib/doctor/doctor.e2e.test.mjs +29 -0
- package/bin/lib/doctor/vitest.mjs +120 -0
- package/bin/lib/doctor/vitest.test.mjs +139 -0
- package/bin/lib/fs/contain.mjs +7 -0
- package/bin/lib/openapi/escape.mjs +68 -4
- package/bin/lib/openapi/generate.hostile.test.mjs +228 -0
- package/bin/lib/openapi/generate.mjs +119 -15
- package/bin/lib/openapi/pattern-risk.mjs +134 -0
- package/bin/lib/openapi/pattern-risk.test.mjs +45 -0
- package/bin/tempest-gen.test.mjs +100 -0
- package/bin/tempest.mjs +24 -8
- package/dist/access/use-can.cjs +1 -1
- package/dist/access/use-can.cjs.map +1 -1
- package/dist/access/use-can.js +25 -28
- package/dist/access/use-can.js.map +1 -1
- package/dist/auth/create-tempest-auth.cjs +1 -1
- package/dist/auth/create-tempest-auth.cjs.map +1 -1
- package/dist/auth/create-tempest-auth.js +189 -143
- package/dist/auth/create-tempest-auth.js.map +1 -1
- package/dist/auth/session-identity.cjs +2 -0
- package/dist/auth/session-identity.cjs.map +1 -0
- package/dist/auth/session-identity.js +12 -0
- package/dist/auth/session-identity.js.map +1 -0
- package/dist/br/boleto.cjs.map +1 -1
- package/dist/br/boleto.js.map +1 -1
- package/dist/components/AuthForm/AuthLinks.cjs +1 -1
- package/dist/components/AuthForm/AuthLinks.cjs.map +1 -1
- package/dist/components/AuthForm/AuthLinks.js +18 -15
- package/dist/components/AuthForm/AuthLinks.js.map +1 -1
- package/dist/components/AuthPage/AuthPage.cjs +1 -1
- package/dist/components/AuthPage/AuthPage.cjs.map +1 -1
- package/dist/components/AuthPage/AuthPage.js +58 -52
- package/dist/components/AuthPage/AuthPage.js.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.cjs.map +1 -1
- package/dist/components/Breadcrumbs/Breadcrumbs.js +22 -21
- package/dist/components/Breadcrumbs/Breadcrumbs.js.map +1 -1
- package/dist/components/Button/Button.cjs +1 -1
- package/dist/components/Button/Button.cjs.map +1 -1
- package/dist/components/Button/Button.js +41 -40
- package/dist/components/Button/Button.js.map +1 -1
- package/dist/components/Chat/ChatAttachments.cjs +1 -1
- package/dist/components/Chat/ChatAttachments.cjs.map +1 -1
- package/dist/components/Chat/ChatAttachments.js +38 -38
- package/dist/components/Form/Form.cjs +1 -1
- package/dist/components/Form/Form.cjs.map +1 -1
- package/dist/components/Form/Form.js +30 -32
- package/dist/components/Form/Form.js.map +1 -1
- package/dist/components/Layout/Layout.cjs +1 -1
- package/dist/components/Layout/Layout.cjs.map +1 -1
- package/dist/components/Layout/Layout.js +25 -24
- package/dist/components/Layout/Layout.js.map +1 -1
- package/dist/components/Layout/gap.cjs +2 -0
- package/dist/components/Layout/gap.cjs.map +1 -0
- package/dist/components/Layout/gap.js +15 -0
- package/dist/components/Layout/gap.js.map +1 -0
- package/dist/components/NavigationMenu/NavigationMenu.cjs +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.cjs.map +1 -1
- package/dist/components/NavigationMenu/NavigationMenu.js +71 -70
- package/dist/components/NavigationMenu/NavigationMenu.js.map +1 -1
- package/dist/components/Sidebar/Sidebar.cjs.map +1 -1
- package/dist/components/Sidebar/Sidebar.js.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs +1 -1
- package/dist/components/Sidebar/SidebarEntry.cjs.map +1 -1
- package/dist/components/Sidebar/SidebarEntry.js +30 -29
- package/dist/components/Sidebar/SidebarEntry.js.map +1 -1
- package/dist/data/create-data-provider.cjs +1 -1
- package/dist/data/create-data-provider.cjs.map +1 -1
- package/dist/data/create-data-provider.js +1 -2
- package/dist/data/create-data-provider.js.map +1 -1
- package/dist/forms/validate-form.cjs +1 -1
- package/dist/forms/validate-form.cjs.map +1 -1
- package/dist/forms/validate-form.js +6 -1
- package/dist/forms/validate-form.js.map +1 -1
- package/dist/forms/zod-resolver.cjs +1 -1
- package/dist/forms/zod-resolver.cjs.map +1 -1
- package/dist/forms/zod-resolver.js +8 -3
- package/dist/forms/zod-resolver.js.map +1 -1
- package/dist/http/api-client.cjs +1 -1
- package/dist/http/api-client.cjs.map +1 -1
- package/dist/http/api-client.js +90 -72
- package/dist/http/api-client.js.map +1 -1
- package/dist/http/credential-scope.cjs +1 -1
- package/dist/http/credential-scope.cjs.map +1 -1
- package/dist/http/credential-scope.js +33 -9
- package/dist/http/credential-scope.js.map +1 -1
- package/dist/http/presigned-post.cjs.map +1 -1
- package/dist/http/presigned-post.js.map +1 -1
- package/dist/http/resumable-upload.cjs +1 -1
- package/dist/http/resumable-upload.cjs.map +1 -1
- package/dist/http/resumable-upload.js +167 -112
- package/dist/http/resumable-upload.js.map +1 -1
- package/dist/http/upload-with-progress.cjs +1 -1
- package/dist/http/upload-with-progress.cjs.map +1 -1
- package/dist/http/upload-with-progress.js +100 -44
- package/dist/http/upload-with-progress.js.map +1 -1
- package/dist/oauth/GoogleSignIn.cjs +1 -1
- package/dist/oauth/GoogleSignIn.cjs.map +1 -1
- package/dist/oauth/GoogleSignIn.js +5 -4
- package/dist/oauth/GoogleSignIn.js.map +1 -1
- package/dist/offline/create-offline-sync.cjs +1 -1
- package/dist/offline/create-offline-sync.cjs.map +1 -1
- package/dist/offline/create-offline-sync.js +87 -69
- package/dist/offline/create-offline-sync.js.map +1 -1
- package/dist/query/offline-persistence.cjs +1 -1
- package/dist/query/offline-persistence.cjs.map +1 -1
- package/dist/query/offline-persistence.js +67 -44
- package/dist/query/offline-persistence.js.map +1 -1
- package/dist/sse/create-event-stream.cjs +1 -1
- package/dist/sse/create-event-stream.cjs.map +1 -1
- package/dist/sse/create-event-stream.js +48 -41
- package/dist/sse/create-event-stream.js.map +1 -1
- package/dist/sse/ticket-event-stream.cjs +1 -1
- package/dist/sse/ticket-event-stream.cjs.map +1 -1
- package/dist/sse/ticket-event-stream.js +48 -19
- package/dist/sse/ticket-event-stream.js.map +1 -1
- package/dist/sse/use-event-stream.cjs +1 -1
- package/dist/sse/use-event-stream.cjs.map +1 -1
- package/dist/sse/use-event-stream.js +19 -14
- package/dist/sse/use-event-stream.js.map +1 -1
- package/dist/sw/background-sync.cjs +1 -1
- package/dist/sw/background-sync.cjs.map +1 -1
- package/dist/sw/background-sync.js +10 -8
- package/dist/sw/background-sync.js.map +1 -1
- package/dist/sw/cache.cjs +1 -1
- package/dist/sw/cache.cjs.map +1 -1
- package/dist/sw/cache.js +111 -55
- package/dist/sw/cache.js.map +1 -1
- package/dist/sw/create-push-handler.cjs +1 -1
- package/dist/sw/create-push-handler.cjs.map +1 -1
- package/dist/sw/create-push-handler.js +20 -10
- package/dist/sw/create-push-handler.js.map +1 -1
- package/dist/sw.d.ts +65 -14
- package/dist/tabular/compact.cjs +1 -1
- package/dist/tabular/compact.cjs.map +1 -1
- package/dist/tabular/compact.js +35 -16
- package/dist/tabular/compact.js.map +1 -1
- package/dist/tabular/manifest.cjs +1 -1
- package/dist/tabular/manifest.cjs.map +1 -1
- package/dist/tabular/manifest.js +33 -15
- package/dist/tabular/manifest.js.map +1 -1
- package/dist/tabular.d.ts +11 -1
- package/dist/tempest-react-sdk.cjs +1 -1
- package/dist/tempest-react-sdk.d.ts +543 -70
- package/dist/tempest-react-sdk.js +224 -224
- package/dist/theme/initial-theme.cjs +4 -4
- package/dist/theme/initial-theme.cjs.map +1 -1
- package/dist/theme/initial-theme.js +8 -5
- package/dist/theme/initial-theme.js.map +1 -1
- package/dist/utils/compressed-storage.cjs +1 -1
- package/dist/utils/compressed-storage.cjs.map +1 -1
- package/dist/utils/compressed-storage.js +13 -7
- package/dist/utils/compressed-storage.js.map +1 -1
- package/dist/utils/objects.cjs +1 -1
- package/dist/utils/objects.cjs.map +1 -1
- package/dist/utils/objects.js +13 -7
- package/dist/utils/objects.js.map +1 -1
- package/dist/utils/safe-url.cjs +1 -1
- package/dist/utils/safe-url.cjs.map +1 -1
- package/dist/utils/safe-url.js +4 -1
- package/dist/utils/safe-url.js.map +1 -1
- package/dist/vite/base-url.cjs +1 -1
- package/dist/vite/base-url.cjs.map +1 -1
- package/dist/vite/base-url.js +5 -1
- package/dist/vite/base-url.js.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs +1 -1
- package/dist/vite/tempest-pwa-dev-sw.cjs.map +1 -1
- package/dist/vite/tempest-pwa-dev-sw.js +96 -31
- package/dist/vite/tempest-pwa-dev-sw.js.map +1 -1
- package/dist/vite/tempest-pwa-manifest.cjs +2 -2
- package/dist/vite/tempest-pwa-manifest.cjs.map +1 -1
- package/dist/vite/tempest-pwa-manifest.js +32 -16
- package/dist/vite/tempest-pwa-manifest.js.map +1 -1
- package/dist/vite.cjs +1 -1
- package/dist/vite.d.ts +63 -12
- package/dist/vite.js +4 -4
- package/dist/webrtc/peer-mesh.cjs +1 -1
- package/dist/webrtc/peer-mesh.cjs.map +1 -1
- package/dist/webrtc/peer-mesh.js +7 -1
- package/dist/webrtc/peer-mesh.js.map +1 -1
- package/dist/ws/create-web-socket.cjs +1 -1
- package/dist/ws/create-web-socket.cjs.map +1 -1
- package/dist/ws/create-web-socket.js +77 -61
- package/dist/ws/create-web-socket.js.map +1 -1
- package/dist/ws/use-web-socket.cjs +1 -1
- package/dist/ws/use-web-socket.cjs.map +1 -1
- package/dist/ws/use-web-socket.js +4 -3
- package/dist/ws/use-web-socket.js.map +1 -1
- package/package.json +1 -1
- package/template-pwa/src/pwa.tsx +42 -0
- package/template-pwa/src/sw.ts +5 -4
package/dist/sw/cache.js
CHANGED
|
@@ -3,7 +3,7 @@ import { credentialHeaderSet as e } from "./credential-headers.js";
|
|
|
3
3
|
function t() {
|
|
4
4
|
return globalThis;
|
|
5
5
|
}
|
|
6
|
-
var n =
|
|
6
|
+
var n = /* @__PURE__ */ new WeakSet(), r = "/__tempest-precache-state__", i = "x-tempest-cached-at";
|
|
7
7
|
function a(e, t) {
|
|
8
8
|
if (!t) return !1;
|
|
9
9
|
let n = Number(e.headers.get(i)), r = e.headers.get("date"), a = n > 0 ? n : r ? new Date(r).getTime() : NaN;
|
|
@@ -12,43 +12,49 @@ function a(e, t) {
|
|
|
12
12
|
function o(e) {
|
|
13
13
|
return (e.headers.get("cache-control") ?? "").toLowerCase().split(",").map((e) => e.trim().split("=")[0]);
|
|
14
14
|
}
|
|
15
|
-
function s(e
|
|
16
|
-
|
|
15
|
+
function s(e) {
|
|
16
|
+
return (e ?? "").toLowerCase().split(",").map((e) => e.trim());
|
|
17
|
+
}
|
|
18
|
+
function c(e, t) {
|
|
19
|
+
if (!e.ok || s(e.headers.get("vary")).includes("*")) return !1;
|
|
17
20
|
let n = o(e);
|
|
18
21
|
return n.includes("no-store") ? !1 : t.cacheAuthenticated === !0 || !n.includes("private");
|
|
19
22
|
}
|
|
20
|
-
function
|
|
23
|
+
function l(e, t) {
|
|
24
|
+
return e.mode === "cors" && e.credentials === "include" || u(e, t).length > 0;
|
|
25
|
+
}
|
|
26
|
+
function u(t, n) {
|
|
21
27
|
return [...e(n.credentialHeaders)].filter((e) => t.headers.has(e));
|
|
22
28
|
}
|
|
23
|
-
async function
|
|
24
|
-
if (!
|
|
29
|
+
async function d(e, t, n, r) {
|
|
30
|
+
if (!c(n, r)) return;
|
|
25
31
|
let a = new Headers(n.headers);
|
|
26
32
|
if (a.set(i, String(Date.now())), r.cacheAuthenticated) {
|
|
27
|
-
let e = a.get("vary"), n = (e
|
|
28
|
-
i.length > 0 &&
|
|
33
|
+
let e = a.get("vary"), n = s(e), i = u(t, r).filter((e) => !n.includes(e));
|
|
34
|
+
i.length > 0 && a.set("vary", [e, ...i].filter(Boolean).join(", "));
|
|
29
35
|
}
|
|
30
36
|
let o = n.clone();
|
|
31
37
|
await e.put(t, new Response(o.body, {
|
|
32
38
|
status: o.status,
|
|
33
39
|
statusText: o.statusText,
|
|
34
40
|
headers: a
|
|
35
|
-
})), await
|
|
41
|
+
})), await f(r.cacheName, r.maxEntries);
|
|
36
42
|
}
|
|
37
|
-
async function
|
|
43
|
+
async function f(e, t) {
|
|
38
44
|
if (!t) return;
|
|
39
45
|
let n = await caches.open(e), r = await n.keys();
|
|
40
46
|
if (!(r.length <= t)) for (let e of r.slice(0, r.length - t)) await n.delete(e);
|
|
41
47
|
}
|
|
42
|
-
async function
|
|
48
|
+
async function p(e, t) {
|
|
43
49
|
let n = await caches.open(t.cacheName), r = await n.match(e);
|
|
44
50
|
if (r && !a(r, t.maxAgeSeconds)) return r;
|
|
45
51
|
let i = await fetch(e);
|
|
46
|
-
return await
|
|
52
|
+
return await d(n, e, i, t), i;
|
|
47
53
|
}
|
|
48
|
-
async function
|
|
54
|
+
async function m(e, t, n) {
|
|
49
55
|
let r = await caches.open(t.cacheName), i = (async () => {
|
|
50
56
|
let n = await fetch(e);
|
|
51
|
-
return await
|
|
57
|
+
return await d(r, e, n, t), n;
|
|
52
58
|
})(), o = (t.networkTimeoutSeconds ?? 0) * 1e3;
|
|
53
59
|
try {
|
|
54
60
|
if (o > 0) {
|
|
@@ -64,24 +70,24 @@ async function f(e, t, n) {
|
|
|
64
70
|
throw Error("network-first: no network and no cache");
|
|
65
71
|
}
|
|
66
72
|
}
|
|
67
|
-
async function
|
|
68
|
-
let r = await caches.open(t.cacheName), i = await r.match(e), o = fetch(e).then(async (n) => (await
|
|
73
|
+
async function h(e, t, n) {
|
|
74
|
+
let r = await caches.open(t.cacheName), i = await r.match(e), o = fetch(e).then(async (n) => (await d(r, e, n, t), n)).catch(() => void 0), s = i && !a(i, t.maxAgeSeconds) ? i : void 0;
|
|
69
75
|
if (s) return n(o), s;
|
|
70
76
|
let c = await o;
|
|
71
77
|
if (c) return c;
|
|
72
78
|
throw Error("stale-while-revalidate: no network and no cache");
|
|
73
79
|
}
|
|
74
|
-
function
|
|
80
|
+
function g(e, t, n) {
|
|
75
81
|
switch (t.strategy) {
|
|
76
|
-
case "cache-first": return
|
|
77
|
-
case "network-first": return
|
|
78
|
-
case "stale-while-revalidate": return
|
|
82
|
+
case "cache-first": return p(e, t);
|
|
83
|
+
case "network-first": return m(e, t, n);
|
|
84
|
+
case "stale-while-revalidate": return h(e, t, n);
|
|
79
85
|
}
|
|
80
86
|
}
|
|
81
|
-
function
|
|
87
|
+
function _(e, t, n) {
|
|
82
88
|
return typeof e.match == "function" ? e.match(t, n) : e.match.test(t.href);
|
|
83
89
|
}
|
|
84
|
-
async function
|
|
90
|
+
async function v(e, t) {
|
|
85
91
|
let n = e.headers.get("range");
|
|
86
92
|
if (!n) return t;
|
|
87
93
|
let r = /^bytes=(\d*)-(\d*)$/.exec(n.trim());
|
|
@@ -106,62 +112,112 @@ async function g(e, t) {
|
|
|
106
112
|
headers: l
|
|
107
113
|
});
|
|
108
114
|
}
|
|
109
|
-
function
|
|
115
|
+
function y(e) {
|
|
110
116
|
t().addEventListener("fetch", (t) => {
|
|
111
|
-
let
|
|
112
|
-
if (
|
|
113
|
-
let
|
|
114
|
-
if (!
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
117
|
+
let r = t.request;
|
|
118
|
+
if (r.method !== "GET") return;
|
|
119
|
+
let i = new URL(r.url), a = e.find((e) => _(e, i, r));
|
|
120
|
+
if (!a || !a.cacheAuthenticated && l(r, a)) return;
|
|
121
|
+
n.add(t);
|
|
122
|
+
let o = (e) => t.waitUntil(e);
|
|
123
|
+
if (a.rangeRequests && r.headers.has("range")) {
|
|
124
|
+
let e = new Request(r.url, {
|
|
125
|
+
headers: b(r.headers),
|
|
126
|
+
credentials: r.credentials,
|
|
127
|
+
mode: r.mode === "navigate" ? "same-origin" : r.mode
|
|
121
128
|
});
|
|
122
|
-
t.respondWith(
|
|
123
|
-
} else t.respondWith(
|
|
129
|
+
t.respondWith(g(e, a, o).then((e) => v(r, e)));
|
|
130
|
+
} else t.respondWith(g(r, a, o));
|
|
124
131
|
});
|
|
125
132
|
}
|
|
126
|
-
function
|
|
133
|
+
function b(e) {
|
|
127
134
|
let t = new Headers(e);
|
|
128
135
|
return t.delete("range"), t;
|
|
129
136
|
}
|
|
130
|
-
function
|
|
131
|
-
let
|
|
132
|
-
|
|
137
|
+
async function x(e) {
|
|
138
|
+
let t = await e.match(r);
|
|
139
|
+
if (t) try {
|
|
140
|
+
return await t.json();
|
|
141
|
+
} catch {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
async function S(e, t) {
|
|
146
|
+
await e.put(r, new Response(JSON.stringify(t), { headers: { "content-type": "application/json" } }));
|
|
147
|
+
}
|
|
148
|
+
async function C(e, t) {
|
|
149
|
+
let n;
|
|
150
|
+
for (let r of await caches.keys()) {
|
|
151
|
+
if (!r.startsWith(`${e}-`)) continue;
|
|
152
|
+
let i = await x(await caches.open(r)), a = i ? t(i) : void 0;
|
|
153
|
+
a !== void 0 && (!n || a > n.at) && (n = {
|
|
154
|
+
name: r,
|
|
155
|
+
at: a
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
return n?.name;
|
|
159
|
+
}
|
|
160
|
+
async function w(e) {
|
|
161
|
+
let t = await (await caches.open(e)).keys();
|
|
162
|
+
return {
|
|
163
|
+
name: e,
|
|
164
|
+
paths: new Set(t.map((e) => new URL(e.url).pathname).filter((e) => e !== r))
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
async function T(e, t) {
|
|
168
|
+
return await (await caches.open(e)).match(t) ?? fetch(t);
|
|
169
|
+
}
|
|
170
|
+
function E(e = {}) {
|
|
171
|
+
let r = t(), { manifestUrl: i = "/precache-manifest.json", cacheName: a = "tempest-precache", navigateFallback: o = "/index.html", navigateFallbackDenylist: s = [], skipWaiting: c = !0, navigationPreload: l = !0 } = e, u, d, f = !1, p, m = () => f ? Promise.resolve(d) : (p ??= (async () => {
|
|
172
|
+
let e = await C(a, (e) => e.activatedAt), t = e ? await w(e) : void 0;
|
|
173
|
+
return f ||= (d = t, !0), d;
|
|
174
|
+
})().finally(() => {
|
|
175
|
+
p = void 0;
|
|
176
|
+
}), p);
|
|
177
|
+
r.addEventListener("install", (e) => {
|
|
133
178
|
e.waitUntil((async () => {
|
|
134
|
-
let e = await (await fetch(
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
179
|
+
let e = await (await fetch(i, { cache: "no-cache" })).json(), t = `${a}-${e.version}`, n = await caches.open(t);
|
|
180
|
+
await n.addAll(e.urls), await S(n, {
|
|
181
|
+
...await x(n),
|
|
182
|
+
installedAt: Date.now()
|
|
183
|
+
}), u = t, c && await r.skipWaiting();
|
|
138
184
|
})());
|
|
139
|
-
}),
|
|
185
|
+
}), r.addEventListener("activate", (e) => {
|
|
140
186
|
e.waitUntil((async () => {
|
|
141
|
-
let e = await
|
|
142
|
-
|
|
187
|
+
let e = u ?? await C(a, (e) => e.installedAt > (e.activatedAt ?? -Infinity) ? e.installedAt : void 0);
|
|
188
|
+
if (e) {
|
|
189
|
+
let t = await caches.open(e), n = await x(t);
|
|
190
|
+
await S(t, {
|
|
191
|
+
installedAt: Date.now(),
|
|
192
|
+
...n,
|
|
193
|
+
activatedAt: Date.now()
|
|
194
|
+
}), d = await w(e), f = !0;
|
|
195
|
+
let r = await caches.keys();
|
|
196
|
+
await Promise.all(r.filter((t) => t.startsWith(`${a}-`) && t !== e).map((e) => caches.delete(e)));
|
|
197
|
+
}
|
|
198
|
+
l && r.registration?.navigationPreload && await r.registration.navigationPreload.enable(), await r.clients.claim();
|
|
143
199
|
})());
|
|
144
|
-
}),
|
|
200
|
+
}), r.addEventListener("fetch", (e) => {
|
|
145
201
|
let t = e.request;
|
|
146
|
-
if (t.method !== "GET") return;
|
|
147
|
-
let
|
|
148
|
-
if (
|
|
202
|
+
if (t.method !== "GET" || n.has(e)) return;
|
|
203
|
+
let i = new URL(t.url);
|
|
204
|
+
if (i.origin === r.location.origin) {
|
|
149
205
|
if (t.mode === "navigate") {
|
|
150
|
-
if (
|
|
206
|
+
if (s.some((e) => e.test(i.pathname))) return;
|
|
151
207
|
e.respondWith((async () => {
|
|
152
208
|
try {
|
|
153
209
|
return await e.preloadResponse || await fetch(t);
|
|
154
210
|
} catch {
|
|
155
|
-
let e = await (await caches.open(
|
|
156
|
-
if (
|
|
211
|
+
let e = await m(), t = e ? await (await caches.open(e.name)).match(o) : void 0;
|
|
212
|
+
if (t) return t;
|
|
157
213
|
throw Error("offline and no cached app shell");
|
|
158
214
|
}
|
|
159
215
|
})());
|
|
160
|
-
} else
|
|
216
|
+
} else f ? d?.paths.has(i.pathname) && e.respondWith(T(d.name, t)) : e.respondWith(m().catch(() => void 0).then((e) => e?.paths.has(i.pathname) ? T(e.name, t) : fetch(t)));
|
|
161
217
|
}
|
|
162
218
|
});
|
|
163
219
|
}
|
|
164
220
|
//#endregion
|
|
165
|
-
export {
|
|
221
|
+
export { v as createPartialResponse, E as installPrecache, y as installRuntimeCache };
|
|
166
222
|
|
|
167
223
|
//# sourceMappingURL=cache.js.map
|
package/dist/sw/cache.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.js","names":["getSwScope","precacheName","precachedPaths","CACHED_AT_HEADER","isExpired","maxAgeSeconds","stamp","response","dateHeader","storedAt","cacheControlDirectives","directive","isStorable","directives","route","credentialsOn","credentialHeaderSet","request","name","store","headers","vary","varies","missing","copy","cache","trimCache","maxEntries","cacheName","keys","key","cacheFirst","cached","networkFirst","network","timeoutMs","timeout","extend","reject","staleWhileRevalidate","fresh","runRoute","routeMatches","url","createPartialResponse","rangeHeader","match","buffer","total","start","end","suffix","slice","installRuntimeCache","event","routes","candidate","promise","full","stripRange","out","installPrecache","sw","options","manifest","manifestUrl","skipWaiting","navigationPreload","navigateFallbackDenylist","re","shell","navigateFallback"],"sources":["../../src/sw/cache.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — precache, runtime strategies (cache-first, network-\n * first, stale-while-revalidate), expiry and the inspect/clear helpers all address\n * the same Cache Storage namespace, and the naming scheme that keeps them from\n * evicting each other is the file's whole contract.\n */\n/**\n * Service-worker caching helpers — a small, dependency-free subset of what\n * Workbox provides: precaching of the build's app shell (so the app launches\n * offline) plus runtime caching strategies for fonts, APIs and images.\n *\n * Import these inside your own `sw.ts`. They run in the service-worker global\n * scope, not the main thread. Pair `installPrecache` with the\n * `tempestPwaManifest()` Vite plugin (from `tempest-react-sdk/vite`), which\n * emits the `precache-manifest.json` this reads at install time.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installRuntimeCache, installPrecache } from \"tempest-react-sdk/sw\";\n *\n * // Register specific routes FIRST so they win over the precache catch-all.\n * installRuntimeCache([\n * { match: /\\/api\\//, strategy: \"network-first\", cacheName: \"tempest-api\", maxAgeSeconds: 300 },\n * ]);\n * installPrecache();\n */\n\nimport { credentialHeaderSet } from \"./credential-headers\";\n\n/** Minimal shape of the events we touch, to avoid pulling in `lib.webworker`. */\ninterface ExtendableEventLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\ninterface FetchEventLike extends ExtendableEventLike {\n request: Request;\n respondWith(response: Response | Promise<Response>): void;\n /** Resolves to the navigation-preload response when it is enabled, else `undefined`. */\n preloadResponse?: Promise<Response | undefined>;\n}\n\n/** Minimal shape of `registration.navigationPreload` (Navigation Preload API). */\ninterface NavigationPreloadManagerLike {\n enable(): Promise<void>;\n disable(): Promise<void>;\n}\n\ninterface CacheSwGlobal {\n addEventListener(type: \"install\", listener: (event: ExtendableEventLike) => void): void;\n addEventListener(type: \"activate\", listener: (event: ExtendableEventLike) => void): void;\n addEventListener(type: \"fetch\", listener: (event: FetchEventLike) => void): void;\n clients: { claim(): Promise<void> };\n skipWaiting(): Promise<void>;\n location: { origin: string };\n registration?: { navigationPreload?: NavigationPreloadManagerLike };\n}\n\nfunction getSwScope(): CacheSwGlobal {\n return globalThis as unknown as CacheSwGlobal;\n}\n\n/** Caching strategy for a runtime route. Mirrors the common Workbox trio. */\nexport type RuntimeStrategy = \"cache-first\" | \"network-first\" | \"stale-while-revalidate\";\n\n/** A single runtime-caching rule, matched against each `GET` request. */\nexport interface RuntimeRoute {\n /** A `RegExp` tested against the full URL, or a predicate over the parsed URL. */\n match: RegExp | ((url: URL, request: Request) => boolean);\n /** How to resolve a match. */\n strategy: RuntimeStrategy;\n /** Cache bucket name for this route. */\n cacheName: string;\n /** Trim the cache to at most this many entries (FIFO) after each write. */\n maxEntries?: number;\n /** Treat a cached response older than this (seconds) as a miss. */\n maxAgeSeconds?: number;\n /** For `network-first`: fall back to cache after this timeout (seconds). */\n networkTimeoutSeconds?: number;\n /**\n * Serve HTTP `Range` requests (206 Partial Content) by slicing the cached\n * full response. Enable for audio/video so seeking works offline. The full\n * resource is cached once (the `Range` header is stripped before caching).\n */\n rangeRequests?: boolean;\n /**\n * Opt in to caching requests that carry a credential header (see\n * `credentialHeaders`), and responses marked `Cache-Control: private`.\n * Default `false`.\n *\n * Cache Storage is keyed by URL and outlives the session: without this\n * gate, a `GET /api/me` stored for one account was served to the next\n * account on the same browser whenever the network failed or exceeded\n * `networkTimeoutSeconds` (reproduced in Chromium 153, 11/10/2026). With\n * the default, credentialed requests bypass the route entirely and reach\n * the network as if no service worker were installed.\n *\n * When enabled, every stored entry gets `Vary` on each credential header the\n * request carried, so the Cache API only matches it for the same credential —\n * another account (or no account) misses instead of reading it. A token\n * rotated while online simply repopulates the entry. Pair it with\n * `clearSessionData()` on logout, which removes the entries themselves.\n * `Cache-Control: no-store` is never cached, opt-in or not.\n *\n * A cookie session is invisible here: the worker cannot see `Cookie`, and\n * gating on `request.credentials` would switch off caching for every\n * same-origin image and font. A cookie-authenticated API stays private only\n * through `Cache-Control: private`/`no-store` on its responses and\n * `clearSessionData()` on logout.\n */\n cacheAuthenticated?: boolean;\n /**\n * Header names that mark a request as credentialed. Default\n * `DEFAULT_CREDENTIAL_HEADERS` (`authorization`, `x-api-key`,\n * `x-auth-token`, …) — the same list the background-sync queue strips.\n */\n credentialHeaders?: readonly string[];\n}\n\n/** Options for {@link installPrecache}. */\nexport interface InstallPrecacheOptions {\n /** URL of the manifest emitted by `tempestPwaManifest()`. Default `/precache-manifest.json`. */\n manifestUrl?: string;\n /** Cache name prefix; the manifest `version` is appended. Default `tempest-precache`. */\n cacheName?: string;\n /** App-shell document served for navigation requests offline. Default `/index.html`. */\n navigateFallback?: string;\n /** Navigation paths that should NOT use the fallback (e.g. `[/^\\/api\\//]`). */\n navigateFallbackDenylist?: RegExp[];\n /** Activate the new worker immediately after precaching. Default `true`. */\n skipWaiting?: boolean;\n /**\n * Enable the Navigation Preload API on `activate`, so the browser fetches\n * the navigation request in parallel with the worker boot and the handler\n * serves `event.preloadResponse` — cutting first-navigation latency after\n * the worker starts. Silently ignored where unsupported. Default `true`.\n */\n navigationPreload?: boolean;\n}\n\ninterface PrecacheManifest {\n version: string;\n urls: string[];\n}\n\nlet precacheName = \"\";\nconst precachedPaths = new Set<string>();\n\n/**\n * Header stamped on every runtime-cache entry with the epoch milliseconds of the\n * write. `Date` alone is not enough: it is not a CORS-safelisted response\n * header, so for a cross-origin API the worker reads `null` and `maxAgeSeconds`\n * would never expire anything.\n */\nconst CACHED_AT_HEADER = \"x-tempest-cached-at\";\n\n/**\n * Whether a cached response has aged past `maxAgeSeconds`. Reads the\n * {@link CACHED_AT_HEADER} stamp written by the runtime cache, and falls back to\n * `Date` for entries written before the stamp existed.\n */\nfunction isExpired(response: Response, maxAgeSeconds?: number): boolean {\n if (!maxAgeSeconds) return false;\n const stamp = Number(response.headers.get(CACHED_AT_HEADER));\n const dateHeader = response.headers.get(\"date\");\n const storedAt = stamp > 0 ? stamp : dateHeader ? new Date(dateHeader).getTime() : NaN;\n if (Number.isNaN(storedAt)) return false;\n return (Date.now() - storedAt) / 1000 > maxAgeSeconds;\n}\n\n/** The `Cache-Control` directives of a response, lower-cased. */\nfunction cacheControlDirectives(response: Response): string[] {\n return (response.headers.get(\"cache-control\") ?? \"\")\n .toLowerCase()\n .split(\",\")\n .map((directive) => directive.trim().split(\"=\")[0]);\n}\n\n/**\n * Whether a network response may be written to the route's cache: it must be\n * `ok`, never `no-store`, and `private` only under `cacheAuthenticated`.\n */\nfunction isStorable(response: Response, route: RuntimeRoute): boolean {\n if (!response.ok) return false;\n const directives = cacheControlDirectives(response);\n if (directives.includes(\"no-store\")) return false;\n return route.cacheAuthenticated === true || !directives.includes(\"private\");\n}\n\n/** The credential headers, lower-cased, that a request carries for its route. */\nfunction credentialsOn(request: Request, route: RuntimeRoute): string[] {\n return [...credentialHeaderSet(route.credentialHeaders)].filter((name) =>\n request.headers.has(name),\n );\n}\n\n/**\n * Write a response to the route's cache with the {@link CACHED_AT_HEADER} stamp\n * and, under `cacheAuthenticated`, `Vary` on each credential header the request\n * carried, so the entry only matches the credential that produced it. Skips\n * responses that {@link isStorable} rejects, then trims the cache to\n * `maxEntries`.\n */\nasync function store(\n cache: Cache,\n request: Request,\n response: Response,\n route: RuntimeRoute,\n): Promise<void> {\n if (!isStorable(response, route)) return;\n const headers = new Headers(response.headers);\n headers.set(CACHED_AT_HEADER, String(Date.now()));\n if (route.cacheAuthenticated) {\n const vary = headers.get(\"vary\");\n const varies = (vary ?? \"\")\n .toLowerCase()\n .split(\",\")\n .map((name) => name.trim());\n const missing = credentialsOn(request, route).filter((name) => !varies.includes(name));\n if (missing.length > 0 && !varies.includes(\"*\")) {\n headers.set(\"vary\", [vary, ...missing].filter(Boolean).join(\", \"));\n }\n }\n const copy = response.clone();\n await cache.put(\n request,\n new Response(copy.body, { status: copy.status, statusText: copy.statusText, headers }),\n );\n await trimCache(route.cacheName, route.maxEntries);\n}\n\n/** Drop oldest entries until the cache holds at most `maxEntries`. */\nasync function trimCache(cacheName: string, maxEntries?: number): Promise<void> {\n if (!maxEntries) return;\n const cache = await caches.open(cacheName);\n const keys = await cache.keys();\n if (keys.length <= maxEntries) return;\n for (const key of keys.slice(0, keys.length - maxEntries)) {\n await cache.delete(key);\n }\n}\n\nasync function cacheFirst(request: Request, route: RuntimeRoute): Promise<Response> {\n const cache = await caches.open(route.cacheName);\n const cached = await cache.match(request);\n if (cached && !isExpired(cached, route.maxAgeSeconds)) return cached;\n\n const response = await fetch(request);\n await store(cache, request, response, route);\n return response;\n}\n\n/**\n * Network-first. When the timeout wins, the network request keeps running to\n * refresh the cache, and is handed to `extend` (`event.waitUntil`) so the\n * worker is not stopped mid-write.\n */\nasync function networkFirst(\n request: Request,\n route: RuntimeRoute,\n extend: (promise: Promise<unknown>) => void,\n): Promise<Response> {\n const cache = await caches.open(route.cacheName);\n\n const network = (async (): Promise<Response> => {\n const response = await fetch(request);\n await store(cache, request, response, route);\n return response;\n })();\n\n const timeoutMs = (route.networkTimeoutSeconds ?? 0) * 1000;\n try {\n if (timeoutMs > 0) {\n const timeout = new Promise<never>((_, reject) =>\n setTimeout(() => {\n extend(network.catch(() => undefined));\n reject(new Error(\"network-timeout\"));\n }, timeoutMs),\n );\n return await Promise.race([network, timeout]);\n }\n return await network;\n } catch {\n const cached = await cache.match(request);\n if (cached && !isExpired(cached, route.maxAgeSeconds)) return cached;\n throw new Error(\"network-first: no network and no cache\");\n }\n}\n\n/**\n * Stale-while-revalidate. A fresh cached response is served at once, and the\n * revalidation is handed to `extend` (`event.waitUntil`): without it the\n * browser may stop the worker before the refreshed response is written.\n */\nasync function staleWhileRevalidate(\n request: Request,\n route: RuntimeRoute,\n extend: (promise: Promise<unknown>) => void,\n): Promise<Response> {\n const cache = await caches.open(route.cacheName);\n const cached = await cache.match(request);\n\n const network = fetch(request)\n .then(async (response) => {\n await store(cache, request, response, route);\n return response;\n })\n .catch(() => undefined);\n\n const fresh = cached && !isExpired(cached, route.maxAgeSeconds) ? cached : undefined;\n if (fresh) {\n extend(network);\n return fresh;\n }\n const response = await network;\n if (response) return response;\n throw new Error(\"stale-while-revalidate: no network and no cache\");\n}\n\nfunction runRoute(\n request: Request,\n route: RuntimeRoute,\n extend: (promise: Promise<unknown>) => void,\n): Promise<Response> {\n switch (route.strategy) {\n case \"cache-first\":\n return cacheFirst(request, route);\n case \"network-first\":\n return networkFirst(request, route, extend);\n case \"stale-while-revalidate\":\n return staleWhileRevalidate(request, route, extend);\n }\n}\n\nfunction routeMatches(route: RuntimeRoute, url: URL, request: Request): boolean {\n return typeof route.match === \"function\"\n ? route.match(url, request)\n : route.match.test(url.href);\n}\n\n/**\n * Build a `206 Partial Content` response from a full one for an HTTP `Range`\n * request. Supports `bytes=start-end`, open-ended `bytes=start-` and suffix\n * `bytes=-suffixLength` (the last N bytes). Returns the original response when there is no usable\n * `Range` header, or a `416` when the range is unsatisfiable.\n *\n * @param request The incoming request (its `Range` header drives the slice).\n * @param response The full (200) response to slice.\n */\nexport async function createPartialResponse(\n request: Request,\n response: Response,\n): Promise<Response> {\n const rangeHeader = request.headers.get(\"range\");\n if (!rangeHeader) return response;\n\n const match = /^bytes=(\\d*)-(\\d*)$/.exec(rangeHeader.trim());\n if (!match || (match[1] === \"\" && match[2] === \"\")) {\n return new Response(null, { status: 416, statusText: \"Range Not Satisfiable\" });\n }\n\n const buffer = await response.clone().arrayBuffer();\n const total = buffer.byteLength;\n\n let start: number;\n let end: number;\n if (match[1] === \"\") {\n const suffix = Number(match[2]);\n start = Math.max(0, total - suffix);\n end = total - 1;\n } else {\n start = Number(match[1]);\n end = match[2] === \"\" ? total - 1 : Math.min(Number(match[2]), total - 1);\n }\n\n if (start > end || start >= total) {\n return new Response(null, {\n status: 416,\n statusText: \"Range Not Satisfiable\",\n headers: { \"Content-Range\": `bytes */${total}` },\n });\n }\n\n const slice = buffer.slice(start, end + 1);\n const headers = new Headers(response.headers);\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${total}`);\n headers.set(\"Content-Length\", String(slice.byteLength));\n headers.set(\"Accept-Ranges\", \"bytes\");\n\n return new Response(slice, {\n status: 206,\n statusText: \"Partial Content\",\n headers,\n });\n}\n\n/**\n * Install a `fetch` handler that resolves matching `GET` requests with the\n * given runtime strategies. Non-matching requests are left untouched (no\n * `respondWith`), so a later {@link installPrecache} can handle them.\n *\n * A request carrying a credential header (`Authorization`, `X-Api-Key`, … — the\n * route's `credentialHeaders`) is also left untouched unless its route sets\n * `cacheAuthenticated`, and a response with `Cache-Control: no-store`\n * (or `private`, without that opt-in) is served but never stored. `maxAgeSeconds`\n * applies to every read, including the offline fallback: an expired entry is a\n * miss, never a last resort. Under `rangeRequests`, the full resource is cached\n * once (the `Range` header stripped) and each range is sliced from it.\n *\n * Register this BEFORE `installPrecache` so specific routes win over the\n * precache catch-all.\n *\n * @param routes Ordered rules; the first whose `match` passes handles the request.\n */\nexport function installRuntimeCache(routes: RuntimeRoute[]): void {\n const sw = getSwScope();\n sw.addEventListener(\"fetch\", (event) => {\n const request = event.request;\n if (request.method !== \"GET\") return;\n\n const url = new URL(request.url);\n const route = routes.find((candidate) => routeMatches(candidate, url, request));\n if (!route) return;\n if (!route.cacheAuthenticated && credentialsOn(request, route).length > 0) return;\n const extend = (promise: Promise<unknown>): void => event.waitUntil(promise);\n\n if (route.rangeRequests && request.headers.has(\"range\")) {\n const full = new Request(request.url, {\n headers: stripRange(request.headers),\n credentials: request.credentials,\n mode: request.mode === \"navigate\" ? \"same-origin\" : request.mode,\n });\n event.respondWith(\n runRoute(full, route, extend).then((response) =>\n createPartialResponse(request, response),\n ),\n );\n return;\n }\n\n event.respondWith(runRoute(request, route, extend));\n });\n}\n\n/** Copy headers without the `Range` header (so the cached entry is the full file). */\nfunction stripRange(headers: Headers): Headers {\n const out = new Headers(headers);\n out.delete(\"range\");\n return out;\n}\n\n/**\n * Precache the app shell at `install` and serve it offline:\n * - reads `precache-manifest.json` (emitted by `tempestPwaManifest()`),\n * - caches every listed URL under a versioned cache,\n * - on `activate`, deletes stale precache versions and claims open clients,\n * - on `fetch`, serves precached assets cache-first and falls back to the\n * `navigateFallback` document for offline navigations (SPA routing).\n *\n * Same-origin only. Register this LAST, after any {@link installRuntimeCache}.\n */\nexport function installPrecache(options: InstallPrecacheOptions = {}): void {\n const sw = getSwScope();\n const {\n manifestUrl = \"/precache-manifest.json\",\n cacheName = \"tempest-precache\",\n navigateFallback = \"/index.html\",\n navigateFallbackDenylist = [],\n skipWaiting = true,\n navigationPreload = true,\n } = options;\n\n sw.addEventListener(\"install\", (event) => {\n event.waitUntil(\n (async () => {\n const response = await fetch(manifestUrl, { cache: \"no-cache\" });\n const manifest = (await response.json()) as PrecacheManifest;\n precacheName = `${cacheName}-${manifest.version}`;\n\n const cache = await caches.open(precacheName);\n await cache.addAll(manifest.urls);\n for (const url of manifest.urls) {\n precachedPaths.add(new URL(url, sw.location.origin).pathname);\n }\n if (skipWaiting) await sw.skipWaiting();\n })(),\n );\n });\n\n sw.addEventListener(\"activate\", (event) => {\n event.waitUntil(\n (async () => {\n const keys = await caches.keys();\n await Promise.all(\n keys\n .filter((key) => key.startsWith(`${cacheName}-`) && key !== precacheName)\n .map((key) => caches.delete(key)),\n );\n if (navigationPreload && sw.registration?.navigationPreload) {\n await sw.registration.navigationPreload.enable();\n }\n await sw.clients.claim();\n })(),\n );\n });\n\n sw.addEventListener(\"fetch\", (event) => {\n const request = event.request;\n if (request.method !== \"GET\") return;\n\n const url = new URL(request.url);\n if (url.origin !== sw.location.origin) return;\n\n // SPA navigations: serve the cached app shell when offline.\n if (request.mode === \"navigate\") {\n if (navigateFallbackDenylist.some((re) => re.test(url.pathname))) return;\n event.respondWith(\n (async () => {\n try {\n const preloaded = await event.preloadResponse;\n if (preloaded) return preloaded;\n return await fetch(request);\n } catch {\n const cache = await caches.open(precacheName);\n const shell = await cache.match(navigateFallback);\n if (shell) return shell;\n throw new Error(\"offline and no cached app shell\");\n }\n })(),\n );\n return;\n }\n\n // Precached static assets: cache-first.\n if (precachedPaths.has(url.pathname)) {\n event.respondWith(\n (async () => {\n const cache = await caches.open(precacheName);\n const cached = await cache.match(request);\n return cached ?? fetch(request);\n })(),\n );\n }\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"cache.js","names":["getSwScope","answeredByRuntime","PRECACHE_STATE_PATH","CACHED_AT_HEADER","isExpired","maxAgeSeconds","stamp","response","dateHeader","storedAt","cacheControlDirectives","directive","varyNames","vary","name","isStorable","directives","route","isCredentialed","request","credentialsOn","credentialHeaderSet","store","headers","varies","missing","copy","cache","trimCache","maxEntries","cacheName","keys","key","cacheFirst","cached","networkFirst","network","timeoutMs","timeout","extend","reject","staleWhileRevalidate","fresh","runRoute","routeMatches","url","createPartialResponse","rangeHeader","match","buffer","total","start","end","suffix","slice","installRuntimeCache","event","routes","candidate","promise","full","stripRange","out","readPrecacheState","writePrecacheState","state","latestPrecache","latest","at","pick","describePrecache","path","fromPrecache","installPrecache","sw","options","installedName","active","known","loading","loadActive","found","manifest","manifestUrl","skipWaiting","previous","navigationPreload","navigateFallbackDenylist","re","precache","shell","navigateFallback"],"sources":["../../src/sw/cache.ts"],"sourcesContent":["/**\n * @tempest-limits file-lines — precache, runtime strategies (cache-first, network-\n * first, stale-while-revalidate), expiry and the inspect/clear helpers all address\n * the same Cache Storage namespace, and the naming scheme that keeps them from\n * evicting each other is the file's whole contract.\n */\n/**\n * Service-worker caching helpers — a small, dependency-free subset of what\n * Workbox provides: precaching of the build's app shell (so the app launches\n * offline) plus runtime caching strategies for fonts, APIs and images.\n *\n * Import these inside your own `sw.ts`. They run in the service-worker global\n * scope, not the main thread. Pair `installPrecache` with the\n * `tempestPwaManifest()` Vite plugin (from `tempest-react-sdk/vite`), which\n * emits the `precache-manifest.json` this reads at install time.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installRuntimeCache, installPrecache } from \"tempest-react-sdk/sw\";\n *\n * // Register specific routes FIRST so they win over the precache catch-all.\n * installRuntimeCache([\n * { match: /\\/api\\//, strategy: \"network-first\", cacheName: \"tempest-api\", maxAgeSeconds: 300 },\n * ]);\n * installPrecache();\n */\n\nimport { credentialHeaderSet } from \"./credential-headers\";\n\n/** Minimal shape of the events we touch, to avoid pulling in `lib.webworker`. */\ninterface ExtendableEventLike {\n waitUntil(promise: Promise<unknown>): void;\n}\n\ninterface FetchEventLike extends ExtendableEventLike {\n request: Request;\n respondWith(response: Response | Promise<Response>): void;\n /** Resolves to the navigation-preload response when it is enabled, else `undefined`. */\n preloadResponse?: Promise<Response | undefined>;\n}\n\n/** Minimal shape of `registration.navigationPreload` (Navigation Preload API). */\ninterface NavigationPreloadManagerLike {\n enable(): Promise<void>;\n disable(): Promise<void>;\n}\n\ninterface CacheSwGlobal {\n addEventListener(type: \"install\", listener: (event: ExtendableEventLike) => void): void;\n addEventListener(type: \"activate\", listener: (event: ExtendableEventLike) => void): void;\n addEventListener(type: \"fetch\", listener: (event: FetchEventLike) => void): void;\n clients: { claim(): Promise<void> };\n skipWaiting(): Promise<void>;\n location: { origin: string };\n registration?: { navigationPreload?: NavigationPreloadManagerLike };\n}\n\nfunction getSwScope(): CacheSwGlobal {\n return globalThis as unknown as CacheSwGlobal;\n}\n\n/** Caching strategy for a runtime route. Mirrors the common Workbox trio. */\nexport type RuntimeStrategy = \"cache-first\" | \"network-first\" | \"stale-while-revalidate\";\n\n/** A single runtime-caching rule, matched against each `GET` request. */\nexport interface RuntimeRoute {\n /** A `RegExp` tested against the full URL, or a predicate over the parsed URL. */\n match: RegExp | ((url: URL, request: Request) => boolean);\n /** How to resolve a match. */\n strategy: RuntimeStrategy;\n /** Cache bucket name for this route. */\n cacheName: string;\n /** Trim the cache to at most this many entries (FIFO) after each write. */\n maxEntries?: number;\n /** Treat a cached response older than this (seconds) as a miss. */\n maxAgeSeconds?: number;\n /** For `network-first`: fall back to cache after this timeout (seconds). */\n networkTimeoutSeconds?: number;\n /**\n * Serve HTTP `Range` requests (206 Partial Content) by slicing the cached\n * full response. Enable for audio/video so seeking works offline. The full\n * resource is cached once (the `Range` header is stripped before caching).\n */\n rangeRequests?: boolean;\n /**\n * Opt in to caching credentialed requests — those that carry a credential\n * header (see `credentialHeaders`) or are a CORS-mode `fetch` sent with\n * `credentials: \"include\"` — and responses marked `Cache-Control: private`.\n * Default `false`.\n *\n * Cache Storage is keyed by URL and outlives the session: without this\n * gate, a `GET /api/me` stored for one account was served to the next\n * account on the same browser whenever the network failed or exceeded\n * `networkTimeoutSeconds` (reproduced in Chromium 153, 11/10/2026). With\n * the default, credentialed requests bypass the route entirely and reach\n * the network as if no service worker were installed.\n *\n * When enabled, every stored entry gets `Vary` on each credential header the\n * request carried, so the Cache API only matches it for the same credential —\n * another account (or no account) misses instead of reading it. A token\n * rotated while online simply repopulates the entry. Pair it with\n * `clearSessionData()` on logout, which removes the entries themselves.\n * `Cache-Control: no-store` is never cached, opt-in or not.\n *\n * A cookie session is only partly visible here: the worker cannot see\n * `Cookie`. What it can see is a `fetch` that asked for cookies explicitly —\n * `mode: \"cors\"` with `credentials: \"include\"`, which is what `ApiClient`\n * sends under `withCredentials` — and since 0.82.0 that request is treated\n * as credentialed too (#531). `<img>`, `<audio>` and `<video>` also send\n * `credentials: \"include\"`, but in `no-cors` mode, so they keep caching. A\n * plain same-origin `fetch` (`credentials: \"same-origin\"`, the default)\n * carries the cookie without saying so; such an API stays private only\n * through `Cache-Control: private`/`no-store` on its responses and\n * `clearSessionData()` on logout. Opting a cookie request in caches it by\n * URL — there is no credential header to vary on — so `clearSessionData()`\n * is then its only isolation.\n */\n cacheAuthenticated?: boolean;\n /**\n * Header names that mark a request as credentialed. Default\n * `DEFAULT_CREDENTIAL_HEADERS` (`authorization`, `x-api-key`,\n * `x-auth-token`, …) — the same list the background-sync queue strips.\n */\n credentialHeaders?: readonly string[];\n}\n\n/** Options for {@link installPrecache}. */\nexport interface InstallPrecacheOptions {\n /** URL of the manifest emitted by `tempestPwaManifest()`. Default `/precache-manifest.json`. */\n manifestUrl?: string;\n /** Cache name prefix; the manifest `version` is appended. Default `tempest-precache`. */\n cacheName?: string;\n /** App-shell document served for navigation requests offline. Default `/index.html`. */\n navigateFallback?: string;\n /** Navigation paths that should NOT use the fallback (e.g. `[/^\\/api\\//]`). */\n navigateFallbackDenylist?: RegExp[];\n /** Activate the new worker immediately after precaching. Default `true`. */\n skipWaiting?: boolean;\n /**\n * Enable the Navigation Preload API on `activate`, so the browser fetches\n * the navigation request in parallel with the worker boot and the handler\n * serves `event.preloadResponse` — cutting first-navigation latency after\n * the worker starts. Silently ignored where unsupported. Default `true`.\n */\n navigationPreload?: boolean;\n}\n\ninterface PrecacheManifest {\n version: string;\n urls: string[];\n}\n\n/**\n * Fetch events a runtime route already answered. The precache listener skips\n * them: a second `respondWith` on the same event throws `InvalidStateError`,\n * which a precached path also matched by a runtime route used to raise.\n */\nconst answeredByRuntime = new WeakSet<object>();\n\n/**\n * Synthetic key, inside each versioned precache, of the entry that records when\n * that version was installed and activated. It never collides with a built\n * asset, and living inside the versioned cache means it is deleted with it and\n * survives `clearSessionData()`, which keeps the precache.\n */\nconst PRECACHE_STATE_PATH = \"/__tempest-precache-state__\";\n\n/** What {@link PRECACHE_STATE_PATH} holds for one precache version. */\ninterface PrecacheState {\n installedAt: number;\n activatedAt?: number;\n}\n\n/** The precache a worker serves: its cache name and the pathnames it holds. */\ninterface ActivePrecache {\n name: string;\n paths: Set<string>;\n}\n\n/**\n * Header stamped on every runtime-cache entry with the epoch milliseconds of the\n * write. `Date` alone is not enough: it is not a CORS-safelisted response\n * header, so for a cross-origin API the worker reads `null` and `maxAgeSeconds`\n * would never expire anything.\n */\nconst CACHED_AT_HEADER = \"x-tempest-cached-at\";\n\n/**\n * Whether a cached response has aged past `maxAgeSeconds`. Reads the\n * {@link CACHED_AT_HEADER} stamp written by the runtime cache, and falls back to\n * `Date` for entries written before the stamp existed.\n */\nfunction isExpired(response: Response, maxAgeSeconds?: number): boolean {\n if (!maxAgeSeconds) return false;\n const stamp = Number(response.headers.get(CACHED_AT_HEADER));\n const dateHeader = response.headers.get(\"date\");\n const storedAt = stamp > 0 ? stamp : dateHeader ? new Date(dateHeader).getTime() : NaN;\n if (Number.isNaN(storedAt)) return false;\n return (Date.now() - storedAt) / 1000 > maxAgeSeconds;\n}\n\n/** The `Cache-Control` directives of a response, lower-cased. */\nfunction cacheControlDirectives(response: Response): string[] {\n return (response.headers.get(\"cache-control\") ?? \"\")\n .toLowerCase()\n .split(\",\")\n .map((directive) => directive.trim().split(\"=\")[0]);\n}\n\n/** The comma-separated names of a response's `Vary` header, trimmed and lower-cased. */\nfunction varyNames(vary: string | null): string[] {\n return (vary ?? \"\")\n .toLowerCase()\n .split(\",\")\n .map((name) => name.trim());\n}\n\n/**\n * Whether a network response may be written to the route's cache: it must be\n * `ok`, never `no-store`, never `Vary: *`, and `private` only under\n * `cacheAuthenticated`.\n *\n * `Vary: *` says no later request can be proven to match, and `Cache.put`\n * rejects it with a `TypeError`. Before 0.82.0 that rejection escaped the\n * strategy, so a response the network had delivered became the offline\n * fallback path (network-first), an error (cache-first) or nothing at all\n * (stale-while-revalidate) (#534).\n */\nfunction isStorable(response: Response, route: RuntimeRoute): boolean {\n if (!response.ok) return false;\n if (varyNames(response.headers.get(\"vary\")).includes(\"*\")) return false;\n const directives = cacheControlDirectives(response);\n if (directives.includes(\"no-store\")) return false;\n return route.cacheAuthenticated === true || !directives.includes(\"private\");\n}\n\n/**\n * Whether a request is session-bound for its route: it carries a credential\n * header, or it is a CORS `fetch` that asked for cookies with\n * `credentials: \"include\"` (`ApiClient` under `withCredentials`). A `no-cors`\n * request — `<img>`, `<audio>`, `<video>` — also sends `include` by default and\n * is not counted, or every image would bypass the cache.\n */\nfunction isCredentialed(request: Request, route: RuntimeRoute): boolean {\n if (request.mode === \"cors\" && request.credentials === \"include\") return true;\n return credentialsOn(request, route).length > 0;\n}\n\n/** The credential headers, lower-cased, that a request carries for its route. */\nfunction credentialsOn(request: Request, route: RuntimeRoute): string[] {\n return [...credentialHeaderSet(route.credentialHeaders)].filter((name) =>\n request.headers.has(name),\n );\n}\n\n/**\n * Write a response to the route's cache with the {@link CACHED_AT_HEADER} stamp\n * and, under `cacheAuthenticated`, `Vary` on each credential header the request\n * carried, so the entry only matches the credential that produced it. Skips\n * responses that {@link isStorable} rejects, then trims the cache to\n * `maxEntries`.\n */\nasync function store(\n cache: Cache,\n request: Request,\n response: Response,\n route: RuntimeRoute,\n): Promise<void> {\n if (!isStorable(response, route)) return;\n const headers = new Headers(response.headers);\n headers.set(CACHED_AT_HEADER, String(Date.now()));\n if (route.cacheAuthenticated) {\n const vary = headers.get(\"vary\");\n const varies = varyNames(vary);\n const missing = credentialsOn(request, route).filter((name) => !varies.includes(name));\n if (missing.length > 0) {\n headers.set(\"vary\", [vary, ...missing].filter(Boolean).join(\", \"));\n }\n }\n const copy = response.clone();\n await cache.put(\n request,\n new Response(copy.body, { status: copy.status, statusText: copy.statusText, headers }),\n );\n await trimCache(route.cacheName, route.maxEntries);\n}\n\n/** Drop oldest entries until the cache holds at most `maxEntries`. */\nasync function trimCache(cacheName: string, maxEntries?: number): Promise<void> {\n if (!maxEntries) return;\n const cache = await caches.open(cacheName);\n const keys = await cache.keys();\n if (keys.length <= maxEntries) return;\n for (const key of keys.slice(0, keys.length - maxEntries)) {\n await cache.delete(key);\n }\n}\n\nasync function cacheFirst(request: Request, route: RuntimeRoute): Promise<Response> {\n const cache = await caches.open(route.cacheName);\n const cached = await cache.match(request);\n if (cached && !isExpired(cached, route.maxAgeSeconds)) return cached;\n\n const response = await fetch(request);\n await store(cache, request, response, route);\n return response;\n}\n\n/**\n * Network-first. When the timeout wins, the network request keeps running to\n * refresh the cache, and is handed to `extend` (`event.waitUntil`) so the\n * worker is not stopped mid-write.\n */\nasync function networkFirst(\n request: Request,\n route: RuntimeRoute,\n extend: (promise: Promise<unknown>) => void,\n): Promise<Response> {\n const cache = await caches.open(route.cacheName);\n\n const network = (async (): Promise<Response> => {\n const response = await fetch(request);\n await store(cache, request, response, route);\n return response;\n })();\n\n const timeoutMs = (route.networkTimeoutSeconds ?? 0) * 1000;\n try {\n if (timeoutMs > 0) {\n const timeout = new Promise<never>((_, reject) =>\n setTimeout(() => {\n extend(network.catch(() => undefined));\n reject(new Error(\"network-timeout\"));\n }, timeoutMs),\n );\n return await Promise.race([network, timeout]);\n }\n return await network;\n } catch {\n const cached = await cache.match(request);\n if (cached && !isExpired(cached, route.maxAgeSeconds)) return cached;\n throw new Error(\"network-first: no network and no cache\");\n }\n}\n\n/**\n * Stale-while-revalidate. A fresh cached response is served at once, and the\n * revalidation is handed to `extend` (`event.waitUntil`): without it the\n * browser may stop the worker before the refreshed response is written.\n */\nasync function staleWhileRevalidate(\n request: Request,\n route: RuntimeRoute,\n extend: (promise: Promise<unknown>) => void,\n): Promise<Response> {\n const cache = await caches.open(route.cacheName);\n const cached = await cache.match(request);\n\n const network = fetch(request)\n .then(async (response) => {\n await store(cache, request, response, route);\n return response;\n })\n .catch(() => undefined);\n\n const fresh = cached && !isExpired(cached, route.maxAgeSeconds) ? cached : undefined;\n if (fresh) {\n extend(network);\n return fresh;\n }\n const response = await network;\n if (response) return response;\n throw new Error(\"stale-while-revalidate: no network and no cache\");\n}\n\nfunction runRoute(\n request: Request,\n route: RuntimeRoute,\n extend: (promise: Promise<unknown>) => void,\n): Promise<Response> {\n switch (route.strategy) {\n case \"cache-first\":\n return cacheFirst(request, route);\n case \"network-first\":\n return networkFirst(request, route, extend);\n case \"stale-while-revalidate\":\n return staleWhileRevalidate(request, route, extend);\n }\n}\n\nfunction routeMatches(route: RuntimeRoute, url: URL, request: Request): boolean {\n return typeof route.match === \"function\"\n ? route.match(url, request)\n : route.match.test(url.href);\n}\n\n/**\n * Build a `206 Partial Content` response from a full one for an HTTP `Range`\n * request. Supports `bytes=start-end`, open-ended `bytes=start-` and suffix\n * `bytes=-suffixLength` (the last N bytes). Returns the original response when there is no usable\n * `Range` header, or a `416` when the range is unsatisfiable.\n *\n * @param request The incoming request (its `Range` header drives the slice).\n * @param response The full (200) response to slice.\n */\nexport async function createPartialResponse(\n request: Request,\n response: Response,\n): Promise<Response> {\n const rangeHeader = request.headers.get(\"range\");\n if (!rangeHeader) return response;\n\n const match = /^bytes=(\\d*)-(\\d*)$/.exec(rangeHeader.trim());\n if (!match || (match[1] === \"\" && match[2] === \"\")) {\n return new Response(null, { status: 416, statusText: \"Range Not Satisfiable\" });\n }\n\n const buffer = await response.clone().arrayBuffer();\n const total = buffer.byteLength;\n\n let start: number;\n let end: number;\n if (match[1] === \"\") {\n const suffix = Number(match[2]);\n start = Math.max(0, total - suffix);\n end = total - 1;\n } else {\n start = Number(match[1]);\n end = match[2] === \"\" ? total - 1 : Math.min(Number(match[2]), total - 1);\n }\n\n if (start > end || start >= total) {\n return new Response(null, {\n status: 416,\n statusText: \"Range Not Satisfiable\",\n headers: { \"Content-Range\": `bytes */${total}` },\n });\n }\n\n const slice = buffer.slice(start, end + 1);\n const headers = new Headers(response.headers);\n headers.set(\"Content-Range\", `bytes ${start}-${end}/${total}`);\n headers.set(\"Content-Length\", String(slice.byteLength));\n headers.set(\"Accept-Ranges\", \"bytes\");\n\n return new Response(slice, {\n status: 206,\n statusText: \"Partial Content\",\n headers,\n });\n}\n\n/**\n * Install a `fetch` handler that resolves matching `GET` requests with the\n * given runtime strategies. Non-matching requests are left untouched (no\n * `respondWith`), so a later {@link installPrecache} can handle them.\n *\n * A request carrying a credential header (`Authorization`, `X-Api-Key`, … — the\n * route's `credentialHeaders`), or a CORS `fetch` sent with\n * `credentials: \"include\"`, is also left untouched unless its route sets\n * `cacheAuthenticated`, and a response with `Cache-Control: no-store`\n * (or `private`, without that opt-in) or `Vary: *` is served but never stored. `maxAgeSeconds`\n * applies to every read, including the offline fallback: an expired entry is a\n * miss, never a last resort. Under `rangeRequests`, the full resource is cached\n * once (the `Range` header stripped) and each range is sliced from it.\n *\n * Register this BEFORE `installPrecache` so specific routes win over the\n * precache catch-all.\n *\n * @param routes Ordered rules; the first whose `match` passes handles the request.\n */\nexport function installRuntimeCache(routes: RuntimeRoute[]): void {\n const sw = getSwScope();\n sw.addEventListener(\"fetch\", (event) => {\n const request = event.request;\n if (request.method !== \"GET\") return;\n\n const url = new URL(request.url);\n const route = routes.find((candidate) => routeMatches(candidate, url, request));\n if (!route) return;\n if (!route.cacheAuthenticated && isCredentialed(request, route)) return;\n answeredByRuntime.add(event);\n const extend = (promise: Promise<unknown>): void => event.waitUntil(promise);\n\n if (route.rangeRequests && request.headers.has(\"range\")) {\n const full = new Request(request.url, {\n headers: stripRange(request.headers),\n credentials: request.credentials,\n mode: request.mode === \"navigate\" ? \"same-origin\" : request.mode,\n });\n event.respondWith(\n runRoute(full, route, extend).then((response) =>\n createPartialResponse(request, response),\n ),\n );\n return;\n }\n\n event.respondWith(runRoute(request, route, extend));\n });\n}\n\n/** Copy headers without the `Range` header (so the cached entry is the full file). */\nfunction stripRange(headers: Headers): Headers {\n const out = new Headers(headers);\n out.delete(\"range\");\n return out;\n}\n\n/** Read the {@link PrecacheState} a versioned precache recorded, if any. */\nasync function readPrecacheState(cache: Cache): Promise<PrecacheState | undefined> {\n const response = await cache.match(PRECACHE_STATE_PATH);\n if (!response) return undefined;\n try {\n return (await response.json()) as PrecacheState;\n } catch {\n return undefined;\n }\n}\n\nasync function writePrecacheState(cache: Cache, state: PrecacheState): Promise<void> {\n await cache.put(\n PRECACHE_STATE_PATH,\n new Response(JSON.stringify(state), { headers: { \"content-type\": \"application/json\" } }),\n );\n}\n\n/**\n * The name of the precache version whose `pick` timestamp is the latest, among\n * the `${cacheName}-*` caches that recorded a {@link PrecacheState}.\n */\nasync function latestPrecache(\n cacheName: string,\n pick: (state: PrecacheState) => number | undefined,\n): Promise<string | undefined> {\n let latest: { name: string; at: number } | undefined;\n for (const name of await caches.keys()) {\n if (!name.startsWith(`${cacheName}-`)) continue;\n const state = await readPrecacheState(await caches.open(name));\n const at = state ? pick(state) : undefined;\n if (at !== undefined && (!latest || at > latest.at)) latest = { name, at };\n }\n return latest?.name;\n}\n\n/** The pathnames a precache holds, read back from the cache itself. */\nasync function describePrecache(name: string): Promise<ActivePrecache> {\n const keys = await (await caches.open(name)).keys();\n const paths = new Set(\n keys\n .map((request) => new URL(request.url).pathname)\n .filter((path) => path !== PRECACHE_STATE_PATH),\n );\n return { name, paths };\n}\n\nasync function fromPrecache(name: string, request: Request): Promise<Response> {\n const cached = await (await caches.open(name)).match(request);\n return cached ?? fetch(request);\n}\n\n/**\n * Precache the app shell at `install` and serve it offline:\n * - reads `precache-manifest.json` (emitted by `tempestPwaManifest()`),\n * - caches every listed URL under a versioned cache,\n * - on `activate`, deletes stale precache versions and claims open clients,\n * - on `fetch`, serves precached assets cache-first and falls back to the\n * `navigateFallback` document for offline navigations (SPA routing).\n *\n * Survives the browser stopping and restarting the worker, which it does\n * whenever the worker sits idle and which never repeats `install`. Nothing the\n * fetch handler needs lives only in memory: `install` and `activate` record, in\n * an entry inside the versioned cache, when that version was installed and\n * activated, and a restarted worker reads back the activated version and its\n * pathnames from Cache Storage. Before 0.83.0 the cache name and the precached\n * paths were module variables filled only by `install`, so after the first\n * restart offline navigations failed with \"offline and no cached app shell\",\n * precached assets went to the network, and an `activate` in a run without\n * `install` deleted every precache, the current one included (#572).\n *\n * `activate` deletes the other `${cacheName}-*` caches only once it knows which\n * version it is activating; when no version recorded an install it deletes\n * nothing. In the first request after a restart, while the worker is still\n * reading which paths are precached, a same-origin `GET` that a runtime route\n * did not answer is answered with `fetch(request)` when it is not precached —\n * the decision to call `respondWith` cannot wait for Cache Storage. From then on\n * a non-precached request is left to the browser, as before.\n *\n * Same-origin only. Register this LAST, after any {@link installRuntimeCache}:\n * a request a runtime route answered is never answered again here.\n */\nexport function installPrecache(options: InstallPrecacheOptions = {}): void {\n const sw = getSwScope();\n const {\n manifestUrl = \"/precache-manifest.json\",\n cacheName = \"tempest-precache\",\n navigateFallback = \"/index.html\",\n navigateFallbackDenylist = [],\n skipWaiting = true,\n navigationPreload = true,\n } = options;\n\n let installedName: string | undefined;\n let active: ActivePrecache | undefined;\n let known = false;\n let loading: Promise<ActivePrecache | undefined> | undefined;\n\n const loadActive = (): Promise<ActivePrecache | undefined> => {\n if (known) return Promise.resolve(active);\n loading ??= (async () => {\n const name = await latestPrecache(cacheName, (state) => state.activatedAt);\n const found = name ? await describePrecache(name) : undefined;\n if (!known) {\n active = found;\n known = true;\n }\n return active;\n })().finally(() => {\n loading = undefined;\n });\n return loading;\n };\n\n sw.addEventListener(\"install\", (event) => {\n event.waitUntil(\n (async () => {\n const response = await fetch(manifestUrl, { cache: \"no-cache\" });\n const manifest = (await response.json()) as PrecacheManifest;\n const name = `${cacheName}-${manifest.version}`;\n\n const cache = await caches.open(name);\n await cache.addAll(manifest.urls);\n const previous = await readPrecacheState(cache);\n await writePrecacheState(cache, { ...previous, installedAt: Date.now() });\n installedName = name;\n if (skipWaiting) await sw.skipWaiting();\n })(),\n );\n });\n\n sw.addEventListener(\"activate\", (event) => {\n event.waitUntil(\n (async () => {\n const name =\n installedName ??\n (await latestPrecache(cacheName, (state) =>\n state.installedAt > (state.activatedAt ?? -Infinity)\n ? state.installedAt\n : undefined,\n ));\n if (name) {\n const cache = await caches.open(name);\n const previous = await readPrecacheState(cache);\n await writePrecacheState(cache, {\n installedAt: Date.now(),\n ...previous,\n activatedAt: Date.now(),\n });\n active = await describePrecache(name);\n known = true;\n const keys = await caches.keys();\n await Promise.all(\n keys\n .filter((key) => key.startsWith(`${cacheName}-`) && key !== name)\n .map((key) => caches.delete(key)),\n );\n }\n if (navigationPreload && sw.registration?.navigationPreload) {\n await sw.registration.navigationPreload.enable();\n }\n await sw.clients.claim();\n })(),\n );\n });\n\n sw.addEventListener(\"fetch\", (event) => {\n const request = event.request;\n if (request.method !== \"GET\") return;\n if (answeredByRuntime.has(event)) return;\n\n const url = new URL(request.url);\n if (url.origin !== sw.location.origin) return;\n\n if (request.mode === \"navigate\") {\n if (navigateFallbackDenylist.some((re) => re.test(url.pathname))) return;\n event.respondWith(\n (async () => {\n try {\n const preloaded = await event.preloadResponse;\n if (preloaded) return preloaded;\n return await fetch(request);\n } catch {\n const precache = await loadActive();\n const shell = precache\n ? await (await caches.open(precache.name)).match(navigateFallback)\n : undefined;\n if (shell) return shell;\n throw new Error(\"offline and no cached app shell\");\n }\n })(),\n );\n return;\n }\n\n if (known) {\n if (active?.paths.has(url.pathname)) {\n event.respondWith(fromPrecache(active.name, request));\n }\n return;\n }\n\n event.respondWith(\n loadActive()\n .catch(() => undefined)\n .then((precache) =>\n precache?.paths.has(url.pathname)\n ? fromPrecache(precache.name, request)\n : fetch(request),\n ),\n );\n });\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
function e(){return globalThis}var t=[`body`,`image`,`tag`,`requireInteraction`,`vibrate`,`silent`,`renotify`,`timestamp`,`dir`,`lang`];function n(e,n,r){let i={icon:e.icon??n,badge:e.badge??r};for(let n of t)e[n]!==void 0&&(i[n]=e[n]);let a={
|
|
1
|
+
function e(){return globalThis}var t=[`body`,`image`,`tag`,`requireInteraction`,`vibrate`,`silent`,`renotify`,`timestamp`,`dir`,`lang`];function n(e,n,r){let i={icon:e.icon??n,badge:e.badge??r};for(let n of t)e[n]!==void 0&&(i[n]=e[n]);let a={...e.data??{},url:e.url??`/`};if(delete a.actionUrls,e.actions){let t={};i.actions=e.actions.map(({url:e,...n})=>(e!==void 0&&(t[n.action]=e),n)),Object.keys(t).length>0&&(a.actionUrls=t)}return i.data=a,i}function r(t={}){let r=e(),{defaultTitle:i=`Notificação`,defaultIcon:a,defaultBadge:o,transform:s}=t;r.addEventListener(`push`,e=>{if(!e.data)return;let t;try{t=e.data.json()}catch{t={title:i,body:e.data.text()}}let c=s?s(t):t;if(!c)return;let l=c.title??i,u=n(c,a,o);e.waitUntil(r.registration.showNotification(l,u))})}function i(e,t){if(typeof e==`string`)return e;if(!e||typeof e!=`object`)return`/`;let n=e;if(t&&n.actionUrls&&typeof n.actionUrls==`object`){let e=n.actionUrls[t];if(typeof e==`string`)return e}return typeof n.url==`string`?n.url:`/`}function a(e,t,n){if(t===void 0)return`/`;let r;try{r=new URL(e,t)}catch{return`/`}return r.protocol!==`http:`&&r.protocol!==`https:`||n&&r.origin!==t?`/`:e}function o(e,t){try{return new URL(t,e).href===new URL(e).href}catch{return!1}}function s(t={}){let n=e(),r=t.resolveUrl??i,s=t.resolveUrl===void 0;n.addEventListener(`notificationclick`,e=>{e.notification.close();let t=a(r(e.notification.data,e.action),n.location?.origin,s);e.waitUntil((async()=>{let e=await n.clients.matchAll({type:`window`,includeUncontrolled:!0});for(let n of e)if(o(n.url,t))return n.focus();return n.clients.openWindow(t)})())})}function c(){let t=e();t.addEventListener(`message`,e=>{e.data?.type===`SKIP_WAITING`&&t.skipWaiting()})}exports.installNotificationClickHandler=s,exports.installPushHandler=r,exports.installSkipWaitingListener=c;
|
|
2
2
|
//# sourceMappingURL=create-push-handler.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"create-push-handler.cjs","names":["getSwScope","FORWARDED_OPTIONS","buildNotificationOptions","options","payload","defaultIcon","defaultBadge","key","data","actionUrls","action","url","installPushHandler","sw","event","raw","defaultTitle","transform","title","notification","defaultResolveUrl","record","actionUrl","clientShowsTarget","target","clientUrl","installNotificationClickHandler","resolveUrl","clients","client","installSkipWaitingListener"],"sources":["../../src/sw/create-push-handler.ts"],"sourcesContent":["/**\n * Service-worker context helpers for handling `push` and `notificationclick`\n * events. Import these inside your own `sw.ts` — they expect to run in the\n * service-worker global scope, not in the main thread.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installPushHandler, installNotificationClickHandler } from \"tempest-react-sdk\";\n *\n * installPushHandler({ defaultIcon: \"/icons/Logo.png\" });\n * installNotificationClickHandler();\n */\n\ninterface SwGlobal {\n registration: {\n showNotification(title: string, options?: NotificationOptions): Promise<void>;\n };\n clients: {\n matchAll(options: { type: \"window\"; includeUncontrolled?: boolean }): Promise<\n {\n url: string;\n focused: boolean;\n focus(): Promise<unknown>;\n navigate(url: string): Promise<unknown>;\n }[]\n >;\n openWindow(url: string): Promise<unknown>;\n };\n addEventListener(\n type: \"push\",\n listener: (event: {\n data: { json(): unknown; text(): string } | null;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n addEventListener(\n type: \"notificationclick\",\n listener: (event: {\n notification: { close(): void; data?: unknown };\n action?: string;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n skipWaiting(): Promise<void>;\n}\n\nfunction getSwScope(): SwGlobal {\n return globalThis as unknown as SwGlobal;\n}\n\n/**\n * A notification button as the push payload describes it.\n *\n * `action`, `title` and `icon` are forwarded to the browser as a\n * `NotificationAction`. `url` is not part of that type — the browser would drop\n * it — so {@link installPushHandler} moves it into `data.actionUrls`, where\n * {@link installNotificationClickHandler} reads it back when that button is\n * clicked.\n */\nexport interface PushNotificationAction {\n /** Identifier delivered as `event.action` when the button is clicked. */\n action: string;\n /** Button label. */\n title: string;\n /** Button icon URL. */\n icon?: string;\n /** URL opened when this button is clicked. Falls back to the top-level `url`. */\n url?: string;\n}\n\nexport interface PushPayload {\n title?: string;\n body?: string;\n icon?: string;\n badge?: string;\n image?: string;\n tag?: string;\n /** URL opened when the notification body is clicked. Default: `\"/\"`. */\n url?: string;\n /** Buttons rendered on the notification (browsers show up to `Notification.maxActions`). */\n actions?: PushNotificationAction[];\n /** Keep the notification on screen until the user interacts with it. */\n requireInteraction?: boolean;\n /** Vibration pattern in milliseconds (mobile). */\n vibrate?: number | number[];\n /** Show the notification without sound or vibration. */\n silent?: boolean;\n /** Alert again when a notification with the same `tag` replaces an older one. */\n renotify?: boolean;\n /** Event time in epoch milliseconds, shown by some platforms. */\n timestamp?: number;\n /** Text direction of the title and body. */\n dir?: NotificationDirection;\n /** BCP 47 language tag of the title and body. */\n lang?: string;\n /** Arbitrary extra data forwarded to `event.notification.data`. */\n data?: Record<string, unknown>;\n}\n\n/**\n * `NotificationOptions` plus the members TypeScript's DOM lib leaves out\n * because not every engine implements them. Chromium honours all of them;\n * engines without support ignore the unknown keys.\n */\ntype ExtendedNotificationOptions = NotificationOptions & {\n image?: string;\n actions?: Omit<PushNotificationAction, \"url\">[];\n vibrate?: number | number[];\n renotify?: boolean;\n timestamp?: number;\n};\n\n/**\n * Payload keys copied verbatim onto the notification options when present.\n */\nconst FORWARDED_OPTIONS = [\n \"body\",\n \"image\",\n \"tag\",\n \"requireInteraction\",\n \"vibrate\",\n \"silent\",\n \"renotify\",\n \"timestamp\",\n \"dir\",\n \"lang\",\n] as const satisfies readonly (keyof PushPayload & keyof ExtendedNotificationOptions)[];\n\n/**\n * Build the `showNotification` options from a push payload.\n *\n * Keys the payload omits stay absent instead of being set to `undefined`, so\n * the browser applies its own defaults. Per-action `url`s are collected into\n * `data.actionUrls` (only when at least one action carries one) and stripped\n * from the actions handed to the browser.\n *\n * @param payload - The parsed (and transformed) push payload.\n * @param defaultIcon - Icon used when the payload omits one.\n * @param defaultBadge - Badge used when the payload omits one.\n * @returns The options object for `registration.showNotification`.\n */\nfunction buildNotificationOptions(\n payload: PushPayload,\n defaultIcon: string | undefined,\n defaultBadge: string | undefined,\n): ExtendedNotificationOptions {\n const options: Record<string, unknown> = {\n icon: payload.icon ?? defaultIcon,\n badge: payload.badge ?? defaultBadge,\n };\n for (const key of FORWARDED_OPTIONS) {\n if (payload[key] !== undefined) options[key] = payload[key];\n }\n\n const data: Record<string, unknown> = { url: payload.url ?? \"/\", ...(payload.data ?? {}) };\n if (payload.actions) {\n const actionUrls: Record<string, string> = {};\n options.actions = payload.actions.map(({ url, ...action }) => {\n if (url !== undefined) actionUrls[action.action] = url;\n return action;\n });\n if (Object.keys(actionUrls).length > 0) data.actionUrls = actionUrls;\n }\n options.data = data;\n return options as ExtendedNotificationOptions;\n}\n\nexport interface InstallPushHandlerOptions {\n /** Title used when the payload omits one. */\n defaultTitle?: string;\n /** Icon used when the payload omits one. */\n defaultIcon?: string;\n /** Badge image (mobile). */\n defaultBadge?: string;\n /**\n * Transform the raw payload before showing the notification. Return `null`\n * to suppress the notification entirely (e.g. silent pings).\n */\n transform?: (payload: PushPayload) => PushPayload | null;\n}\n\n/**\n * Install a `push` event listener that parses the payload as JSON (with a\n * plain-text fallback) and shows a notification.\n *\n * Every `NotificationOptions` member the payload carries is forwarded —\n * `actions`, `requireInteraction`, `vibrate`, `silent`, `renotify`,\n * `timestamp`, `dir` and `lang` alongside `body`, `icon`, `badge`, `image` and\n * `tag`. `data` receives the top-level `url` (default `\"/\"`), the payload's\n * own `data`, and `actionUrls` mapping each action id to its `url`.\n */\nexport function installPushHandler(options: InstallPushHandlerOptions = {}): void {\n const sw = getSwScope();\n const { defaultTitle = \"Notificação\", defaultIcon, defaultBadge, transform } = options;\n\n sw.addEventListener(\"push\", (event) => {\n if (!event.data) return;\n\n let raw: PushPayload;\n try {\n raw = event.data.json() as PushPayload;\n } catch {\n raw = { title: defaultTitle, body: event.data.text() };\n }\n\n const payload = transform ? transform(raw) : raw;\n if (!payload) return;\n\n const title = payload.title ?? defaultTitle;\n const notification = buildNotificationOptions(payload, defaultIcon, defaultBadge);\n\n event.waitUntil(sw.registration.showNotification(title, notification));\n });\n}\n\nexport interface InstallNotificationClickHandlerOptions {\n /**\n * Resolve the destination URL from the notification data and the clicked\n * action id (`\"\"` or `undefined` for a click on the body). Default: the\n * action's entry in `data.actionUrls`, else `data.url`, else `\"/\"`.\n */\n resolveUrl?: (data: unknown, action: string | undefined) => string;\n}\n\n/**\n * Default URL resolution for a notification click.\n *\n * @param data - `event.notification.data`, as {@link installPushHandler} wrote it.\n * @param action - `event.action`; empty or `undefined` for a click on the body.\n * @returns The action's URL when it has one, else `data.url`, else `\"/\"`.\n */\nfunction defaultResolveUrl(data: unknown, action: string | undefined): string {\n if (typeof data === \"string\") return data;\n if (!data || typeof data !== \"object\") return \"/\";\n const record = data as Record<string, unknown>;\n if (action && record.actionUrls && typeof record.actionUrls === \"object\") {\n const actionUrl = (record.actionUrls as Record<string, unknown>)[action];\n if (typeof actionUrl === \"string\") return actionUrl;\n }\n return typeof record.url === \"string\" ? record.url : \"/\";\n}\n\n/**\n * Whether an open window client is already showing `target`.\n *\n * `target` is resolved against the client's own URL, so a relative path\n * compares on the client's origin, and the comparison is on the full href.\n * A substring test would let `/events/1` match a client on `/events/10`, and\n * `/` match every client.\n *\n * @param clientUrl - The window client's absolute URL.\n * @param target - The URL the click resolved to, absolute or relative.\n * @returns `true` when both point at the same document.\n */\nfunction clientShowsTarget(clientUrl: string, target: string): boolean {\n try {\n return new URL(target, clientUrl).href === new URL(clientUrl).href;\n } catch {\n return false;\n }\n}\n\n/**\n * Install a `notificationclick` handler that focuses an existing client when\n * possible and falls back to opening a new window.\n *\n * A click on an action button opens that action's URL (see\n * {@link PushNotificationAction.url}); a click on the body, or on an action\n * without one, opens the top-level `url`.\n */\nexport function installNotificationClickHandler(\n options: InstallNotificationClickHandlerOptions = {},\n): void {\n const sw = getSwScope();\n const resolveUrl = options.resolveUrl ?? defaultResolveUrl;\n\n sw.addEventListener(\"notificationclick\", (event) => {\n event.notification.close();\n const target = resolveUrl(event.notification.data, event.action);\n\n event.waitUntil(\n (async () => {\n const clients = await sw.clients.matchAll({\n type: \"window\",\n includeUncontrolled: true,\n });\n for (const client of clients) {\n if (clientShowsTarget(client.url, target)) {\n return client.focus();\n }\n }\n return sw.clients.openWindow(target);\n })(),\n );\n });\n}\n\n/**\n * Install a `message` listener that activates a waiting worker when the host\n * app sends `{ type: \"SKIP_WAITING\" }`.\n */\nexport function installSkipWaitingListener(): void {\n const sw = getSwScope() as SwGlobal & {\n addEventListener(\n type: \"message\",\n listener: (event: { data?: { type?: string } }) => void,\n ): void;\n };\n sw.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"SKIP_WAITING\") {\n void sw.skipWaiting();\n }\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"create-push-handler.cjs","names":["getSwScope","FORWARDED_OPTIONS","buildNotificationOptions","options","payload","defaultIcon","defaultBadge","key","data","actionUrls","action","url","installPushHandler","sw","event","raw","defaultTitle","transform","title","notification","defaultResolveUrl","record","actionUrl","allowedTarget","origin","target","sameOriginOnly","clientShowsTarget","clientUrl","installNotificationClickHandler","resolveUrl","clients","client","installSkipWaitingListener"],"sources":["../../src/sw/create-push-handler.ts"],"sourcesContent":["/**\n * Service-worker context helpers for handling `push` and `notificationclick`\n * events. Import these inside your own `sw.ts` — they expect to run in the\n * service-worker global scope, not in the main thread.\n *\n * @example\n * /// <reference lib=\"webworker\" />\n * import { installPushHandler, installNotificationClickHandler } from \"tempest-react-sdk\";\n *\n * installPushHandler({ defaultIcon: \"/icons/Logo.png\" });\n * installNotificationClickHandler();\n */\n\ninterface SwGlobal {\n registration: {\n showNotification(title: string, options?: NotificationOptions): Promise<void>;\n };\n clients: {\n matchAll(options: { type: \"window\"; includeUncontrolled?: boolean }): Promise<\n {\n url: string;\n focused: boolean;\n focus(): Promise<unknown>;\n navigate(url: string): Promise<unknown>;\n }[]\n >;\n openWindow(url: string): Promise<unknown>;\n };\n addEventListener(\n type: \"push\",\n listener: (event: {\n data: { json(): unknown; text(): string } | null;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n addEventListener(\n type: \"notificationclick\",\n listener: (event: {\n notification: { close(): void; data?: unknown };\n action?: string;\n waitUntil(promise: Promise<unknown>): void;\n }) => void,\n ): void;\n skipWaiting(): Promise<void>;\n location?: { origin: string };\n}\n\nfunction getSwScope(): SwGlobal {\n return globalThis as unknown as SwGlobal;\n}\n\n/**\n * A notification button as the push payload describes it.\n *\n * `action`, `title` and `icon` are forwarded to the browser as a\n * `NotificationAction`. `url` is not part of that type — the browser would drop\n * it — so {@link installPushHandler} moves it into `data.actionUrls`, where\n * {@link installNotificationClickHandler} reads it back when that button is\n * clicked.\n */\nexport interface PushNotificationAction {\n /** Identifier delivered as `event.action` when the button is clicked. */\n action: string;\n /** Button label. */\n title: string;\n /** Button icon URL. */\n icon?: string;\n /** URL opened when this button is clicked. Falls back to the top-level `url`. */\n url?: string;\n}\n\nexport interface PushPayload {\n title?: string;\n body?: string;\n icon?: string;\n badge?: string;\n image?: string;\n tag?: string;\n /** URL opened when the notification body is clicked. Default: `\"/\"`. */\n url?: string;\n /** Buttons rendered on the notification (browsers show up to `Notification.maxActions`). */\n actions?: PushNotificationAction[];\n /** Keep the notification on screen until the user interacts with it. */\n requireInteraction?: boolean;\n /** Vibration pattern in milliseconds (mobile). */\n vibrate?: number | number[];\n /** Show the notification without sound or vibration. */\n silent?: boolean;\n /** Alert again when a notification with the same `tag` replaces an older one. */\n renotify?: boolean;\n /** Event time in epoch milliseconds, shown by some platforms. */\n timestamp?: number;\n /** Text direction of the title and body. */\n dir?: NotificationDirection;\n /** BCP 47 language tag of the title and body. */\n lang?: string;\n /**\n * Arbitrary extra data forwarded to `event.notification.data`.\n *\n * Its `url` and `actionUrls` keys are overwritten by the top-level `url` and\n * the actions' `url`s: those two keys decide where a click goes, and a\n * free-form bag — often filled from the record that triggered the push — must\n * not be able to redirect it.\n */\n data?: Record<string, unknown>;\n}\n\n/**\n * `NotificationOptions` plus the members TypeScript's DOM lib leaves out\n * because not every engine implements them. Chromium honours all of them;\n * engines without support ignore the unknown keys.\n */\ntype ExtendedNotificationOptions = NotificationOptions & {\n image?: string;\n actions?: Omit<PushNotificationAction, \"url\">[];\n vibrate?: number | number[];\n renotify?: boolean;\n timestamp?: number;\n};\n\n/**\n * Payload keys copied verbatim onto the notification options when present.\n */\nconst FORWARDED_OPTIONS = [\n \"body\",\n \"image\",\n \"tag\",\n \"requireInteraction\",\n \"vibrate\",\n \"silent\",\n \"renotify\",\n \"timestamp\",\n \"dir\",\n \"lang\",\n] as const satisfies readonly (keyof PushPayload & keyof ExtendedNotificationOptions)[];\n\n/**\n * Build the `showNotification` options from a push payload.\n *\n * Keys the payload omits stay absent instead of being set to `undefined`, so\n * the browser applies its own defaults. Per-action `url`s are collected into\n * `data.actionUrls` (only when at least one action carries one) and stripped\n * from the actions handed to the browser.\n *\n * `payload.data` is spread **first**, so its own `url` or `actionUrls` can never\n * replace the destinations the payload declares: until 0.81.0 it was spread last,\n * and `{ url: \"/orders/9\", data: { url: \"https://elsewhere\" } }` opened\n * `https://elsewhere` on click.\n *\n * @param payload - The parsed (and transformed) push payload.\n * @param defaultIcon - Icon used when the payload omits one.\n * @param defaultBadge - Badge used when the payload omits one.\n * @returns The options object for `registration.showNotification`.\n */\nfunction buildNotificationOptions(\n payload: PushPayload,\n defaultIcon: string | undefined,\n defaultBadge: string | undefined,\n): ExtendedNotificationOptions {\n const options: Record<string, unknown> = {\n icon: payload.icon ?? defaultIcon,\n badge: payload.badge ?? defaultBadge,\n };\n for (const key of FORWARDED_OPTIONS) {\n if (payload[key] !== undefined) options[key] = payload[key];\n }\n\n const data: Record<string, unknown> = { ...(payload.data ?? {}), url: payload.url ?? \"/\" };\n delete data.actionUrls;\n if (payload.actions) {\n const actionUrls: Record<string, string> = {};\n options.actions = payload.actions.map(({ url, ...action }) => {\n if (url !== undefined) actionUrls[action.action] = url;\n return action;\n });\n if (Object.keys(actionUrls).length > 0) data.actionUrls = actionUrls;\n }\n options.data = data;\n return options as ExtendedNotificationOptions;\n}\n\nexport interface InstallPushHandlerOptions {\n /** Title used when the payload omits one. */\n defaultTitle?: string;\n /** Icon used when the payload omits one. */\n defaultIcon?: string;\n /** Badge image (mobile). */\n defaultBadge?: string;\n /**\n * Transform the raw payload before showing the notification. Return `null`\n * to suppress the notification entirely (e.g. silent pings).\n */\n transform?: (payload: PushPayload) => PushPayload | null;\n}\n\n/**\n * Install a `push` event listener that parses the payload as JSON (with a\n * plain-text fallback) and shows a notification.\n *\n * Every `NotificationOptions` member the payload carries is forwarded —\n * `actions`, `requireInteraction`, `vibrate`, `silent`, `renotify`,\n * `timestamp`, `dir` and `lang` alongside `body`, `icon`, `badge`, `image` and\n * `tag`. `data` receives the payload's own `data`, then the top-level `url`\n * (default `\"/\"`) and `actionUrls` mapping each action id to its `url` — in that\n * order, so `payload.data` cannot override where a click goes.\n */\nexport function installPushHandler(options: InstallPushHandlerOptions = {}): void {\n const sw = getSwScope();\n const { defaultTitle = \"Notificação\", defaultIcon, defaultBadge, transform } = options;\n\n sw.addEventListener(\"push\", (event) => {\n if (!event.data) return;\n\n let raw: PushPayload;\n try {\n raw = event.data.json() as PushPayload;\n } catch {\n raw = { title: defaultTitle, body: event.data.text() };\n }\n\n const payload = transform ? transform(raw) : raw;\n if (!payload) return;\n\n const title = payload.title ?? defaultTitle;\n const notification = buildNotificationOptions(payload, defaultIcon, defaultBadge);\n\n event.waitUntil(sw.registration.showNotification(title, notification));\n });\n}\n\nexport interface InstallNotificationClickHandlerOptions {\n /**\n * Resolve the destination URL from the notification data and the clicked\n * action id (`\"\"` or `undefined` for a click on the body). Default: the\n * action's entry in `data.actionUrls`, else `data.url`, else `\"/\"`.\n *\n * The default only ever opens a page of the worker's **own origin**: a URL on\n * another origin, or with a scheme other than `http:`/`https:`, becomes `\"/\"`.\n * A resolver of your own is how an app opts into another origin — what it\n * returns may point anywhere `http:`/`https:`, and any other scheme still\n * becomes `\"/\"`.\n */\n resolveUrl?: (data: unknown, action: string | undefined) => string;\n}\n\n/**\n * Default URL resolution for a notification click.\n *\n * @param data - `event.notification.data`, as {@link installPushHandler} wrote it.\n * @param action - `event.action`; empty or `undefined` for a click on the body.\n * @returns The action's URL when it has one, else `data.url`, else `\"/\"`.\n */\nfunction defaultResolveUrl(data: unknown, action: string | undefined): string {\n if (typeof data === \"string\") return data;\n if (!data || typeof data !== \"object\") return \"/\";\n const record = data as Record<string, unknown>;\n if (action && record.actionUrls && typeof record.actionUrls === \"object\") {\n const actionUrl = (record.actionUrls as Record<string, unknown>)[action];\n if (typeof actionUrl === \"string\") return actionUrl;\n }\n return typeof record.url === \"string\" ? record.url : \"/\";\n}\n\n/**\n * The URL a click may open, given the worker's origin.\n *\n * The push payload is written by the app's server, but its `url` is often\n * copied from the record that triggered the push — a chat message, a ticket —\n * and `openWindow` opens it with the app's notification as the only context the\n * user sees. So the default resolver keeps to the worker's own origin, the same\n * line `safeLinkUrl` draws for rendered links, and a custom resolver is the\n * explicit opt-in to leave it. Neither may open a non-`http(s)` scheme.\n *\n * The URL comes back as given, not resolved, so a relative path stays relative —\n * `openWindow` resolves it against the worker's location exactly like this check\n * did.\n *\n * @param target - What the resolver returned.\n * @param origin - The worker's origin; `undefined` outside a worker, where\n * nothing can be judged and only `\"/\"` is opened.\n * @param sameOriginOnly - Whether another origin is refused too.\n * @returns `target` when it may be opened, `\"/\"` otherwise.\n */\nfunction allowedTarget(\n target: string,\n origin: string | undefined,\n sameOriginOnly: boolean,\n): string {\n if (origin === undefined) return \"/\";\n let url: URL;\n try {\n url = new URL(target, origin);\n } catch {\n return \"/\";\n }\n if (url.protocol !== \"http:\" && url.protocol !== \"https:\") return \"/\";\n if (sameOriginOnly && url.origin !== origin) return \"/\";\n return target;\n}\n\n/**\n * Whether an open window client is already showing `target`.\n *\n * `target` is resolved against the client's own URL, so a relative path\n * compares on the client's origin, and the comparison is on the full href.\n * A substring test would let `/events/1` match a client on `/events/10`, and\n * `/` match every client.\n *\n * @param clientUrl - The window client's absolute URL.\n * @param target - The URL the click resolved to, absolute or relative.\n * @returns `true` when both point at the same document.\n */\nfunction clientShowsTarget(clientUrl: string, target: string): boolean {\n try {\n return new URL(target, clientUrl).href === new URL(clientUrl).href;\n } catch {\n return false;\n }\n}\n\n/**\n * Install a `notificationclick` handler that focuses an existing client when\n * possible and falls back to opening a new window.\n *\n * A click on an action button opens that action's URL (see\n * {@link PushNotificationAction.url}); a click on the body, or on an action\n * without one, opens the top-level `url`. Only a same-origin `http(s)` URL is\n * opened by default; see {@link InstallNotificationClickHandlerOptions.resolveUrl}\n * for opting into another origin.\n */\nexport function installNotificationClickHandler(\n options: InstallNotificationClickHandlerOptions = {},\n): void {\n const sw = getSwScope();\n const resolveUrl = options.resolveUrl ?? defaultResolveUrl;\n const sameOriginOnly = options.resolveUrl === undefined;\n\n sw.addEventListener(\"notificationclick\", (event) => {\n event.notification.close();\n const target = allowedTarget(\n resolveUrl(event.notification.data, event.action),\n sw.location?.origin,\n sameOriginOnly,\n );\n\n event.waitUntil(\n (async () => {\n const clients = await sw.clients.matchAll({\n type: \"window\",\n includeUncontrolled: true,\n });\n for (const client of clients) {\n if (clientShowsTarget(client.url, target)) {\n return client.focus();\n }\n }\n return sw.clients.openWindow(target);\n })(),\n );\n });\n}\n\n/**\n * Install a `message` listener that activates a waiting worker when the host\n * app sends `{ type: \"SKIP_WAITING\" }`.\n */\nexport function installSkipWaitingListener(): void {\n const sw = getSwScope() as SwGlobal & {\n addEventListener(\n type: \"message\",\n listener: (event: { data?: { type?: string } }) => void,\n ): void;\n };\n sw.addEventListener(\"message\", (event) => {\n if (event.data?.type === \"SKIP_WAITING\") {\n void sw.skipWaiting();\n }\n });\n}\n"],"mappings":"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"}
|
|
@@ -21,10 +21,10 @@ function n(e, n, r) {
|
|
|
21
21
|
};
|
|
22
22
|
for (let n of t) e[n] !== void 0 && (i[n] = e[n]);
|
|
23
23
|
let a = {
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
...e.data ?? {},
|
|
25
|
+
url: e.url ?? "/"
|
|
26
26
|
};
|
|
27
|
-
if (e.actions) {
|
|
27
|
+
if (delete a.actionUrls, e.actions) {
|
|
28
28
|
let t = {};
|
|
29
29
|
i.actions = e.actions.map(({ url: e, ...n }) => (e !== void 0 && (t[n.action] = e), n)), Object.keys(t).length > 0 && (a.actionUrls = t);
|
|
30
30
|
}
|
|
@@ -59,35 +59,45 @@ function i(e, t) {
|
|
|
59
59
|
}
|
|
60
60
|
return typeof n.url == "string" ? n.url : "/";
|
|
61
61
|
}
|
|
62
|
-
function a(e, t) {
|
|
62
|
+
function a(e, t, n) {
|
|
63
|
+
if (t === void 0) return "/";
|
|
64
|
+
let r;
|
|
65
|
+
try {
|
|
66
|
+
r = new URL(e, t);
|
|
67
|
+
} catch {
|
|
68
|
+
return "/";
|
|
69
|
+
}
|
|
70
|
+
return r.protocol !== "http:" && r.protocol !== "https:" || n && r.origin !== t ? "/" : e;
|
|
71
|
+
}
|
|
72
|
+
function o(e, t) {
|
|
63
73
|
try {
|
|
64
74
|
return new URL(t, e).href === new URL(e).href;
|
|
65
75
|
} catch {
|
|
66
76
|
return !1;
|
|
67
77
|
}
|
|
68
78
|
}
|
|
69
|
-
function
|
|
70
|
-
let n = e(), r = t.resolveUrl ?? i;
|
|
79
|
+
function s(t = {}) {
|
|
80
|
+
let n = e(), r = t.resolveUrl ?? i, s = t.resolveUrl === void 0;
|
|
71
81
|
n.addEventListener("notificationclick", (e) => {
|
|
72
82
|
e.notification.close();
|
|
73
|
-
let t = r(e.notification.data, e.action);
|
|
83
|
+
let t = a(r(e.notification.data, e.action), n.location?.origin, s);
|
|
74
84
|
e.waitUntil((async () => {
|
|
75
85
|
let e = await n.clients.matchAll({
|
|
76
86
|
type: "window",
|
|
77
87
|
includeUncontrolled: !0
|
|
78
88
|
});
|
|
79
|
-
for (let n of e) if (
|
|
89
|
+
for (let n of e) if (o(n.url, t)) return n.focus();
|
|
80
90
|
return n.clients.openWindow(t);
|
|
81
91
|
})());
|
|
82
92
|
});
|
|
83
93
|
}
|
|
84
|
-
function
|
|
94
|
+
function c() {
|
|
85
95
|
let t = e();
|
|
86
96
|
t.addEventListener("message", (e) => {
|
|
87
97
|
e.data?.type === "SKIP_WAITING" && t.skipWaiting();
|
|
88
98
|
});
|
|
89
99
|
}
|
|
90
100
|
//#endregion
|
|
91
|
-
export {
|
|
101
|
+
export { s as installNotificationClickHandler, r as installPushHandler, c as installSkipWaitingListener };
|
|
92
102
|
|
|
93
103
|
//# sourceMappingURL=create-push-handler.js.map
|