@alxia/react-router 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (56) hide show
  1. package/README.md +107 -8
  2. package/dist/chunks/index-zqa30gr7.js +139 -0
  3. package/dist/chunks/index-zqa30gr7.js.map +12 -0
  4. package/dist/pwa/index.d.ts +14 -0
  5. package/dist/pwa/index.d.ts.map +1 -0
  6. package/dist/pwa/index.js +165 -0
  7. package/dist/pwa/index.js.map +14 -0
  8. package/dist/pwa/model/caching.d.ts +46 -0
  9. package/dist/pwa/model/caching.d.ts.map +1 -0
  10. package/dist/pwa/model/clear-app-caches.d.ts +9 -0
  11. package/dist/pwa/model/clear-app-caches.d.ts.map +1 -0
  12. package/dist/pwa/model/manifest.d.ts +80 -0
  13. package/dist/pwa/model/manifest.d.ts.map +1 -0
  14. package/dist/pwa/model/precache-list.d.ts +40 -0
  15. package/dist/pwa/model/precache-list.d.ts.map +1 -0
  16. package/dist/pwa/model/registration.d.ts +39 -0
  17. package/dist/pwa/model/registration.d.ts.map +1 -0
  18. package/dist/pwa/model/start-service-worker.d.ts +24 -0
  19. package/dist/pwa/model/start-service-worker.d.ts.map +1 -0
  20. package/dist/pwa/model/storable.d.ts +8 -0
  21. package/dist/pwa/model/storable.d.ts.map +1 -0
  22. package/dist/pwa/model/update-notice.d.ts +38 -0
  23. package/dist/pwa/model/update-notice.d.ts.map +1 -0
  24. package/dist/pwa/model/worker-contract.d.ts +61 -0
  25. package/dist/pwa/model/worker-contract.d.ts.map +1 -0
  26. package/dist/pwa/react/index.d.ts +9 -0
  27. package/dist/pwa/react/index.d.ts.map +1 -0
  28. package/dist/pwa/react/index.js +75 -0
  29. package/dist/pwa/react/index.js.map +13 -0
  30. package/dist/pwa/react/use-online.d.ts +6 -0
  31. package/dist/pwa/react/use-online.d.ts.map +1 -0
  32. package/dist/pwa/react/use-service-worker.d.ts +33 -0
  33. package/dist/pwa/react/use-service-worker.d.ts.map +1 -0
  34. package/dist/pwa/react/use-update-notice.d.ts +14 -0
  35. package/dist/pwa/react/use-update-notice.d.ts.map +1 -0
  36. package/dist/pwa/server/index.d.ts +7 -0
  37. package/dist/pwa/server/index.d.ts.map +1 -0
  38. package/dist/pwa/server/index.js +18 -0
  39. package/dist/pwa/server/index.js.map +10 -0
  40. package/dist/pwa/server/no-cache.d.ts +22 -0
  41. package/dist/pwa/server/no-cache.d.ts.map +1 -0
  42. package/docs/README.md +12 -3
  43. package/docs/guide/beside-the-pages.md +178 -0
  44. package/docs/guide/csp-nonce.md +100 -0
  45. package/docs/guide/customising-the-server.md +292 -0
  46. package/docs/guide/deploying.md +99 -0
  47. package/docs/guide/getting-started.md +112 -0
  48. package/docs/guide/how-it-works.md +262 -0
  49. package/docs/guide/openapi.md +51 -0
  50. package/docs/guide/pwa.md +475 -0
  51. package/docs/guide/testing.md +42 -0
  52. package/docs/guide/typing-the-loaders.md +311 -0
  53. package/docs/guide.md +27 -1391
  54. package/docs/roadmap.md +25 -1
  55. package/docs/troubleshooting.md +195 -9
  56. package/package.json +32 -10
package/README.md CHANGED
@@ -92,7 +92,7 @@ Vite's preview server, as `bun run start` would: the pages, `/api`, the
92
92
  client's files and the app's middlewares. So does React Router's prerendering.
93
93
 
94
94
  `@alxia/core` and `react-router` 8 are peers, and `vite` 7 or 8 is an
95
- optional peer, for `/vite`. The package declares no dependency.
95
+ optional peer, for `/vite`; `react` 19 is an optional peer, for `/pwa/react`. The package declares no dependency.
96
96
 
97
97
  Its `typescript` peer is 6 or 7. The template's TypeScript 5.9 typechecks,
98
98
  but `bun add` warns about it; `bun add -d typescript@^6` silences the
@@ -246,7 +246,7 @@ the `Bun.Server` the app's sockets are relayed to, on a loopback port:
246
246
  It is not `Register`'s `server`, which names what `createServer()` returned
247
247
  for the types.
248
248
 
249
- [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#typing-the-loaders)
249
+ [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/typing-the-loaders.md#typing-the-loaders)
250
250
 
251
251
  ## A CSP nonce
252
252
 
@@ -293,7 +293,7 @@ bunx react-router reveal entry.server
293
293
  `nonceOf` returns `undefined` when no middleware set a nonce, so the entry works
294
294
  with or without secure-headers: neither package depends on the other. Any
295
295
  `derive` that returns a string `nonce` works the same.
296
- [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#a-csp-nonce)
296
+ [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/csp-nonce.md#a-csp-nonce)
297
297
 
298
298
  ## WebSockets
299
299
 
@@ -321,7 +321,7 @@ middlewares before the route, a refusal's status, `socket.data`, `publish`. An
321
321
  edit to the server is used from the next connection. An action's
322
322
  `alxiaOf(context).server.publish` reaches those sockets too
323
323
  ([The server](#the-server-server)).
324
- [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#websockets)
324
+ [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/beside-the-pages.md#websockets)
325
325
 
326
326
  ## Options
327
327
 
@@ -351,7 +351,7 @@ ignored. A server of your own gives `alxia({ proxy })` the same option. [More](h
351
351
  `build`, `mode` and `client` override what the plugin wires;
352
352
  `client: false` serves no client files, so you can serve them yourself.
353
353
  A server of your own, with no plugin, calls `reactRouter(app, { build,
354
- client })` itself. [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#escape-hatches)
354
+ client })` itself. [More](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/customising-the-server.md#escape-hatches)
355
355
 
356
356
  ## The client's files
357
357
 
@@ -439,7 +439,7 @@ export default defineConfig({
439
439
  ```
440
440
 
441
441
  The
442
- [guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#built-for-bun)
442
+ [guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/how-it-works.md#built-for-bun)
443
443
  has each option and why.
444
444
 
445
445
  ## Docker
@@ -474,9 +474,56 @@ docker run -p 3000:3000 my-app
474
474
 
475
475
  Keep the template's `.dockerignore`, and commit `bun.lock`. The server
476
476
  runs as the non-root `bun` user, on `PORT` (3000). The
477
- [guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md#docker)
477
+ [guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/deploying.md#docker)
478
478
  has the commented file, and what to copy for a package left external.
479
479
 
480
+ ## Installable and offline (PWA)
481
+
482
+ `@alxia/react-router/pwa` holds what an installable app shares and that has
483
+ no UI: the service worker's contract, which requests it caches and which
484
+ responses it may store, the precache list read from a build, the
485
+ registration and update notice, the web app manifest, and the cleaning at
486
+ sign-out. It imports nothing, so the worker, a build script, the page and
487
+ the server can all import it. The hooks are in `/pwa/react`
488
+ (`react`, an optional peer) and `noCache` in `/pwa/server`; this package
489
+ never reads `import.meta.env`, so the app passes `enabled`.
490
+
491
+ ```ts
492
+ // file: app/pwa-contract.ts, shared by the worker and the page:
493
+ // every cache is named `my-app-<kind>-<version>`.
494
+ import { defineWorkerContract } from '@alxia/react-router/pwa';
495
+
496
+ export const workerContract = defineWorkerContract({ cachePrefix: 'my-app' });
497
+ ```
498
+
499
+ ```tsx
500
+ // file: app/root.tsx
501
+ import { useServiceWorker } from '@alxia/react-router/pwa/react';
502
+ import { Outlet } from 'react-router';
503
+
504
+ // A production build only, never under `react-router dev`.
505
+ export default function App() {
506
+ useServiceWorker({ enabled: import.meta.env.PROD });
507
+ return <Outlet />;
508
+ }
509
+ ```
510
+
511
+ ```ts
512
+ // file: app/server.ts
513
+ import { createServer } from '@alxia/react-router';
514
+ import { SERVICE_WORKER_URL } from '@alxia/react-router/pwa';
515
+ import { noCache } from '@alxia/react-router/pwa/server';
516
+
517
+ // The worker's script is revalidated; beforeAll runs before the client's files.
518
+ export default createServer({
519
+ beforeAll: (app) => app.use(SERVICE_WORKER_URL, noCache),
520
+ });
521
+ ```
522
+
523
+ The [PWA guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/pwa.md)
524
+ has the worker's fetch handler, the manifest route, the update banner and the
525
+ rest.
526
+
480
527
  ## Traps
481
528
 
482
529
  - **A context key made in `app/` reaches the loaders only through the
@@ -530,6 +577,52 @@ From `@alxia/react-router/vite`:
530
577
  | `alxia(options?)` | the Vite plugin, for `react-router dev`, `react-router build` and `vite preview`, building the server for Bun. `entry` is the server file: `app/server.ts` by default, or the default server when there is none |
531
578
  | `AlxiaOptions` | its options |
532
579
 
580
+ From `@alxia/react-router/pwa`:
581
+
582
+ | export | |
583
+ | --- | --- |
584
+ | `defineWorkerContract(options)` | the contract of one app's worker from its `cachePrefix` (required), with `cacheVersion` (`v1`), `serviceWorkerUrl` and `offlineUrl`: its `cacheNames`, `obsoleteCaches(names)` and `clearableCaches(names)`. Throws on an empty prefix or one ending with `-`, and on an empty `cacheVersion` or one with a `-` |
585
+ | `WorkerContract`, `WorkerContractOptions`, `CacheNames` | its types |
586
+ | `SERVICE_WORKER_URL`, `OFFLINE_URL`, `SKIP_WAITING` | `/service-worker.js`, `/offline.html` and the message that applies an update |
587
+ | `cachingFor(request, origin, options?)` | `navigation`, `immutable`, `static` or `none` for a request. `options`: `never(path)`, `immutable(path)` (everything under `/assets/` by default), `serviceWorkerUrl`, `contract` (whose `serviceWorkerUrl` is never cached), `manifestPath` |
588
+ | `Caching`, `RequestFacts`, `CachingOptions` | its types |
589
+ | `isStorable(response)` | whether a response may be stored: a full, same-origin, `public` 200 of a script, style, font or image, not varying on who asks |
590
+ | `parseBrowserManifest(source)` | React Router's browser manifest, read from `build/client/assets/manifest-*.js` |
591
+ | `precacheList(manifest)` | the hashed scripts and stylesheets every route needs, as Workbox precache entries |
592
+ | `assetPrecache(files, include, base?)` | the entries of the files in `build/client/assets` that a regex of yours matches, such as fonts |
593
+ | `BrowserManifest`, `PrecacheEntry` | their types |
594
+ | `clearAppCaches(contract)` | deletes the caches the worker filled but the offline page; for sign-out. Does nothing without Cache Storage |
595
+ | `registerServiceWorker(container, onWaiting, options?)` | registers the worker (`url`, `scope` options) and calls `onWaiting(worker, apply)` for each new version waiting behind a controller; checks for an update when the tab shows again. `apply` activates the worker and reloads once |
596
+ | `watchForUpdates(registration, container, onWaiting)`, `createUpdater(container, reload?)`, `afterLoad(run)` | its parts: offering waiting workers, the one update path of a page, and running once the page has loaded |
597
+ | `ApplyUpdate`, `RegisterOptions`, `ServiceWorkerContainerLike` | their types |
598
+ | `createUpdateNotice(storage, key?)` | the update notice's store (`offer`, `current`, `subscribe`, as `useSyncExternalStore` takes it): hidden for the session by "Later", kept in `storage()`, which may be missing or throw |
599
+ | `UpdateNotice`, `UpdateNoticeStore`, `NoticeStorage` | its types. `NoticeStorage` is any object with `getItem(key)` and `setItem(key, value)` |
600
+ | `sessionStore`, `UPDATE_LATER_KEY` | `sessionStorage` or nothing, and the default key, `alxia.update-later` |
601
+ | `MANIFEST_PATH`, `MANIFEST_HEADERS` | `/manifest.webmanifest`, and the headers to serve it with: cacheable for an hour, varying on `Accept-Language`, a policy that loads nothing |
602
+ | `webAppManifest(options)` | the web app manifest's JSON from the app's `name`, `description`, `lang`, `themeColor`, `backgroundColor` and the optional rest; `id`, `start_url` and `scope` are `/` in every language |
603
+ | `WebAppManifest`, `WebAppManifestOptions`, `ManifestIcon` | its types |
604
+ | `MINIMAL_2023_ICONS` | the four icons `@vite-pwa/assets-generator`'s `minimal2023Preset` writes |
605
+ | `manifestHref(language, path?)` | the manifest's URL for a page: `/manifest.webmanifest?lang=fr` |
606
+ | `manifestLanguage(url, supported, fallback)` | `?lang=` when it is one of `supported`, else `fallback` |
607
+
608
+ From `@alxia/react-router/pwa/react` (`react`, an optional peer, is its only package import):
609
+
610
+ | export | |
611
+ | --- | --- |
612
+ | `useOnline()` | whether the browser says it is online; online on the server and while hydrating |
613
+ | `useServiceWorker(options)` | registers the worker after `load` when `options.enabled` (required; the app passes `import.meta.env.PROD`), in a browser that has one. `url`, `scope`, `notice` (where a waiting version is offered), `onError` (registering failed; dropped by default) |
614
+ | `ServiceWorkerOptions` | its options |
615
+ | `useUpdateNotice(notice?)` | the `{ reload, later }` to show while a new version waits and "Later" was not asked, else `undefined`; always `undefined` on the server |
616
+ | `updateNotice` | the page's one `UpdateNoticeStore`, which `useServiceWorker` offers and `useUpdateNotice` reads by default |
617
+
618
+ From `@alxia/react-router/pwa/server` (`@alxia/core` is its only package import):
619
+
620
+ | export | |
621
+ | --- | --- |
622
+ | `noCache` | a middleware: the response it wraps gets `Cache-Control: no-cache` and the worker's policy. For `use(SERVICE_WORKER_URL, noCache)` in `beforeAll` |
623
+ | `revalidated(response, policy?)` | a response with those two headers set, on a copy when its headers are immutable; `policy` is `WORKER_POLICY` by default |
624
+ | `WORKER_POLICY` | `default-src 'none'; connect-src 'self'`: the worker runs no other script and fetches this site only |
625
+
533
626
  The `alxia-react-router` bin, run with `bunx`:
534
627
 
535
628
  | command | |
@@ -538,7 +631,13 @@ The `alxia-react-router` bin, run with `bunx`:
538
631
 
539
632
  ## Documentation
540
633
 
541
- - [Guide](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide.md): the setup, how dev, the build and `vite preview` work, what the build sets for Bun, customising the server, typing the loaders, the app's own keys, a CSP nonce, escape hatches, WebSockets, the client's files, OpenAPI, testing and deploying, with Docker.
634
+ - [Documentation index](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/README.md): every page, and when to read it.
635
+ - [Getting started](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/getting-started.md): a new app, the official template or an existing app.
636
+ - [How it works](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/how-it-works.md): dev, the build and `vite preview`, and what the build sets for Bun.
637
+ - [Customising the server](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/customising-the-server.md): `createServer()`, and the escape hatches.
638
+ - [Typing the loaders](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/typing-the-loaders.md): `Register`, and the app's own context keys.
639
+ - [A CSP nonce](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/csp-nonce.md), [Beside the pages](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/beside-the-pages.md) (middlewares, routes, WebSockets, the client's files), [OpenAPI](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/openapi.md), [Testing](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/testing.md) and [Deploying](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/deploying.md), with Docker.
640
+ - [Installable and offline](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/guide/pwa.md): what `/pwa` holds, the worker's contract, what the worker caches, the precache list from a build, registering the worker and offering an update, the web app manifest, the hooks in React, and clearing at sign-out.
542
641
  - [Troubleshooting](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/troubleshooting.md): each message, and the traps that print none.
543
642
  - [Roadmap](https://github.com/softistx/alxia/blob/develop/packages/react-router/docs/roadmap.md): what is coming, and what is not planned.
544
643
  - [Example](https://github.com/softistx/alxia/tree/develop/examples/react-router): the official template, these four changes, then an `app/server.ts` with a session, an `/api`, secure headers with a nonce and a streamed page.
@@ -0,0 +1,139 @@
1
+ // src/pwa/model/worker-contract.ts
2
+ var SERVICE_WORKER_URL2 = "/service-worker.js";
3
+ var OFFLINE_URL2 = "/offline.html";
4
+ var SKIP_WAITING2 = "SKIP_WAITING";
5
+ function defineWorkerContract2(options) {
6
+ const { cachePrefix } = options;
7
+ if (typeof cachePrefix !== "string" || cachePrefix === "") {
8
+ throw new TypeError(`defineWorkerContract(): cachePrefix must be a non-empty name, such as 'my-app'; got '${String(cachePrefix)}'.`);
9
+ }
10
+ if (cachePrefix.endsWith("-")) {
11
+ throw new TypeError(`defineWorkerContract(): cachePrefix must not end with '-', which the contract adds; got '${cachePrefix}'. Use '${cachePrefix.replace(/-+$/, "")}'.`);
12
+ }
13
+ const cacheVersion = options.cacheVersion ?? "v1";
14
+ if (typeof cacheVersion !== "string" || cacheVersion === "" || cacheVersion.includes("-")) {
15
+ throw new TypeError(`defineWorkerContract(): cacheVersion must be a non-empty name without '-', such as 'v2'; got '${String(cacheVersion)}'.`);
16
+ }
17
+ const cacheNames = Object.freeze({
18
+ precache: `${cachePrefix}-precache-${cacheVersion}`,
19
+ assets: `${cachePrefix}-assets-${cacheVersion}`,
20
+ static: `${cachePrefix}-static-${cacheVersion}`
21
+ });
22
+ const current = new Set(Object.values(cacheNames));
23
+ const shape = new RegExp(`^${cachePrefix.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}-[a-z0-9]+-[^-]+$`);
24
+ const owned = (name) => shape.test(name);
25
+ const contract = {
26
+ cachePrefix,
27
+ cacheVersion,
28
+ serviceWorkerUrl: options.serviceWorkerUrl ?? SERVICE_WORKER_URL2,
29
+ offlineUrl: options.offlineUrl ?? OFFLINE_URL2,
30
+ cacheNames,
31
+ obsoleteCaches: (names) => names.filter((name) => owned(name) && !current.has(name)),
32
+ clearableCaches: (names) => names.filter((name) => owned(name) && name !== cacheNames.precache)
33
+ };
34
+ return Object.freeze(contract);
35
+ }
36
+
37
+ // src/pwa/model/registration.ts
38
+ function watchForUpdates2(registration, container, onWaiting) {
39
+ const offer = (worker) => {
40
+ if (worker && container.controller)
41
+ onWaiting(worker);
42
+ };
43
+ offer(registration.waiting);
44
+ registration.addEventListener("updatefound", () => {
45
+ const installing = registration.installing;
46
+ installing?.addEventListener("statechange", () => {
47
+ if (installing.state === "installed")
48
+ offer(installing);
49
+ });
50
+ });
51
+ }
52
+ function createUpdater2(container, reload = () => window.location.reload()) {
53
+ let requested = false;
54
+ let reloaded = false;
55
+ const reloadOnce = () => {
56
+ if (reloaded)
57
+ return;
58
+ reloaded = true;
59
+ reload();
60
+ };
61
+ container.addEventListener("controllerchange", () => {
62
+ if (requested)
63
+ reloadOnce();
64
+ });
65
+ return (worker) => {
66
+ const active = worker.state === "activating" || worker.state === "activated" || container.controller === worker;
67
+ if (active)
68
+ return reloadOnce();
69
+ requested = true;
70
+ worker.postMessage({ type: SKIP_WAITING2 });
71
+ };
72
+ }
73
+ function afterLoad2(run) {
74
+ if (document.readyState === "complete")
75
+ run();
76
+ else
77
+ window.addEventListener("load", run, { once: true });
78
+ }
79
+ var updater;
80
+ async function registerServiceWorker2(container, onWaiting, options = {}) {
81
+ updater ??= createUpdater2(container);
82
+ const apply = updater;
83
+ const registration = await container.register(options.url ?? SERVICE_WORKER_URL2, { scope: options.scope ?? "/" });
84
+ watchForUpdates2(registration, container, (worker) => onWaiting(worker, apply));
85
+ document.addEventListener("visibilitychange", () => {
86
+ if (document.visibilityState === "visible")
87
+ registration.update().catch(() => {});
88
+ });
89
+ return registration;
90
+ }
91
+
92
+ // src/pwa/model/update-notice.ts
93
+ var UPDATE_LATER_KEY2 = "alxia.update-later";
94
+ function createUpdateNotice2(storage, key = UPDATE_LATER_KEY2) {
95
+ let reload;
96
+ let laterThisPage = false;
97
+ let notice;
98
+ const listeners = new Set;
99
+ const later = () => {
100
+ laterThisPage = true;
101
+ try {
102
+ storage()?.setItem(key, "1");
103
+ } catch {}
104
+ refresh();
105
+ };
106
+ const laterAsked = () => {
107
+ if (laterThisPage)
108
+ return true;
109
+ try {
110
+ return storage()?.getItem(key) === "1";
111
+ } catch {
112
+ return false;
113
+ }
114
+ };
115
+ function refresh() {
116
+ notice = reload && !laterAsked() ? { reload, later } : undefined;
117
+ for (const listener of listeners)
118
+ listener();
119
+ }
120
+ return {
121
+ offer(apply) {
122
+ reload = apply;
123
+ refresh();
124
+ },
125
+ current: () => notice,
126
+ subscribe(listener) {
127
+ listeners.add(listener);
128
+ return () => {
129
+ listeners.delete(listener);
130
+ };
131
+ }
132
+ };
133
+ }
134
+ var sessionStore2 = () => typeof sessionStorage === "undefined" ? undefined : sessionStorage;
135
+
136
+ export { SERVICE_WORKER_URL2, OFFLINE_URL2, SKIP_WAITING2, defineWorkerContract2, watchForUpdates2, createUpdater2, afterLoad2, registerServiceWorker2, UPDATE_LATER_KEY2, createUpdateNotice2, sessionStore2 };
137
+
138
+ //# debugId=E59639D8397E531464756E2164756E21
139
+ //# sourceMappingURL=index-zqa30gr7.js.map
@@ -0,0 +1,12 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/pwa/model/worker-contract.ts", "../src/pwa/model/registration.ts", "../src/pwa/model/update-notice.ts"],
4
+ "sourcesContent": [
5
+ "/**\n * What the service worker and the page agree on: the worker's address, the\n * offline page, the message that applies an update, and the names of the\n * caches the worker owns. Dependency-free: the worker bundles it, with no\n * DOM and no React.\n */\n\n/** Where the build writes the worker; registered with scope `/`. */\nexport const SERVICE_WORKER_URL = '/service-worker.js';\n\n/** The page served when a navigation fails for want of a network. */\nexport const OFFLINE_URL = '/offline.html';\n\n/** The message a page posts to a waiting worker to make it take over. */\nexport const SKIP_WAITING = 'SKIP_WAITING';\n\nexport interface WorkerContractOptions {\n\t/**\n\t * Every Cache Storage cache the worker owns is named\n\t * `<cachePrefix>-<kind>-<version>` (kind: letters and digits; version:\n\t * no `-`); a cache of another shape is never swept.\n\t * Required: two apps on one origin must not share it.\n\t */\n\tcachePrefix: string;\n\t/**\n\t * Bumped when a cache's content changes shape: the next worker deletes\n\t * every cache of the prefix that has another version when it activates.\n\t * `'v1'` by default; not empty, and without a `-`.\n\t */\n\tcacheVersion?: string;\n\t/** `SERVICE_WORKER_URL` by default. */\n\tserviceWorkerUrl?: string;\n\t/** `OFFLINE_URL` by default. */\n\tofflineUrl?: string;\n}\n\nexport type CacheNames = {\n\t/**\n\t * The offline page and the hashed scripts and stylesheets of every route.\n\t * `<prefix>-precache-<version>` is the name Workbox builds from\n\t * `setCacheNameDetails({ prefix, precache: 'precache', suffix: version })`.\n\t */\n\treadonly precache: string;\n\t/** `/assets/*` bundles: hashed names, so cache-first. */\n\treadonly assets: string;\n\t/** Icons, sprites, the logo: stale-while-revalidate. */\n\treadonly static: string;\n};\n\nexport interface WorkerContract {\n\treadonly cachePrefix: string;\n\treadonly cacheVersion: string;\n\treadonly serviceWorkerUrl: string;\n\treadonly offlineUrl: string;\n\treadonly cacheNames: CacheNames;\n\t/** The caches a worker deletes on activate: the prefix's, of another version. */\n\tobsoleteCaches(names: readonly string[]): string[];\n\t/**\n\t * The caches `clearAppCaches()` deletes: all of the prefix's but the\n\t * current precache, which holds the offline page and the app's code, never\n\t * a user's data.\n\t */\n\tclearableCaches(names: readonly string[]): string[];\n}\n\n/** Builds the contract of one app from its cache prefix. */\nexport function defineWorkerContract(\n\toptions: WorkerContractOptions,\n): WorkerContract {\n\tconst { cachePrefix } = options;\n\tif (typeof cachePrefix !== 'string' || cachePrefix === '') {\n\t\tthrow new TypeError(\n\t\t\t`defineWorkerContract(): cachePrefix must be a non-empty name, such as 'my-app'; got '${String(cachePrefix)}'.`,\n\t\t);\n\t}\n\tif (cachePrefix.endsWith('-')) {\n\t\tthrow new TypeError(\n\t\t\t`defineWorkerContract(): cachePrefix must not end with '-', which the contract adds; got '${cachePrefix}'. Use '${cachePrefix.replace(/-+$/, '')}'.`,\n\t\t);\n\t}\n\tconst cacheVersion = options.cacheVersion ?? 'v1';\n\tif (\n\t\ttypeof cacheVersion !== 'string' ||\n\t\tcacheVersion === '' ||\n\t\tcacheVersion.includes('-')\n\t) {\n\t\tthrow new TypeError(\n\t\t\t`defineWorkerContract(): cacheVersion must be a non-empty name without '-', such as 'v2'; got '${String(cacheVersion)}'.`,\n\t\t);\n\t}\n\tconst cacheNames: CacheNames = Object.freeze({\n\t\tprecache: `${cachePrefix}-precache-${cacheVersion}`,\n\t\tassets: `${cachePrefix}-assets-${cacheVersion}`,\n\t\tstatic: `${cachePrefix}-static-${cacheVersion}`,\n\t});\n\tconst current = new Set<string>(Object.values(cacheNames));\n\t// Exactly `<prefix>-<kind>-<version>`: the prefix 'app' must not claim\n\t// 'app-admin-assets-v1' (one segment too many), but still owns a retired\n\t// kind such as 'app-pages-v0'.\n\tconst shape = new RegExp(\n\t\t`^${cachePrefix.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}-[a-z0-9]+-[^-]+$`,\n\t);\n\tconst owned = (name: string) => shape.test(name);\n\tconst contract: WorkerContract = {\n\t\tcachePrefix,\n\t\tcacheVersion,\n\t\tserviceWorkerUrl: options.serviceWorkerUrl ?? SERVICE_WORKER_URL,\n\t\tofflineUrl: options.offlineUrl ?? OFFLINE_URL,\n\t\tcacheNames,\n\t\tobsoleteCaches: (names) =>\n\t\t\tnames.filter((name) => owned(name) && !current.has(name)),\n\t\tclearableCaches: (names) =>\n\t\t\tnames.filter((name) => owned(name) && name !== cacheNames.precache),\n\t};\n\treturn Object.freeze(contract);\n}\n",
6
+ "import { SERVICE_WORKER_URL, SKIP_WAITING } from './worker-contract';\n\n/** What the registration reads of `navigator.serviceWorker`. */\nexport type ServiceWorkerContainerLike = Pick<\n\tServiceWorkerContainer,\n\t'controller' | 'addEventListener' | 'register'\n>;\n\n/**\n * Calls `onWaiting` with each new version that finished installing while an\n * older one controls the page: the one waiting now, and each found later.\n * A first install controls nothing yet, so it offers nothing.\n */\nexport function watchForUpdates(\n\tregistration: ServiceWorkerRegistration,\n\tcontainer: Pick<ServiceWorkerContainerLike, 'controller'>,\n\tonWaiting: (worker: ServiceWorker) => void,\n): void {\n\tconst offer = (worker: ServiceWorker | null) => {\n\t\tif (worker && container.controller) onWaiting(worker);\n\t};\n\toffer(registration.waiting);\n\tregistration.addEventListener('updatefound', () => {\n\t\tconst installing = registration.installing;\n\t\tinstalling?.addEventListener('statechange', () => {\n\t\t\tif (installing.state === 'installed') offer(installing);\n\t\t});\n\t});\n}\n\n/** Moves this page onto a waiting worker: `apply(worker)`. */\nexport type ApplyUpdate = (\n\tworker: Pick<ServiceWorker, 'postMessage' | 'state'>,\n) => void;\n\n/**\n * The one update path of a page, created once: `apply(worker)` activates\n * the waiting worker and reloads once, when it takes control, so the page\n * runs the version its assets came from.\n *\n * - Several tabs: another tab may have activated the worker already. Its\n * `controllerchange` reached this page before anyone asked, so this page\n * did not reload (a form may be half filled); its \"Reload\" then reloads\n * at once, the worker being active.\n * - Clicked twice, or a `controllerchange` after a direct reload: one\n * reload, never two.\n */\nexport function createUpdater(\n\tcontainer: Pick<\n\t\tServiceWorkerContainerLike,\n\t\t'controller' | 'addEventListener'\n\t>,\n\treload: () => void = () => window.location.reload(),\n): ApplyUpdate {\n\tlet requested = false;\n\tlet reloaded = false;\n\tconst reloadOnce = () => {\n\t\tif (reloaded) return;\n\t\treloaded = true;\n\t\treload();\n\t};\n\tcontainer.addEventListener('controllerchange', () => {\n\t\tif (requested) reloadOnce();\n\t});\n\treturn (worker) => {\n\t\tconst active =\n\t\t\tworker.state === 'activating' ||\n\t\t\tworker.state === 'activated' ||\n\t\t\tcontainer.controller === worker;\n\t\tif (active) return reloadOnce();\n\t\trequested = true;\n\t\tworker.postMessage({ type: SKIP_WAITING });\n\t};\n}\n\n/** Runs `run` once the page has loaded: registering never competes with it. */\nexport function afterLoad(run: () => void): void {\n\tif (document.readyState === 'complete') run();\n\telse window.addEventListener('load', run, { once: true });\n}\n\nexport interface RegisterOptions {\n\t/** The worker's path. `SERVICE_WORKER_URL` by default. */\n\turl?: string;\n\t/** The scope it controls. `'/'` by default. */\n\tscope?: string;\n}\n\nlet updater: ApplyUpdate | undefined;\n\n/**\n * Registers the worker and watches for updates, handing each waiting worker\n * to `onWaiting` with the page's one `apply` (`createUpdater`, made on the\n * first call: one per page). A tab coming back to the foreground checks for\n * an update, as a long-lived page otherwise would only on its next full load.\n */\nexport async function registerServiceWorker(\n\tcontainer: ServiceWorkerContainerLike,\n\tonWaiting: (worker: ServiceWorker, apply: ApplyUpdate) => void,\n\toptions: RegisterOptions = {},\n): Promise<ServiceWorkerRegistration> {\n\tupdater ??= createUpdater(container);\n\tconst apply = updater;\n\tconst registration = await container.register(\n\t\toptions.url ?? SERVICE_WORKER_URL,\n\t\t{ scope: options.scope ?? '/' },\n\t);\n\twatchForUpdates(registration, container, (worker) =>\n\t\tonWaiting(worker, apply),\n\t);\n\tdocument.addEventListener('visibilitychange', () => {\n\t\tif (document.visibilityState === 'visible')\n\t\t\tregistration.update().catch(() => {\n\t\t\t\t// Offline, or the server is down: the next check will tell.\n\t\t\t});\n\t});\n\treturn registration;\n}\n",
7
+ "/**\n * The session's \"Later\": the notice stays hidden until the next visit.\n * `sessionStorage` is per origin, so one key serves every app; a second\n * argument of `createUpdateNotice` names another.\n */\nexport const UPDATE_LATER_KEY = 'alxia.update-later';\n\n/** The part of `Storage` the notice reads and writes. */\nexport type NoticeStorage = {\n\tgetItem(key: string): string | null;\n\tsetItem(key: string, value: string): void;\n};\n\n/** What the notice offers while a new version waits. */\nexport type UpdateNotice = {\n\t/** Moves the page onto the new version: the worker's `apply`. */\n\treload: () => void;\n\t/** Hides the notice until the next visit (the tab's session). */\n\tlater: () => void;\n};\n\n/** What `createUpdateNotice` returns: the shape `useSyncExternalStore` takes. */\nexport interface UpdateNoticeStore {\n\t/** A new version waits: `apply` moves the page onto it. */\n\toffer(apply: () => void): void;\n\t/** The notice to show, if any (the same object until it changes). */\n\tcurrent(): UpdateNotice | undefined;\n\t/** Calls `listener` on each change; returns the function that stops it. */\n\tsubscribe(listener: () => void): () => void;\n}\n\n/**\n * The update notice's state: offered by the worker's registration, read by\n * the banners, hidden for the session by \"Later\". `storage` is the session's\n * (`sessionStorage`), which may be missing or throw (private mode, blocked\n * storage): the notice then hides for this page only. It is a function so\n * that nothing reads the global at load.\n */\nexport function createUpdateNotice(\n\tstorage: () => NoticeStorage | undefined,\n\tkey: string = UPDATE_LATER_KEY,\n): UpdateNoticeStore {\n\tlet reload: (() => void) | undefined;\n\tlet laterThisPage = false;\n\tlet notice: UpdateNotice | undefined;\n\tconst listeners = new Set<() => void>();\n\n\tconst later = () => {\n\t\tlaterThisPage = true;\n\t\ttry {\n\t\t\tstorage()?.setItem(key, '1');\n\t\t} catch {\n\t\t\t// No storage: hidden for this page only.\n\t\t}\n\t\trefresh();\n\t};\n\tconst laterAsked = () => {\n\t\tif (laterThisPage) return true;\n\t\ttry {\n\t\t\treturn storage()?.getItem(key) === '1';\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t};\n\tfunction refresh() {\n\t\tnotice = reload && !laterAsked() ? { reload, later } : undefined;\n\t\tfor (const listener of listeners) listener();\n\t}\n\n\treturn {\n\t\toffer(apply) {\n\t\t\treload = apply;\n\t\t\trefresh();\n\t\t},\n\t\tcurrent: () => notice,\n\t\tsubscribe(listener) {\n\t\t\tlisteners.add(listener);\n\t\t\treturn () => {\n\t\t\t\tlisteners.delete(listener);\n\t\t\t};\n\t\t},\n\t};\n}\n\n/** `sessionStorage`, or nothing where there is none (the server). */\nexport const sessionStore = (): NoticeStorage | undefined =>\n\ttypeof sessionStorage === 'undefined' ? undefined : sessionStorage;\n"
8
+ ],
9
+ "mappings": "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",
10
+ "debugId": "E59639D8397E531464756E2164756E21",
11
+ "names": []
12
+ }
@@ -0,0 +1,14 @@
1
+ /**
2
+ * `@alxia/react-router/pwa`: the code with no UI an installable app shares.
3
+ * No import at all, so the page, the service worker, a build script and the
4
+ * server can each import it.
5
+ */
6
+ export { type Caching, type CachingOptions, cachingFor, type RequestFacts, } from './model/caching';
7
+ export { clearAppCaches } from './model/clear-app-caches';
8
+ export { MANIFEST_HEADERS, MANIFEST_PATH, type ManifestIcon, MINIMAL_2023_ICONS, manifestHref, manifestLanguage, type WebAppManifest, type WebAppManifestOptions, webAppManifest, } from './model/manifest';
9
+ export { assetPrecache, type BrowserManifest, type PrecacheEntry, parseBrowserManifest, precacheList, } from './model/precache-list';
10
+ export { type ApplyUpdate, afterLoad, createUpdater, type RegisterOptions, registerServiceWorker, type ServiceWorkerContainerLike, watchForUpdates, } from './model/registration';
11
+ export { isStorable } from './model/storable';
12
+ export { createUpdateNotice, type NoticeStorage, sessionStore, UPDATE_LATER_KEY, type UpdateNotice, type UpdateNoticeStore, } from './model/update-notice';
13
+ export { type CacheNames, defineWorkerContract, OFFLINE_URL, SERVICE_WORKER_URL, SKIP_WAITING, type WorkerContract, type WorkerContractOptions, } from './model/worker-contract';
14
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pwa/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EACN,KAAK,OAAO,EACZ,KAAK,cAAc,EACnB,UAAU,EACV,KAAK,YAAY,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EAAE,cAAc,EAAE,MAAM,0BAA0B,CAAC;AAC1D,OAAO,EACN,gBAAgB,EAChB,aAAa,EACb,KAAK,YAAY,EACjB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EAChB,KAAK,cAAc,EACnB,KAAK,qBAAqB,EAC1B,cAAc,GACd,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EACN,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,aAAa,EAClB,oBAAoB,EACpB,YAAY,GACZ,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,KAAK,WAAW,EAChB,SAAS,EACT,aAAa,EACb,KAAK,eAAe,EACpB,qBAAqB,EACrB,KAAK,0BAA0B,EAC/B,eAAe,GACf,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAC9C,OAAO,EACN,kBAAkB,EAClB,KAAK,aAAa,EAClB,YAAY,EACZ,gBAAgB,EAChB,KAAK,YAAY,EACjB,KAAK,iBAAiB,GACtB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EACN,KAAK,UAAU,EACf,oBAAoB,EACpB,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,KAAK,cAAc,EACnB,KAAK,qBAAqB,GAC1B,MAAM,yBAAyB,CAAC"}
@@ -0,0 +1,165 @@
1
+ import {
2
+ SERVICE_WORKER_URL2,
3
+ OFFLINE_URL2,
4
+ SKIP_WAITING2,
5
+ defineWorkerContract2,
6
+ watchForUpdates2,
7
+ createUpdater2,
8
+ afterLoad2,
9
+ registerServiceWorker2,
10
+ UPDATE_LATER_KEY2,
11
+ createUpdateNotice2,
12
+ sessionStore2
13
+ } from "../chunks/index-zqa30gr7.js";
14
+
15
+ // src/pwa/model/manifest.ts
16
+ var MANIFEST_PATH = "/manifest.webmanifest";
17
+ var MANIFEST_HEADERS = {
18
+ "content-type": "application/manifest+json; charset=utf-8",
19
+ "cache-control": "public, max-age=3600",
20
+ vary: "Accept-Language",
21
+ "content-security-policy": "default-src 'none'; frame-ancestors 'none'"
22
+ };
23
+ function manifestHref(language, path = MANIFEST_PATH) {
24
+ return `${path}?lang=${encodeURIComponent(language)}`;
25
+ }
26
+ function manifestLanguage(url, supported, fallback) {
27
+ const requested = url.searchParams.get("lang");
28
+ return supported.find((language) => language === requested) ?? fallback;
29
+ }
30
+ var MINIMAL_2023_ICONS = Object.freeze([
31
+ { src: "/pwa-64x64.png", sizes: "64x64", type: "image/png" },
32
+ { src: "/pwa-192x192.png", sizes: "192x192", type: "image/png" },
33
+ { src: "/pwa-512x512.png", sizes: "512x512", type: "image/png" },
34
+ {
35
+ src: "/maskable-icon-512x512.png",
36
+ sizes: "512x512",
37
+ type: "image/png",
38
+ purpose: "maskable"
39
+ }
40
+ ].map((icon) => Object.freeze(icon)));
41
+ function webAppManifest(options) {
42
+ return {
43
+ id: options.id ?? "/",
44
+ name: options.name,
45
+ short_name: options.shortName ?? options.name,
46
+ description: options.description,
47
+ lang: options.lang,
48
+ dir: options.dir ?? "ltr",
49
+ start_url: options.startUrl ?? "/",
50
+ scope: options.scope ?? "/",
51
+ display: options.display ?? "standalone",
52
+ theme_color: options.themeColor,
53
+ background_color: options.backgroundColor,
54
+ ...options.categories && { categories: [...options.categories] },
55
+ icons: (options.icons ?? MINIMAL_2023_ICONS).map((icon) => ({ ...icon }))
56
+ };
57
+ }
58
+
59
+ // src/pwa/model/caching.ts
60
+ var STATIC_EXTENSION = /\.(?:js|mjs|css|woff2?|ttf|otf|svg|png|jpe?g|webp|avif|gif|ico)$/i;
61
+ var underAssets = (path) => path.startsWith("/assets/");
62
+ function cachingFor(request, origin, options = {}) {
63
+ if (request.method !== "GET")
64
+ return "none";
65
+ if (request.mode === "navigate")
66
+ return "navigation";
67
+ const url = new URL(request.url);
68
+ if (url.origin !== origin)
69
+ return "none";
70
+ if (request.headers.has("authorization"))
71
+ return "none";
72
+ if (url.search)
73
+ return "none";
74
+ const path = url.pathname;
75
+ if (path.endsWith(".data") || path === "/__manifest" || path === (options.serviceWorkerUrl ?? options.contract?.serviceWorkerUrl ?? SERVICE_WORKER_URL2) || path === (options.manifestPath ?? MANIFEST_PATH) || options.never?.(path)) {
76
+ return "none";
77
+ }
78
+ if (request.destination === "document" || request.destination === "iframe")
79
+ return "none";
80
+ if (!STATIC_EXTENSION.test(path))
81
+ return "none";
82
+ return (options.immutable ?? underAssets)(path) ? "immutable" : "static";
83
+ }
84
+ // src/pwa/model/clear-app-caches.ts
85
+ async function clearAppCaches(contract) {
86
+ if (typeof caches === "undefined")
87
+ return;
88
+ const names = contract.clearableCaches(await caches.keys());
89
+ await Promise.all(names.map((name) => caches.delete(name)));
90
+ }
91
+ // src/pwa/model/precache-list.ts
92
+ var HASHED_ASSET = /^\/assets\/[^/]+-[\w-]{8}\.(?:js|css)$/;
93
+ function parseBrowserManifest(source) {
94
+ const start = source.indexOf("{");
95
+ const end = source.lastIndexOf("}");
96
+ if (start < 0 || end < start) {
97
+ throw new Error("parseBrowserManifest(): not React Router's browser manifest (build/client/assets/manifest-*.js).");
98
+ }
99
+ return JSON.parse(source.slice(start, end + 1));
100
+ }
101
+ function precacheList(manifest) {
102
+ const urls = new Set([manifest.url]);
103
+ for (const part of [manifest.entry, ...Object.values(manifest.routes)]) {
104
+ for (const url of [
105
+ ...part.module ? [part.module] : [],
106
+ ...part.imports ?? [],
107
+ ...part.css ?? []
108
+ ]) {
109
+ urls.add(url);
110
+ }
111
+ }
112
+ return [...urls].filter((url) => HASHED_ASSET.test(url)).map((url) => ({ url, revision: null }));
113
+ }
114
+ function assetPrecache(assetFiles, include, base = "/assets/") {
115
+ return assetFiles.filter((file) => {
116
+ include.lastIndex = 0;
117
+ return include.test(file);
118
+ }).sort().map((file) => ({ url: `${base}${file}`, revision: null }));
119
+ }
120
+ // src/pwa/model/storable.ts
121
+ var STATIC_CONTENT_TYPE = /^(?:(?:text|application)\/(?:javascript|x-javascript|ecmascript)|text\/css|font\/[\w.+-]+|application\/(?:font-woff2?|x-font-[\w.+-]+|vnd\.ms-fontobject)|image\/[\w.+-]+)$/i;
122
+ function isStorable(response) {
123
+ if (response.status !== 200)
124
+ return false;
125
+ if (response.type !== "basic" && response.type !== "default")
126
+ return false;
127
+ const contentType = (response.headers.get("content-type") ?? "").split(";")[0]?.trim();
128
+ if (!contentType || !STATIC_CONTENT_TYPE.test(contentType))
129
+ return false;
130
+ const cacheControl = response.headers.get("cache-control") ?? "";
131
+ if (!/\bpublic\b/i.test(cacheControl))
132
+ return false;
133
+ if (/\b(?:no-store|private)\b/i.test(cacheControl))
134
+ return false;
135
+ const vary = response.headers.get("vary") ?? "";
136
+ return !/(?:^|,)\s*(?:\*|cookie|authorization)\s*(?:,|$)/i.test(vary);
137
+ }
138
+ export {
139
+ MANIFEST_HEADERS,
140
+ MANIFEST_PATH,
141
+ MINIMAL_2023_ICONS,
142
+ OFFLINE_URL2 as OFFLINE_URL,
143
+ SERVICE_WORKER_URL2 as SERVICE_WORKER_URL,
144
+ SKIP_WAITING2 as SKIP_WAITING,
145
+ UPDATE_LATER_KEY2 as UPDATE_LATER_KEY,
146
+ afterLoad2 as afterLoad,
147
+ assetPrecache,
148
+ cachingFor,
149
+ clearAppCaches,
150
+ createUpdateNotice2 as createUpdateNotice,
151
+ createUpdater2 as createUpdater,
152
+ defineWorkerContract2 as defineWorkerContract,
153
+ isStorable,
154
+ manifestHref,
155
+ manifestLanguage,
156
+ parseBrowserManifest,
157
+ precacheList,
158
+ registerServiceWorker2 as registerServiceWorker,
159
+ sessionStore2 as sessionStore,
160
+ watchForUpdates2 as watchForUpdates,
161
+ webAppManifest
162
+ };
163
+
164
+ //# debugId=941B31DB64DFC2AF64756E2164756E21
165
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/pwa/model/manifest.ts", "../src/pwa/model/caching.ts", "../src/pwa/model/clear-app-caches.ts", "../src/pwa/model/precache-list.ts", "../src/pwa/model/storable.ts"],
4
+ "sourcesContent": [
5
+ "/** Where the server serves the manifest, and the document head links it. */\nexport const MANIFEST_PATH = '/manifest.webmanifest';\n\n/**\n * Cacheable for an hour, like the icons; the language travels in `?lang=`.\n * Its own policy, which loads nothing: the pages' one carries a nonce, and\n * a nonce never goes in a response a cache may keep.\n */\nexport const MANIFEST_HEADERS = {\n\t'content-type': 'application/manifest+json; charset=utf-8',\n\t'cache-control': 'public, max-age=3600',\n\tvary: 'Accept-Language',\n\t'content-security-policy': \"default-src 'none'; frame-ancestors 'none'\",\n} as const;\n\n/**\n * The manifest's URL for a page in `language`. A browser fetches the\n * manifest without cookies, so a `language` cookie would not reach it: the\n * page names its language in the query instead.\n */\nexport function manifestHref(\n\tlanguage: string,\n\tpath: string = MANIFEST_PATH,\n): string {\n\treturn `${path}?lang=${encodeURIComponent(language)}`;\n}\n\n/**\n * `?lang=` when it names one of `supported`, else `fallback`: the request's\n * Accept-Language, never its cookie, so `Vary: Accept-Language` holds.\n */\nexport function manifestLanguage<L extends string>(\n\turl: URL,\n\tsupported: readonly L[],\n\tfallback: L,\n): L {\n\tconst requested = url.searchParams.get('lang');\n\treturn supported.find((language) => language === requested) ?? fallback;\n}\n\nexport type ManifestIcon = {\n\tsrc: string;\n\tsizes: string;\n\ttype: string;\n\tpurpose?: 'any' | 'maskable' | 'monochrome';\n};\n\n/**\n * The four icons `@vite-pwa/assets-generator`'s `minimal2023Preset` writes\n * to `public/`: 64, 192 and 512 pixels, and a maskable 512.\n */\nexport const MINIMAL_2023_ICONS: readonly ManifestIcon[] = Object.freeze(\n\t[\n\t\t{ src: '/pwa-64x64.png', sizes: '64x64', type: 'image/png' },\n\t\t{ src: '/pwa-192x192.png', sizes: '192x192', type: 'image/png' },\n\t\t{ src: '/pwa-512x512.png', sizes: '512x512', type: 'image/png' },\n\t\t{\n\t\t\tsrc: '/maskable-icon-512x512.png',\n\t\t\tsizes: '512x512',\n\t\t\ttype: 'image/png',\n\t\t\tpurpose: 'maskable' as const,\n\t\t},\n\t].map((icon) => Object.freeze(icon)),\n);\n\nexport interface WebAppManifestOptions {\n\tname: string;\n\t/** `name` by default. */\n\tshortName?: string;\n\tdescription: string;\n\t/** The language of `name` and `description`. */\n\tlang: string;\n\t/** `'ltr'` by default. */\n\tdir?: 'ltr' | 'rtl' | 'auto';\n\tthemeColor: string;\n\tbackgroundColor: string;\n\tcategories?: readonly string[];\n\t/** `MINIMAL_2023_ICONS` by default. */\n\ticons?: readonly ManifestIcon[];\n\t/** `'/'` by default: the same in every language, so a language switch never makes a second app. */\n\tid?: string;\n\t/** `'/'` by default. */\n\tstartUrl?: string;\n\t/** `'/'` by default. */\n\tscope?: string;\n\t/** `'standalone'` by default. */\n\tdisplay?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n}\n\n/** The web app manifest's JSON, with its snake_case keys. */\nexport type WebAppManifest = {\n\tid: string;\n\tname: string;\n\tshort_name: string;\n\tdescription: string;\n\tlang: string;\n\tdir: 'ltr' | 'rtl' | 'auto';\n\tstart_url: string;\n\tscope: string;\n\tdisplay: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';\n\ttheme_color: string;\n\tbackground_color: string;\n\tcategories?: string[];\n\ticons: ManifestIcon[];\n};\n\n/**\n * A web app manifest from the app's own (translated) texts and colours.\n * `id`, `start_url` and `scope` default to `'/'` whatever the language.\n */\nexport function webAppManifest(options: WebAppManifestOptions): WebAppManifest {\n\treturn {\n\t\tid: options.id ?? '/',\n\t\tname: options.name,\n\t\tshort_name: options.shortName ?? options.name,\n\t\tdescription: options.description,\n\t\tlang: options.lang,\n\t\tdir: options.dir ?? 'ltr',\n\t\tstart_url: options.startUrl ?? '/',\n\t\tscope: options.scope ?? '/',\n\t\tdisplay: options.display ?? 'standalone',\n\t\ttheme_color: options.themeColor,\n\t\tbackground_color: options.backgroundColor,\n\t\t...(options.categories && { categories: [...options.categories] }),\n\t\ticons: (options.icons ?? MINIMAL_2023_ICONS).map((icon) => ({ ...icon })),\n\t};\n}\n",
6
+ "import { MANIFEST_PATH } from './manifest';\nimport { SERVICE_WORKER_URL, type WorkerContract } from './worker-contract';\n\n/**\n * Which requests the service worker caches, and how. Pure, so that what is\n * never cached is tested, not hoped for.\n *\n * - `navigation`: a page load. Always from the network, never stored; the\n * offline page answers only when the network fails.\n * - `immutable`: a hashed bundle: cache-first.\n * - `static`: another same-origin static file (icon, sprite, logo):\n * stale-while-revalidate.\n * - `none`: left to the browser, untouched: every non-GET, any other\n * origin, React Router's `.data` and `/__manifest`, the worker and the\n * manifest themselves, the app's own `never` paths, and anything carrying\n * credentials.\n */\nexport type Caching = 'navigation' | 'immutable' | 'static' | 'none';\n\n/** What a `Request` exposes that the decision reads. */\nexport interface RequestFacts {\n\treadonly method: string;\n\treadonly url: string;\n\treadonly mode: string;\n\treadonly destination: string;\n\treadonly headers: Pick<Headers, 'has'>;\n}\n\nexport interface CachingOptions {\n\t/**\n\t * The worker's own path, never cached. Wins over `contract`;\n\t * `SERVICE_WORKER_URL` when neither is given.\n\t */\n\tserviceWorkerUrl?: string;\n\t/** The app's contract, whose `serviceWorkerUrl` is never cached. */\n\tcontract?: Pick<WorkerContract, 'serviceWorkerUrl'>;\n\t/** The web app manifest's path, never cached. `MANIFEST_PATH` by default. */\n\tmanifestPath?: string;\n\t/** Paths of the app answered fresh every time, such as `(p) => p.startsWith('/api/')`. */\n\tnever?: (path: string) => boolean;\n\t/**\n\t * Which static paths are immutable (cache-first) rather than static.\n\t * `(path) => path.startsWith('/assets/')` by default, as `serveClient`\n\t * serves them.\n\t */\n\timmutable?: (path: string) => boolean;\n}\n\nconst STATIC_EXTENSION =\n\t/\\.(?:js|mjs|css|woff2?|ttf|otf|svg|png|jpe?g|webp|avif|gif|ico)$/i;\n\nconst underAssets = (path: string) => path.startsWith('/assets/');\n\n/** How the worker answers `request`, for an app served at `origin`. */\nexport function cachingFor(\n\trequest: RequestFacts,\n\torigin: string,\n\toptions: CachingOptions = {},\n): Caching {\n\tif (request.method !== 'GET') return 'none';\n\tif (request.mode === 'navigate') return 'navigation';\n\tconst url = new URL(request.url);\n\tif (url.origin !== origin) return 'none';\n\tif (request.headers.has('authorization')) return 'none';\n\tif (url.search) return 'none';\n\tconst path = url.pathname;\n\tif (\n\t\tpath.endsWith('.data') || // React Router single fetch: loader data\n\t\tpath === '/__manifest' || // React Router route discovery\n\t\tpath ===\n\t\t\t(options.serviceWorkerUrl ??\n\t\t\t\toptions.contract?.serviceWorkerUrl ??\n\t\t\t\tSERVICE_WORKER_URL) ||\n\t\tpath === (options.manifestPath ?? MANIFEST_PATH) ||\n\t\toptions.never?.(path)\n\t) {\n\t\treturn 'none';\n\t}\n\tif (request.destination === 'document' || request.destination === 'iframe')\n\t\treturn 'none';\n\tif (!STATIC_EXTENSION.test(path)) return 'none';\n\treturn (options.immutable ?? underAssets)(path) ? 'immutable' : 'static';\n}\n",
7
+ "import type { WorkerContract } from './worker-contract';\n\n/**\n * Deletes every cache the service worker filled, but the offline page.\n * For sign-out: what the caches hold is static and public by design, and\n * clearing them leaves nothing behind anyway. Safe where Cache Storage does\n * not exist (server, old browsers).\n */\nexport async function clearAppCaches(\n\tcontract: Pick<WorkerContract, 'clearableCaches'>,\n): Promise<void> {\n\tif (typeof caches === 'undefined') return;\n\tconst names = contract.clearableCaches(await caches.keys());\n\tawait Promise.all(names.map((name) => caches.delete(name)));\n}\n",
8
+ "/**\n * What the service worker precaches, read from a build: every script and\n * stylesheet React Router's manifest names (the client entry and each\n * route's module, imports and CSS), and the manifest itself, so a client\n * navigation offline to a route never visited still finds its code. Only\n * hashed `/assets/` files: a new build names new files, so each precaches\n * under its own URL, revision `null`. Chunks only a component loads on\n * demand stay out: a cache-first route stores them the first time they load.\n *\n * Dependency-free: a build script runs it at build time.\n */\n\n/** A route or the entry, as `window.__reactRouterManifest` lists it. */\ntype ManifestModule = { module?: string; imports?: string[]; css?: string[] };\n\n/** The parts of React Router's browser manifest the list reads. */\nexport type BrowserManifest = {\n\turl: string;\n\tentry: ManifestModule;\n\troutes: Record<string, ManifestModule>;\n};\n\n/** A Workbox precache entry: a hashed URL needs no revision. */\nexport type PrecacheEntry = { url: string; revision: string | null };\n\n/** A Vite-hashed file under `/assets/`: `name-<8 hash chars>.js|css`. */\nconst HASHED_ASSET = /^\\/assets\\/[^/]+-[\\w-]{8}\\.(?:js|css)$/;\n\n/** Reads `window.__reactRouterManifest={…};` (build/client/assets/manifest-*.js). */\nexport function parseBrowserManifest(source: string): BrowserManifest {\n\tconst start = source.indexOf('{');\n\tconst end = source.lastIndexOf('}');\n\tif (start < 0 || end < start) {\n\t\tthrow new Error(\n\t\t\t\"parseBrowserManifest(): not React Router's browser manifest (build/client/assets/manifest-*.js).\",\n\t\t);\n\t}\n\treturn JSON.parse(source.slice(start, end + 1)) as BrowserManifest;\n}\n\n/** The hashed scripts and stylesheets every route needs, the manifest's own file first. */\nexport function precacheList(manifest: BrowserManifest): PrecacheEntry[] {\n\tconst urls = new Set<string>([manifest.url]);\n\tfor (const part of [manifest.entry, ...Object.values(manifest.routes)]) {\n\t\tfor (const url of [\n\t\t\t...(part.module ? [part.module] : []),\n\t\t\t...(part.imports ?? []),\n\t\t\t...(part.css ?? []),\n\t\t]) {\n\t\t\turls.add(url);\n\t\t}\n\t}\n\treturn [...urls]\n\t\t.filter((url) => HASHED_ASSET.test(url))\n\t\t.map((url) => ({ url, revision: null }));\n}\n\n/**\n * The precache entries of the files among `assetFiles` (the names in\n * build/client/assets) that `include` matches, sorted, under `base`: fonts\n * an app keeps offline, say. The regex is the app's.\n */\nexport function assetPrecache(\n\tassetFiles: readonly string[],\n\tinclude: RegExp,\n\tbase = '/assets/',\n): PrecacheEntry[] {\n\treturn assetFiles\n\t\t.filter((file) => {\n\t\t\tinclude.lastIndex = 0; // a /g or /y regex remembers its last match\n\t\t\treturn include.test(file);\n\t\t})\n\t\t.sort()\n\t\t.map((file) => ({ url: `${base}${file}`, revision: null }));\n}\n",
9
+ "/**\n * The content types of the static extensions: scripts, styles, fonts and\n * images (SVG and icons included). A page, JSON or anything else served\n * under a static-looking path is refused all the same.\n */\nconst STATIC_CONTENT_TYPE =\n\t/^(?:(?:text|application)\\/(?:javascript|x-javascript|ecmascript)|text\\/css|font\\/[\\w.+-]+|application\\/(?:font-woff2?|x-font-[\\w.+-]+|vnd\\.ms-fontobject)|image\\/[\\w.+-]+)$/i;\n\n/**\n * Whether a response may be stored: a full, same-origin 200 of a static\n * content type, that the server marked `public` (alxia does on every\n * static file it serves; a route answering an image, a user's photo, does\n * not), not private nor no-store, and that does not vary on who asks.\n */\nexport function isStorable(response: Response): boolean {\n\tif (response.status !== 200) return false;\n\tif (response.type !== 'basic' && response.type !== 'default') return false;\n\tconst contentType = (response.headers.get('content-type') ?? '')\n\t\t.split(';')[0]\n\t\t?.trim();\n\tif (!contentType || !STATIC_CONTENT_TYPE.test(contentType)) return false;\n\tconst cacheControl = response.headers.get('cache-control') ?? '';\n\tif (!/\\bpublic\\b/i.test(cacheControl)) return false;\n\tif (/\\b(?:no-store|private)\\b/i.test(cacheControl)) return false;\n\tconst vary = response.headers.get('vary') ?? '';\n\treturn !/(?:^|,)\\s*(?:\\*|cookie|authorization)\\s*(?:,|$)/i.test(vary);\n}\n"
10
+ ],
11
+ "mappings": "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",
12
+ "debugId": "941B31DB64DFC2AF64756E2164756E21",
13
+ "names": []
14
+ }