@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.
- package/README.md +107 -8
- package/dist/chunks/index-zqa30gr7.js +139 -0
- package/dist/chunks/index-zqa30gr7.js.map +12 -0
- package/dist/pwa/index.d.ts +14 -0
- package/dist/pwa/index.d.ts.map +1 -0
- package/dist/pwa/index.js +165 -0
- package/dist/pwa/index.js.map +14 -0
- package/dist/pwa/model/caching.d.ts +46 -0
- package/dist/pwa/model/caching.d.ts.map +1 -0
- package/dist/pwa/model/clear-app-caches.d.ts +9 -0
- package/dist/pwa/model/clear-app-caches.d.ts.map +1 -0
- package/dist/pwa/model/manifest.d.ts +80 -0
- package/dist/pwa/model/manifest.d.ts.map +1 -0
- package/dist/pwa/model/precache-list.d.ts +40 -0
- package/dist/pwa/model/precache-list.d.ts.map +1 -0
- package/dist/pwa/model/registration.d.ts +39 -0
- package/dist/pwa/model/registration.d.ts.map +1 -0
- package/dist/pwa/model/start-service-worker.d.ts +24 -0
- package/dist/pwa/model/start-service-worker.d.ts.map +1 -0
- package/dist/pwa/model/storable.d.ts +8 -0
- package/dist/pwa/model/storable.d.ts.map +1 -0
- package/dist/pwa/model/update-notice.d.ts +38 -0
- package/dist/pwa/model/update-notice.d.ts.map +1 -0
- package/dist/pwa/model/worker-contract.d.ts +61 -0
- package/dist/pwa/model/worker-contract.d.ts.map +1 -0
- package/dist/pwa/react/index.d.ts +9 -0
- package/dist/pwa/react/index.d.ts.map +1 -0
- package/dist/pwa/react/index.js +75 -0
- package/dist/pwa/react/index.js.map +13 -0
- package/dist/pwa/react/use-online.d.ts +6 -0
- package/dist/pwa/react/use-online.d.ts.map +1 -0
- package/dist/pwa/react/use-service-worker.d.ts +33 -0
- package/dist/pwa/react/use-service-worker.d.ts.map +1 -0
- package/dist/pwa/react/use-update-notice.d.ts +14 -0
- package/dist/pwa/react/use-update-notice.d.ts.map +1 -0
- package/dist/pwa/server/index.d.ts +7 -0
- package/dist/pwa/server/index.d.ts.map +1 -0
- package/dist/pwa/server/index.js +18 -0
- package/dist/pwa/server/index.js.map +10 -0
- package/dist/pwa/server/no-cache.d.ts +22 -0
- package/dist/pwa/server/no-cache.d.ts.map +1 -0
- package/docs/README.md +12 -3
- package/docs/guide/beside-the-pages.md +178 -0
- package/docs/guide/csp-nonce.md +100 -0
- package/docs/guide/customising-the-server.md +292 -0
- package/docs/guide/deploying.md +99 -0
- package/docs/guide/getting-started.md +112 -0
- package/docs/guide/how-it-works.md +262 -0
- package/docs/guide/openapi.md +51 -0
- package/docs/guide/pwa.md +475 -0
- package/docs/guide/testing.md +42 -0
- package/docs/guide/typing-the-loaders.md +311 -0
- package/docs/guide.md +27 -1391
- package/docs/roadmap.md +25 -1
- package/docs/troubleshooting.md +195 -9
- package/package.json +32 -10
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type WorkerContract } from './worker-contract';
|
|
2
|
+
/**
|
|
3
|
+
* Which requests the service worker caches, and how. Pure, so that what is
|
|
4
|
+
* never cached is tested, not hoped for.
|
|
5
|
+
*
|
|
6
|
+
* - `navigation`: a page load. Always from the network, never stored; the
|
|
7
|
+
* offline page answers only when the network fails.
|
|
8
|
+
* - `immutable`: a hashed bundle: cache-first.
|
|
9
|
+
* - `static`: another same-origin static file (icon, sprite, logo):
|
|
10
|
+
* stale-while-revalidate.
|
|
11
|
+
* - `none`: left to the browser, untouched: every non-GET, any other
|
|
12
|
+
* origin, React Router's `.data` and `/__manifest`, the worker and the
|
|
13
|
+
* manifest themselves, the app's own `never` paths, and anything carrying
|
|
14
|
+
* credentials.
|
|
15
|
+
*/
|
|
16
|
+
export type Caching = 'navigation' | 'immutable' | 'static' | 'none';
|
|
17
|
+
/** What a `Request` exposes that the decision reads. */
|
|
18
|
+
export interface RequestFacts {
|
|
19
|
+
readonly method: string;
|
|
20
|
+
readonly url: string;
|
|
21
|
+
readonly mode: string;
|
|
22
|
+
readonly destination: string;
|
|
23
|
+
readonly headers: Pick<Headers, 'has'>;
|
|
24
|
+
}
|
|
25
|
+
export interface CachingOptions {
|
|
26
|
+
/**
|
|
27
|
+
* The worker's own path, never cached. Wins over `contract`;
|
|
28
|
+
* `SERVICE_WORKER_URL` when neither is given.
|
|
29
|
+
*/
|
|
30
|
+
serviceWorkerUrl?: string;
|
|
31
|
+
/** The app's contract, whose `serviceWorkerUrl` is never cached. */
|
|
32
|
+
contract?: Pick<WorkerContract, 'serviceWorkerUrl'>;
|
|
33
|
+
/** The web app manifest's path, never cached. `MANIFEST_PATH` by default. */
|
|
34
|
+
manifestPath?: string;
|
|
35
|
+
/** Paths of the app answered fresh every time, such as `(p) => p.startsWith('/api/')`. */
|
|
36
|
+
never?: (path: string) => boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Which static paths are immutable (cache-first) rather than static.
|
|
39
|
+
* `(path) => path.startsWith('/assets/')` by default, as `serveClient`
|
|
40
|
+
* serves them.
|
|
41
|
+
*/
|
|
42
|
+
immutable?: (path: string) => boolean;
|
|
43
|
+
}
|
|
44
|
+
/** How the worker answers `request`, for an app served at `origin`. */
|
|
45
|
+
export declare function cachingFor(request: RequestFacts, origin: string, options?: CachingOptions): Caching;
|
|
46
|
+
//# sourceMappingURL=caching.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"caching.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/caching.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAE5E;;;;;;;;;;;;;GAaG;AACH,MAAM,MAAM,OAAO,GAAG,YAAY,GAAG,WAAW,GAAG,QAAQ,GAAG,MAAM,CAAC;AAErE,wDAAwD;AACxD,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,cAAc;IAC9B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oEAAoE;IACpE,QAAQ,CAAC,EAAE,IAAI,CAAC,cAAc,EAAE,kBAAkB,CAAC,CAAC;IACpD,6EAA6E;IAC7E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0FAA0F;IAC1F,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;IAClC;;;;OAIG;IACH,SAAS,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC;CACtC;AAOD,uEAAuE;AACvE,wBAAgB,UAAU,CACzB,OAAO,EAAE,YAAY,EACrB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,cAAmB,GAC1B,OAAO,CAwBT"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { WorkerContract } from './worker-contract';
|
|
2
|
+
/**
|
|
3
|
+
* Deletes every cache the service worker filled, but the offline page.
|
|
4
|
+
* For sign-out: what the caches hold is static and public by design, and
|
|
5
|
+
* clearing them leaves nothing behind anyway. Safe where Cache Storage does
|
|
6
|
+
* not exist (server, old browsers).
|
|
7
|
+
*/
|
|
8
|
+
export declare function clearAppCaches(contract: Pick<WorkerContract, 'clearableCaches'>): Promise<void>;
|
|
9
|
+
//# sourceMappingURL=clear-app-caches.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"clear-app-caches.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/clear-app-caches.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAExD;;;;;GAKG;AACH,wBAAsB,cAAc,CACnC,QAAQ,EAAE,IAAI,CAAC,cAAc,EAAE,iBAAiB,CAAC,GAC/C,OAAO,CAAC,IAAI,CAAC,CAIf"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Where the server serves the manifest, and the document head links it. */
|
|
2
|
+
export declare const MANIFEST_PATH = "/manifest.webmanifest";
|
|
3
|
+
/**
|
|
4
|
+
* Cacheable for an hour, like the icons; the language travels in `?lang=`.
|
|
5
|
+
* Its own policy, which loads nothing: the pages' one carries a nonce, and
|
|
6
|
+
* a nonce never goes in a response a cache may keep.
|
|
7
|
+
*/
|
|
8
|
+
export declare const MANIFEST_HEADERS: {
|
|
9
|
+
readonly 'content-type': "application/manifest+json; charset=utf-8";
|
|
10
|
+
readonly 'cache-control': "public, max-age=3600";
|
|
11
|
+
readonly vary: "Accept-Language";
|
|
12
|
+
readonly 'content-security-policy': "default-src 'none'; frame-ancestors 'none'";
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* The manifest's URL for a page in `language`. A browser fetches the
|
|
16
|
+
* manifest without cookies, so a `language` cookie would not reach it: the
|
|
17
|
+
* page names its language in the query instead.
|
|
18
|
+
*/
|
|
19
|
+
export declare function manifestHref(language: string, path?: string): string;
|
|
20
|
+
/**
|
|
21
|
+
* `?lang=` when it names one of `supported`, else `fallback`: the request's
|
|
22
|
+
* Accept-Language, never its cookie, so `Vary: Accept-Language` holds.
|
|
23
|
+
*/
|
|
24
|
+
export declare function manifestLanguage<L extends string>(url: URL, supported: readonly L[], fallback: L): L;
|
|
25
|
+
export type ManifestIcon = {
|
|
26
|
+
src: string;
|
|
27
|
+
sizes: string;
|
|
28
|
+
type: string;
|
|
29
|
+
purpose?: 'any' | 'maskable' | 'monochrome';
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* The four icons `@vite-pwa/assets-generator`'s `minimal2023Preset` writes
|
|
33
|
+
* to `public/`: 64, 192 and 512 pixels, and a maskable 512.
|
|
34
|
+
*/
|
|
35
|
+
export declare const MINIMAL_2023_ICONS: readonly ManifestIcon[];
|
|
36
|
+
export interface WebAppManifestOptions {
|
|
37
|
+
name: string;
|
|
38
|
+
/** `name` by default. */
|
|
39
|
+
shortName?: string;
|
|
40
|
+
description: string;
|
|
41
|
+
/** The language of `name` and `description`. */
|
|
42
|
+
lang: string;
|
|
43
|
+
/** `'ltr'` by default. */
|
|
44
|
+
dir?: 'ltr' | 'rtl' | 'auto';
|
|
45
|
+
themeColor: string;
|
|
46
|
+
backgroundColor: string;
|
|
47
|
+
categories?: readonly string[];
|
|
48
|
+
/** `MINIMAL_2023_ICONS` by default. */
|
|
49
|
+
icons?: readonly ManifestIcon[];
|
|
50
|
+
/** `'/'` by default: the same in every language, so a language switch never makes a second app. */
|
|
51
|
+
id?: string;
|
|
52
|
+
/** `'/'` by default. */
|
|
53
|
+
startUrl?: string;
|
|
54
|
+
/** `'/'` by default. */
|
|
55
|
+
scope?: string;
|
|
56
|
+
/** `'standalone'` by default. */
|
|
57
|
+
display?: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
|
|
58
|
+
}
|
|
59
|
+
/** The web app manifest's JSON, with its snake_case keys. */
|
|
60
|
+
export type WebAppManifest = {
|
|
61
|
+
id: string;
|
|
62
|
+
name: string;
|
|
63
|
+
short_name: string;
|
|
64
|
+
description: string;
|
|
65
|
+
lang: string;
|
|
66
|
+
dir: 'ltr' | 'rtl' | 'auto';
|
|
67
|
+
start_url: string;
|
|
68
|
+
scope: string;
|
|
69
|
+
display: 'standalone' | 'fullscreen' | 'minimal-ui' | 'browser';
|
|
70
|
+
theme_color: string;
|
|
71
|
+
background_color: string;
|
|
72
|
+
categories?: string[];
|
|
73
|
+
icons: ManifestIcon[];
|
|
74
|
+
};
|
|
75
|
+
/**
|
|
76
|
+
* A web app manifest from the app's own (translated) texts and colours.
|
|
77
|
+
* `id`, `start_url` and `scope` default to `'/'` whatever the language.
|
|
78
|
+
*/
|
|
79
|
+
export declare function webAppManifest(options: WebAppManifestOptions): WebAppManifest;
|
|
80
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/manifest.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,eAAO,MAAM,aAAa,0BAA0B,CAAC;AAErD;;;;GAIG;AACH,eAAO,MAAM,gBAAgB;;;;;CAKnB,CAAC;AAEX;;;;GAIG;AACH,wBAAgB,YAAY,CAC3B,QAAQ,EAAE,MAAM,EAChB,IAAI,GAAE,MAAsB,GAC1B,MAAM,CAER;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,SAAS,MAAM,EAChD,GAAG,EAAE,GAAG,EACR,SAAS,EAAE,SAAS,CAAC,EAAE,EACvB,QAAQ,EAAE,CAAC,GACT,CAAC,CAGH;AAED,MAAM,MAAM,YAAY,GAAG;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,YAAY,CAAC;CAC5C,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,YAAY,EAYrD,CAAC;AAEF,MAAM,WAAW,qBAAqB;IACrC,IAAI,EAAE,MAAM,CAAC;IACb,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC7B,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC/B,uCAAuC;IACvC,KAAK,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAChC,mGAAmG;IACnG,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,OAAO,CAAC,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;CACjE;AAED,6DAA6D;AAC7D,MAAM,MAAM,cAAc,GAAG;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAC;IAChE,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,KAAK,EAAE,YAAY,EAAE,CAAC;CACtB,CAAC;AAEF;;;GAGG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,qBAAqB,GAAG,cAAc,CAgB7E"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the service worker precaches, read from a build: every script and
|
|
3
|
+
* stylesheet React Router's manifest names (the client entry and each
|
|
4
|
+
* route's module, imports and CSS), and the manifest itself, so a client
|
|
5
|
+
* navigation offline to a route never visited still finds its code. Only
|
|
6
|
+
* hashed `/assets/` files: a new build names new files, so each precaches
|
|
7
|
+
* under its own URL, revision `null`. Chunks only a component loads on
|
|
8
|
+
* demand stay out: a cache-first route stores them the first time they load.
|
|
9
|
+
*
|
|
10
|
+
* Dependency-free: a build script runs it at build time.
|
|
11
|
+
*/
|
|
12
|
+
/** A route or the entry, as `window.__reactRouterManifest` lists it. */
|
|
13
|
+
type ManifestModule = {
|
|
14
|
+
module?: string;
|
|
15
|
+
imports?: string[];
|
|
16
|
+
css?: string[];
|
|
17
|
+
};
|
|
18
|
+
/** The parts of React Router's browser manifest the list reads. */
|
|
19
|
+
export type BrowserManifest = {
|
|
20
|
+
url: string;
|
|
21
|
+
entry: ManifestModule;
|
|
22
|
+
routes: Record<string, ManifestModule>;
|
|
23
|
+
};
|
|
24
|
+
/** A Workbox precache entry: a hashed URL needs no revision. */
|
|
25
|
+
export type PrecacheEntry = {
|
|
26
|
+
url: string;
|
|
27
|
+
revision: string | null;
|
|
28
|
+
};
|
|
29
|
+
/** Reads `window.__reactRouterManifest={…};` (build/client/assets/manifest-*.js). */
|
|
30
|
+
export declare function parseBrowserManifest(source: string): BrowserManifest;
|
|
31
|
+
/** The hashed scripts and stylesheets every route needs, the manifest's own file first. */
|
|
32
|
+
export declare function precacheList(manifest: BrowserManifest): PrecacheEntry[];
|
|
33
|
+
/**
|
|
34
|
+
* The precache entries of the files among `assetFiles` (the names in
|
|
35
|
+
* build/client/assets) that `include` matches, sorted, under `base`: fonts
|
|
36
|
+
* an app keeps offline, say. The regex is the app's.
|
|
37
|
+
*/
|
|
38
|
+
export declare function assetPrecache(assetFiles: readonly string[], include: RegExp, base?: string): PrecacheEntry[];
|
|
39
|
+
export {};
|
|
40
|
+
//# sourceMappingURL=precache-list.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"precache-list.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/precache-list.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,wEAAwE;AACxE,KAAK,cAAc,GAAG;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC;AAE9E,mEAAmE;AACnE,MAAM,MAAM,eAAe,GAAG;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,cAAc,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAC;CACvC,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,aAAa,GAAG;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAAC;AAKrE,qFAAqF;AACrF,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,CASpE;AAED,2FAA2F;AAC3F,wBAAgB,YAAY,CAAC,QAAQ,EAAE,eAAe,GAAG,aAAa,EAAE,CAcvE;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAC5B,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,OAAO,EAAE,MAAM,EACf,IAAI,SAAa,GACf,aAAa,EAAE,CAQjB"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** What the registration reads of `navigator.serviceWorker`. */
|
|
2
|
+
export type ServiceWorkerContainerLike = Pick<ServiceWorkerContainer, 'controller' | 'addEventListener' | 'register'>;
|
|
3
|
+
/**
|
|
4
|
+
* Calls `onWaiting` with each new version that finished installing while an
|
|
5
|
+
* older one controls the page: the one waiting now, and each found later.
|
|
6
|
+
* A first install controls nothing yet, so it offers nothing.
|
|
7
|
+
*/
|
|
8
|
+
export declare function watchForUpdates(registration: ServiceWorkerRegistration, container: Pick<ServiceWorkerContainerLike, 'controller'>, onWaiting: (worker: ServiceWorker) => void): void;
|
|
9
|
+
/** Moves this page onto a waiting worker: `apply(worker)`. */
|
|
10
|
+
export type ApplyUpdate = (worker: Pick<ServiceWorker, 'postMessage' | 'state'>) => void;
|
|
11
|
+
/**
|
|
12
|
+
* The one update path of a page, created once: `apply(worker)` activates
|
|
13
|
+
* the waiting worker and reloads once, when it takes control, so the page
|
|
14
|
+
* runs the version its assets came from.
|
|
15
|
+
*
|
|
16
|
+
* - Several tabs: another tab may have activated the worker already. Its
|
|
17
|
+
* `controllerchange` reached this page before anyone asked, so this page
|
|
18
|
+
* did not reload (a form may be half filled); its "Reload" then reloads
|
|
19
|
+
* at once, the worker being active.
|
|
20
|
+
* - Clicked twice, or a `controllerchange` after a direct reload: one
|
|
21
|
+
* reload, never two.
|
|
22
|
+
*/
|
|
23
|
+
export declare function createUpdater(container: Pick<ServiceWorkerContainerLike, 'controller' | 'addEventListener'>, reload?: () => void): ApplyUpdate;
|
|
24
|
+
/** Runs `run` once the page has loaded: registering never competes with it. */
|
|
25
|
+
export declare function afterLoad(run: () => void): void;
|
|
26
|
+
export interface RegisterOptions {
|
|
27
|
+
/** The worker's path. `SERVICE_WORKER_URL` by default. */
|
|
28
|
+
url?: string;
|
|
29
|
+
/** The scope it controls. `'/'` by default. */
|
|
30
|
+
scope?: string;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Registers the worker and watches for updates, handing each waiting worker
|
|
34
|
+
* to `onWaiting` with the page's one `apply` (`createUpdater`, made on the
|
|
35
|
+
* first call: one per page). A tab coming back to the foreground checks for
|
|
36
|
+
* an update, as a long-lived page otherwise would only on its next full load.
|
|
37
|
+
*/
|
|
38
|
+
export declare function registerServiceWorker(container: ServiceWorkerContainerLike, onWaiting: (worker: ServiceWorker, apply: ApplyUpdate) => void, options?: RegisterOptions): Promise<ServiceWorkerRegistration>;
|
|
39
|
+
//# sourceMappingURL=registration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registration.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/registration.ts"],"names":[],"mappings":"AAEA,gEAAgE;AAChE,MAAM,MAAM,0BAA0B,GAAG,IAAI,CAC5C,sBAAsB,EACtB,YAAY,GAAG,kBAAkB,GAAG,UAAU,CAC9C,CAAC;AAEF;;;;GAIG;AACH,wBAAgB,eAAe,CAC9B,YAAY,EAAE,yBAAyB,EACvC,SAAS,EAAE,IAAI,CAAC,0BAA0B,EAAE,YAAY,CAAC,EACzD,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,GACxC,IAAI,CAWN;AAED,8DAA8D;AAC9D,MAAM,MAAM,WAAW,GAAG,CACzB,MAAM,EAAE,IAAI,CAAC,aAAa,EAAE,aAAa,GAAG,OAAO,CAAC,KAChD,IAAI,CAAC;AAEV;;;;;;;;;;;GAWG;AACH,wBAAgB,aAAa,CAC5B,SAAS,EAAE,IAAI,CACd,0BAA0B,EAC1B,YAAY,GAAG,kBAAkB,CACjC,EACD,MAAM,GAAE,MAAM,IAAqC,GACjD,WAAW,CAoBb;AAED,+EAA+E;AAC/E,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,IAAI,GAAG,IAAI,CAG/C;AAED,MAAM,WAAW,eAAe;IAC/B,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;CACf;AAID;;;;;GAKG;AACH,wBAAsB,qBAAqB,CAC1C,SAAS,EAAE,0BAA0B,EACrC,SAAS,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,WAAW,KAAK,IAAI,EAC9D,OAAO,GAAE,eAAoB,GAC3B,OAAO,CAAC,yBAAyB,CAAC,CAiBpC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { ServiceWorkerContainerLike } from './registration';
|
|
2
|
+
import type { UpdateNoticeStore } from './update-notice';
|
|
3
|
+
/** What starting reads of the browser: `navigator`, which may have no worker. */
|
|
4
|
+
export interface ServiceWorkerEnvironment {
|
|
5
|
+
navigator: object & {
|
|
6
|
+
serviceWorker?: ServiceWorkerContainerLike;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
export interface StartServiceWorkerOptions {
|
|
10
|
+
enabled: boolean;
|
|
11
|
+
url?: string;
|
|
12
|
+
scope?: string;
|
|
13
|
+
notice: Pick<UpdateNoticeStore, 'offer'>;
|
|
14
|
+
onError?: (error: unknown) => void;
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* The body of `useServiceWorker`'s effect, a plain function so that it is
|
|
18
|
+
* tested without React: registers after `load` when `enabled` and the
|
|
19
|
+
* browser has a worker container, offers each waiting version to `notice`,
|
|
20
|
+
* and hands a failed registration to `onError`. The listeners it adds live
|
|
21
|
+
* for the page.
|
|
22
|
+
*/
|
|
23
|
+
export declare function startServiceWorker(environment: ServiceWorkerEnvironment, options: StartServiceWorkerOptions): void;
|
|
24
|
+
//# sourceMappingURL=start-service-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"start-service-worker.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/start-service-worker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAEjE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,iFAAiF;AACjF,MAAM,WAAW,wBAAwB;IACxC,SAAS,EAAE,MAAM,GAAG;QAAE,aAAa,CAAC,EAAE,0BAA0B,CAAA;KAAE,CAAC;CACnE;AAED,MAAM,WAAW,yBAAyB;IACzC,OAAO,EAAE,OAAO,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,CAAC,iBAAiB,EAAE,OAAO,CAAC,CAAC;IACzC,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,WAAW,EAAE,wBAAwB,EACrC,OAAO,EAAE,yBAAyB,GAChC,IAAI,CAgBN"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Whether a response may be stored: a full, same-origin 200 of a static
|
|
3
|
+
* content type, that the server marked `public` (alxia does on every
|
|
4
|
+
* static file it serves; a route answering an image, a user's photo, does
|
|
5
|
+
* not), not private nor no-store, and that does not vary on who asks.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isStorable(response: Response): boolean;
|
|
8
|
+
//# sourceMappingURL=storable.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storable.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/storable.ts"],"names":[],"mappings":"AAQA;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,QAAQ,EAAE,QAAQ,GAAG,OAAO,CAYtD"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The session's "Later": the notice stays hidden until the next visit.
|
|
3
|
+
* `sessionStorage` is per origin, so one key serves every app; a second
|
|
4
|
+
* argument of `createUpdateNotice` names another.
|
|
5
|
+
*/
|
|
6
|
+
export declare const UPDATE_LATER_KEY = "alxia.update-later";
|
|
7
|
+
/** The part of `Storage` the notice reads and writes. */
|
|
8
|
+
export type NoticeStorage = {
|
|
9
|
+
getItem(key: string): string | null;
|
|
10
|
+
setItem(key: string, value: string): void;
|
|
11
|
+
};
|
|
12
|
+
/** What the notice offers while a new version waits. */
|
|
13
|
+
export type UpdateNotice = {
|
|
14
|
+
/** Moves the page onto the new version: the worker's `apply`. */
|
|
15
|
+
reload: () => void;
|
|
16
|
+
/** Hides the notice until the next visit (the tab's session). */
|
|
17
|
+
later: () => void;
|
|
18
|
+
};
|
|
19
|
+
/** What `createUpdateNotice` returns: the shape `useSyncExternalStore` takes. */
|
|
20
|
+
export interface UpdateNoticeStore {
|
|
21
|
+
/** A new version waits: `apply` moves the page onto it. */
|
|
22
|
+
offer(apply: () => void): void;
|
|
23
|
+
/** The notice to show, if any (the same object until it changes). */
|
|
24
|
+
current(): UpdateNotice | undefined;
|
|
25
|
+
/** Calls `listener` on each change; returns the function that stops it. */
|
|
26
|
+
subscribe(listener: () => void): () => void;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The update notice's state: offered by the worker's registration, read by
|
|
30
|
+
* the banners, hidden for the session by "Later". `storage` is the session's
|
|
31
|
+
* (`sessionStorage`), which may be missing or throw (private mode, blocked
|
|
32
|
+
* storage): the notice then hides for this page only. It is a function so
|
|
33
|
+
* that nothing reads the global at load.
|
|
34
|
+
*/
|
|
35
|
+
export declare function createUpdateNotice(storage: () => NoticeStorage | undefined, key?: string): UpdateNoticeStore;
|
|
36
|
+
/** `sessionStorage`, or nothing where there is none (the server). */
|
|
37
|
+
export declare const sessionStore: () => NoticeStorage | undefined;
|
|
38
|
+
//# sourceMappingURL=update-notice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-notice.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/update-notice.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,uBAAuB,CAAC;AAErD,yDAAyD;AACzD,MAAM,MAAM,aAAa,GAAG;IAC3B,OAAO,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IACpC,OAAO,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CAC1C,CAAC;AAEF,wDAAwD;AACxD,MAAM,MAAM,YAAY,GAAG;IAC1B,iEAAiE;IACjE,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,iEAAiE;IACjE,KAAK,EAAE,MAAM,IAAI,CAAC;CAClB,CAAC;AAEF,iFAAiF;AACjF,MAAM,WAAW,iBAAiB;IACjC,2DAA2D;IAC3D,KAAK,CAAC,KAAK,EAAE,MAAM,IAAI,GAAG,IAAI,CAAC;IAC/B,qEAAqE;IACrE,OAAO,IAAI,YAAY,GAAG,SAAS,CAAC;IACpC,2EAA2E;IAC3E,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5C;AAED;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CACjC,OAAO,EAAE,MAAM,aAAa,GAAG,SAAS,EACxC,GAAG,GAAE,MAAyB,GAC5B,iBAAiB,CAyCnB;AAED,qEAAqE;AACrE,eAAO,MAAM,YAAY,QAAO,aAAa,GAAG,SACmB,CAAC"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the service worker and the page agree on: the worker's address, the
|
|
3
|
+
* offline page, the message that applies an update, and the names of the
|
|
4
|
+
* caches the worker owns. Dependency-free: the worker bundles it, with no
|
|
5
|
+
* DOM and no React.
|
|
6
|
+
*/
|
|
7
|
+
/** Where the build writes the worker; registered with scope `/`. */
|
|
8
|
+
export declare const SERVICE_WORKER_URL = "/service-worker.js";
|
|
9
|
+
/** The page served when a navigation fails for want of a network. */
|
|
10
|
+
export declare const OFFLINE_URL = "/offline.html";
|
|
11
|
+
/** The message a page posts to a waiting worker to make it take over. */
|
|
12
|
+
export declare const SKIP_WAITING = "SKIP_WAITING";
|
|
13
|
+
export interface WorkerContractOptions {
|
|
14
|
+
/**
|
|
15
|
+
* Every Cache Storage cache the worker owns is named
|
|
16
|
+
* `<cachePrefix>-<kind>-<version>` (kind: letters and digits; version:
|
|
17
|
+
* no `-`); a cache of another shape is never swept.
|
|
18
|
+
* Required: two apps on one origin must not share it.
|
|
19
|
+
*/
|
|
20
|
+
cachePrefix: string;
|
|
21
|
+
/**
|
|
22
|
+
* Bumped when a cache's content changes shape: the next worker deletes
|
|
23
|
+
* every cache of the prefix that has another version when it activates.
|
|
24
|
+
* `'v1'` by default; not empty, and without a `-`.
|
|
25
|
+
*/
|
|
26
|
+
cacheVersion?: string;
|
|
27
|
+
/** `SERVICE_WORKER_URL` by default. */
|
|
28
|
+
serviceWorkerUrl?: string;
|
|
29
|
+
/** `OFFLINE_URL` by default. */
|
|
30
|
+
offlineUrl?: string;
|
|
31
|
+
}
|
|
32
|
+
export type CacheNames = {
|
|
33
|
+
/**
|
|
34
|
+
* The offline page and the hashed scripts and stylesheets of every route.
|
|
35
|
+
* `<prefix>-precache-<version>` is the name Workbox builds from
|
|
36
|
+
* `setCacheNameDetails({ prefix, precache: 'precache', suffix: version })`.
|
|
37
|
+
*/
|
|
38
|
+
readonly precache: string;
|
|
39
|
+
/** `/assets/*` bundles: hashed names, so cache-first. */
|
|
40
|
+
readonly assets: string;
|
|
41
|
+
/** Icons, sprites, the logo: stale-while-revalidate. */
|
|
42
|
+
readonly static: string;
|
|
43
|
+
};
|
|
44
|
+
export interface WorkerContract {
|
|
45
|
+
readonly cachePrefix: string;
|
|
46
|
+
readonly cacheVersion: string;
|
|
47
|
+
readonly serviceWorkerUrl: string;
|
|
48
|
+
readonly offlineUrl: string;
|
|
49
|
+
readonly cacheNames: CacheNames;
|
|
50
|
+
/** The caches a worker deletes on activate: the prefix's, of another version. */
|
|
51
|
+
obsoleteCaches(names: readonly string[]): string[];
|
|
52
|
+
/**
|
|
53
|
+
* The caches `clearAppCaches()` deletes: all of the prefix's but the
|
|
54
|
+
* current precache, which holds the offline page and the app's code, never
|
|
55
|
+
* a user's data.
|
|
56
|
+
*/
|
|
57
|
+
clearableCaches(names: readonly string[]): string[];
|
|
58
|
+
}
|
|
59
|
+
/** Builds the contract of one app from its cache prefix. */
|
|
60
|
+
export declare function defineWorkerContract(options: WorkerContractOptions): WorkerContract;
|
|
61
|
+
//# sourceMappingURL=worker-contract.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"worker-contract.d.ts","sourceRoot":"","sources":["../../../src/pwa/model/worker-contract.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,oEAAoE;AACpE,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,qEAAqE;AACrE,eAAO,MAAM,WAAW,kBAAkB,CAAC;AAE3C,yEAAyE;AACzE,eAAO,MAAM,YAAY,iBAAiB,CAAC;AAE3C,MAAM,WAAW,qBAAqB;IACrC;;;;;OAKG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB;;;;OAIG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uCAAuC;IACvC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gCAAgC;IAChC,UAAU,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,UAAU,GAAG;IACxB;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,yDAAyD;IACzD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,wDAAwD;IACxD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,MAAM,WAAW,cAAc;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,UAAU,EAAE,UAAU,CAAC;IAChC,iFAAiF;IACjF,cAAc,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;IACnD;;;;OAIG;IACH,eAAe,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;CACpD;AAED,4DAA4D;AAC5D,wBAAgB,oBAAoB,CACnC,OAAO,EAAE,qBAAqB,GAC5B,cAAc,CA+ChB"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@alxia/react-router/pwa/react`: the hooks of an installable app's page.
|
|
3
|
+
* `react` is its only import (an optional peer of the package), and what it
|
|
4
|
+
* shares with `/pwa` is a chunk of that entry.
|
|
5
|
+
*/
|
|
6
|
+
export { useOnline } from './use-online';
|
|
7
|
+
export { type ServiceWorkerOptions, useServiceWorker, } from './use-service-worker';
|
|
8
|
+
export { updateNotice, useUpdateNotice } from './use-update-notice';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/pwa/react/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EACN,KAAK,oBAAoB,EACzB,gBAAgB,GAChB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAAE,YAAY,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import {
|
|
2
|
+
afterLoad2,
|
|
3
|
+
registerServiceWorker2,
|
|
4
|
+
createUpdateNotice2,
|
|
5
|
+
sessionStore2
|
|
6
|
+
} from "../../chunks/index-zqa30gr7.js";
|
|
7
|
+
|
|
8
|
+
// src/pwa/react/use-online.ts
|
|
9
|
+
import { useSyncExternalStore } from "react";
|
|
10
|
+
function subscribe(onChange) {
|
|
11
|
+
window.addEventListener("online", onChange);
|
|
12
|
+
window.addEventListener("offline", onChange);
|
|
13
|
+
return () => {
|
|
14
|
+
window.removeEventListener("online", onChange);
|
|
15
|
+
window.removeEventListener("offline", onChange);
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function useOnline() {
|
|
19
|
+
return useSyncExternalStore(subscribe, () => navigator.onLine, () => true);
|
|
20
|
+
}
|
|
21
|
+
// src/pwa/react/use-service-worker.ts
|
|
22
|
+
import { useEffect, useRef } from "react";
|
|
23
|
+
|
|
24
|
+
// src/pwa/model/start-service-worker.ts
|
|
25
|
+
function startServiceWorker(environment, options) {
|
|
26
|
+
const { navigator: navigator2 } = environment;
|
|
27
|
+
if (!options.enabled || !("serviceWorker" in navigator2))
|
|
28
|
+
return;
|
|
29
|
+
const container = navigator2.serviceWorker;
|
|
30
|
+
if (!container)
|
|
31
|
+
return;
|
|
32
|
+
const { url, scope, notice, onError } = options;
|
|
33
|
+
afterLoad2(() => {
|
|
34
|
+
registerServiceWorker2(container, (worker, apply) => notice.offer(() => apply(worker)), {
|
|
35
|
+
...url !== undefined && { url },
|
|
36
|
+
...scope !== undefined && { scope }
|
|
37
|
+
}).catch((error) => onError?.(error));
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/pwa/react/use-update-notice.ts
|
|
42
|
+
import { useSyncExternalStore as useSyncExternalStore2 } from "react";
|
|
43
|
+
var updateNotice = createUpdateNotice2(sessionStore2);
|
|
44
|
+
function useUpdateNotice(notice = updateNotice) {
|
|
45
|
+
return useSyncExternalStore2((listener) => notice.subscribe(listener), () => notice.current(), () => {
|
|
46
|
+
return;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/pwa/react/use-service-worker.ts
|
|
51
|
+
function useServiceWorker(options) {
|
|
52
|
+
const { enabled, url, scope, notice = updateNotice } = options;
|
|
53
|
+
const onError = useRef(options.onError);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
onError.current = options.onError;
|
|
56
|
+
});
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
startServiceWorker({ navigator }, {
|
|
59
|
+
enabled,
|
|
60
|
+
notice,
|
|
61
|
+
onError: (error) => onError.current?.(error),
|
|
62
|
+
...url !== undefined && { url },
|
|
63
|
+
...scope !== undefined && { scope }
|
|
64
|
+
});
|
|
65
|
+
}, [enabled, url, scope, notice]);
|
|
66
|
+
}
|
|
67
|
+
export {
|
|
68
|
+
updateNotice,
|
|
69
|
+
useOnline,
|
|
70
|
+
useServiceWorker,
|
|
71
|
+
useUpdateNotice
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
//# debugId=DC45CFDED1D0414264756E2164756E21
|
|
75
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pwa/react/use-online.ts", "../src/pwa/react/use-service-worker.ts", "../src/pwa/model/start-service-worker.ts", "../src/pwa/react/use-update-notice.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { useSyncExternalStore } from 'react';\n\nfunction subscribe(onChange: () => void) {\n\twindow.addEventListener('online', onChange);\n\twindow.addEventListener('offline', onChange);\n\treturn () => {\n\t\twindow.removeEventListener('online', onChange);\n\t\twindow.removeEventListener('offline', onChange);\n\t};\n}\n\n/**\n * Whether the browser says it is online. Online on the server and during\n * hydration, so the first client render matches the server's.\n */\nexport function useOnline(): boolean {\n\treturn useSyncExternalStore(\n\t\tsubscribe,\n\t\t() => navigator.onLine,\n\t\t() => true,\n\t);\n}\n",
|
|
6
|
+
"import { useEffect, useRef } from 'react';\nimport { startServiceWorker } from '../model/start-service-worker';\nimport type { UpdateNoticeStore } from '../model/update-notice';\nimport { updateNotice } from './use-update-notice';\n\nexport interface ServiceWorkerOptions {\n\t/**\n\t * Whether to register at all. The app decides, typically\n\t * `import.meta.env.PROD`: a worker under a dev server caches the code being\n\t * edited. This package never reads `import.meta.env` itself.\n\t */\n\tenabled: boolean;\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\t/** Where a waiting version is offered. The page's `updateNotice` by default. */\n\tnotice?: UpdateNoticeStore;\n\t/**\n\t * Called when registering fails (private mode, a blocked script). The app\n\t * works without a worker, so by default the error is dropped. An `onError`\n\t * that throws becomes an unhandled rejection.\n\t */\n\tonError?: (error: unknown) => void;\n}\n\n/**\n * Registers the service worker after `load` when `enabled`, in a browser\n * that has one. When a new version waits, `notice` offers it (`useUpdateNotice`\n * reads it); nothing activates it unasked. Does nothing on the server.\n *\n * The options are read when the effect runs, which is on mount and when\n * `enabled`, `url`, `scope` or `notice` change; `onError` is always the\n * latest. The listeners it adds (the update check, the controller change)\n * stay for the page's life: there is no cleanup, a page has one worker.\n */\nexport function useServiceWorker(options: ServiceWorkerOptions): void {\n\tconst { enabled, url, scope, notice = updateNotice } = options;\n\tconst onError = useRef(options.onError);\n\tuseEffect(() => {\n\t\tonError.current = options.onError;\n\t});\n\tuseEffect(() => {\n\t\tstartServiceWorker(\n\t\t\t{ navigator },\n\t\t\t{\n\t\t\t\tenabled,\n\t\t\t\tnotice,\n\t\t\t\tonError: (error) => onError.current?.(error),\n\t\t\t\t...(url !== undefined && { url }),\n\t\t\t\t...(scope !== undefined && { scope }),\n\t\t\t},\n\t\t);\n\t}, [enabled, url, scope, notice]);\n}\n",
|
|
7
|
+
"import type { ServiceWorkerContainerLike } from './registration';\nimport { afterLoad, registerServiceWorker } from './registration';\nimport type { UpdateNoticeStore } from './update-notice';\n\n/** What starting reads of the browser: `navigator`, which may have no worker. */\nexport interface ServiceWorkerEnvironment {\n\tnavigator: object & { serviceWorker?: ServiceWorkerContainerLike };\n}\n\nexport interface StartServiceWorkerOptions {\n\tenabled: boolean;\n\turl?: string;\n\tscope?: string;\n\tnotice: Pick<UpdateNoticeStore, 'offer'>;\n\tonError?: (error: unknown) => void;\n}\n\n/**\n * The body of `useServiceWorker`'s effect, a plain function so that it is\n * tested without React: registers after `load` when `enabled` and the\n * browser has a worker container, offers each waiting version to `notice`,\n * and hands a failed registration to `onError`. The listeners it adds live\n * for the page.\n */\nexport function startServiceWorker(\n\tenvironment: ServiceWorkerEnvironment,\n\toptions: StartServiceWorkerOptions,\n): void {\n\tconst { navigator } = environment;\n\tif (!options.enabled || !('serviceWorker' in navigator)) return;\n\tconst container = navigator.serviceWorker;\n\tif (!container) return;\n\tconst { url, scope, notice, onError } = options;\n\tafterLoad(() => {\n\t\tregisterServiceWorker(\n\t\t\tcontainer,\n\t\t\t(worker, apply) => notice.offer(() => apply(worker)),\n\t\t\t{\n\t\t\t\t...(url !== undefined && { url }),\n\t\t\t\t...(scope !== undefined && { scope }),\n\t\t\t},\n\t\t).catch((error: unknown) => onError?.(error));\n\t});\n}\n",
|
|
8
|
+
"import { useSyncExternalStore } from 'react';\nimport {\n\tcreateUpdateNotice,\n\tsessionStore,\n\ttype UpdateNotice,\n\ttype UpdateNoticeStore,\n} from '../model/update-notice';\n\n/**\n * The page's one notice, which `useServiceWorker` offers and\n * `useUpdateNotice` reads. It lives here, not in `/pwa`, so that the model a\n * worker bundle imports makes nothing at load.\n */\nexport const updateNotice: UpdateNoticeStore = createUpdateNotice(sessionStore);\n\n/**\n * The update notice to show: a new version waits and \"Later\" was not asked.\n * None on the server. `notice` is the page's `updateNotice` unless the app\n * made a store of its own.\n */\nexport function useUpdateNotice(\n\tnotice: UpdateNoticeStore = updateNotice,\n): UpdateNotice | undefined {\n\treturn useSyncExternalStore(\n\t\t(listener) => notice.subscribe(listener),\n\t\t() => notice.current(),\n\t\t() => undefined,\n\t);\n}\n"
|
|
9
|
+
],
|
|
10
|
+
"mappings": ";;;;;;;;AAAA;AAEA,SAAS,SAAS,CAAC,UAAsB;AAAA,EACxC,OAAO,iBAAiB,UAAU,QAAQ;AAAA,EAC1C,OAAO,iBAAiB,WAAW,QAAQ;AAAA,EAC3C,OAAO,MAAM;AAAA,IACZ,OAAO,oBAAoB,UAAU,QAAQ;AAAA,IAC7C,OAAO,oBAAoB,WAAW,QAAQ;AAAA;AAAA;AAQzC,SAAS,SAAS,GAAY;AAAA,EACpC,OAAO,qBACN,WACA,MAAM,UAAU,QAChB,MAAM,IACP;AAAA;;ACpBD;;;ACwBO,SAAS,kBAAkB,CACjC,aACA,SACO;AAAA,EACP,QAAQ,0BAAc;AAAA,EACtB,IAAI,CAAC,QAAQ,WAAW,EAAE,mBAAmB;AAAA,IAAY;AAAA,EACzD,MAAM,YAAY,WAAU;AAAA,EAC5B,IAAI,CAAC;AAAA,IAAW;AAAA,EAChB,QAAQ,KAAK,OAAO,QAAQ,YAAY;AAAA,EACxC,WAAU,MAAM;AAAA,IACf,uBACC,WACA,CAAC,QAAQ,UAAU,OAAO,MAAM,MAAM,MAAM,MAAM,CAAC,GACnD;AAAA,SACK,QAAQ,aAAa,EAAE,IAAI;AAAA,SAC3B,UAAU,aAAa,EAAE,MAAM;AAAA,IACpC,CACD,EAAE,MAAM,CAAC,UAAmB,UAAU,KAAK,CAAC;AAAA,GAC5C;AAAA;;;AC1CF,iCAAS;AAaF,IAAM,eAAkC,oBAAmB,aAAY;AAOvE,SAAS,eAAe,CAC9B,SAA4B,cACD;AAAA,EAC3B,OAAO,sBACN,CAAC,aAAa,OAAO,UAAU,QAAQ,GACvC,MAAM,OAAO,QAAQ,GACrB,MAAG;AAAA,IAAG;AAAA,GACP;AAAA;;;AFSM,SAAS,gBAAgB,CAAC,SAAqC;AAAA,EACrE,QAAQ,SAAS,KAAK,OAAO,SAAS,iBAAiB;AAAA,EACvD,MAAM,UAAU,OAAO,QAAQ,OAAO;AAAA,EACtC,UAAU,MAAM;AAAA,IACf,QAAQ,UAAU,QAAQ;AAAA,GAC1B;AAAA,EACD,UAAU,MAAM;AAAA,IACf,mBACC,EAAE,UAAU,GACZ;AAAA,MACC;AAAA,MACA;AAAA,MACA,SAAS,CAAC,UAAU,QAAQ,UAAU,KAAK;AAAA,SACvC,QAAQ,aAAa,EAAE,IAAI;AAAA,SAC3B,UAAU,aAAa,EAAE,MAAM;AAAA,IACpC,CACD;AAAA,KACE,CAAC,SAAS,KAAK,OAAO,MAAM,CAAC;AAAA;",
|
|
11
|
+
"debugId": "DC45CFDED1D0414264756E2164756E21",
|
|
12
|
+
"names": []
|
|
13
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-online.d.ts","sourceRoot":"","sources":["../../../src/pwa/react/use-online.ts"],"names":[],"mappings":"AAWA;;;GAGG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAMnC"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { UpdateNoticeStore } from '../model/update-notice';
|
|
2
|
+
export interface ServiceWorkerOptions {
|
|
3
|
+
/**
|
|
4
|
+
* Whether to register at all. The app decides, typically
|
|
5
|
+
* `import.meta.env.PROD`: a worker under a dev server caches the code being
|
|
6
|
+
* edited. This package never reads `import.meta.env` itself.
|
|
7
|
+
*/
|
|
8
|
+
enabled: boolean;
|
|
9
|
+
/** The worker's path. `SERVICE_WORKER_URL` by default. */
|
|
10
|
+
url?: string;
|
|
11
|
+
/** The scope it controls. `'/'` by default. */
|
|
12
|
+
scope?: string;
|
|
13
|
+
/** Where a waiting version is offered. The page's `updateNotice` by default. */
|
|
14
|
+
notice?: UpdateNoticeStore;
|
|
15
|
+
/**
|
|
16
|
+
* Called when registering fails (private mode, a blocked script). The app
|
|
17
|
+
* works without a worker, so by default the error is dropped. An `onError`
|
|
18
|
+
* that throws becomes an unhandled rejection.
|
|
19
|
+
*/
|
|
20
|
+
onError?: (error: unknown) => void;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Registers the service worker after `load` when `enabled`, in a browser
|
|
24
|
+
* that has one. When a new version waits, `notice` offers it (`useUpdateNotice`
|
|
25
|
+
* reads it); nothing activates it unasked. Does nothing on the server.
|
|
26
|
+
*
|
|
27
|
+
* The options are read when the effect runs, which is on mount and when
|
|
28
|
+
* `enabled`, `url`, `scope` or `notice` change; `onError` is always the
|
|
29
|
+
* latest. The listeners it adds (the update check, the controller change)
|
|
30
|
+
* stay for the page's life: there is no cleanup, a page has one worker.
|
|
31
|
+
*/
|
|
32
|
+
export declare function useServiceWorker(options: ServiceWorkerOptions): void;
|
|
33
|
+
//# sourceMappingURL=use-service-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-service-worker.d.ts","sourceRoot":"","sources":["../../../src/pwa/react/use-service-worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGhE,MAAM,WAAW,oBAAoB;IACpC;;;;OAIG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,gFAAgF;IAChF,MAAM,CAAC,EAAE,iBAAiB,CAAC;IAC3B;;;;OAIG;IACH,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,IAAI,CAAC;CACnC;AAED;;;;;;;;;GASG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,oBAAoB,GAAG,IAAI,CAkBpE"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type UpdateNotice, type UpdateNoticeStore } from '../model/update-notice';
|
|
2
|
+
/**
|
|
3
|
+
* The page's one notice, which `useServiceWorker` offers and
|
|
4
|
+
* `useUpdateNotice` reads. It lives here, not in `/pwa`, so that the model a
|
|
5
|
+
* worker bundle imports makes nothing at load.
|
|
6
|
+
*/
|
|
7
|
+
export declare const updateNotice: UpdateNoticeStore;
|
|
8
|
+
/**
|
|
9
|
+
* The update notice to show: a new version waits and "Later" was not asked.
|
|
10
|
+
* None on the server. `notice` is the page's `updateNotice` unless the app
|
|
11
|
+
* made a store of its own.
|
|
12
|
+
*/
|
|
13
|
+
export declare function useUpdateNotice(notice?: UpdateNoticeStore): UpdateNotice | undefined;
|
|
14
|
+
//# sourceMappingURL=use-update-notice.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"use-update-notice.d.ts","sourceRoot":"","sources":["../../../src/pwa/react/use-update-notice.ts"],"names":[],"mappings":"AACA,OAAO,EAGN,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,MAAM,wBAAwB,CAAC;AAEhC;;;;GAIG;AACH,eAAO,MAAM,YAAY,EAAE,iBAAoD,CAAC;AAEhF;;;;GAIG;AACH,wBAAgB,eAAe,CAC9B,MAAM,GAAE,iBAAgC,GACtC,YAAY,GAAG,SAAS,CAM1B"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@alxia/react-router/pwa/server`: the server's side of an installable
|
|
3
|
+
* app, `noCache` for the worker's script. `@alxia/core` is its only import,
|
|
4
|
+
* so keep it out of the page and the worker.
|
|
5
|
+
*/
|
|
6
|
+
export { noCache, revalidated, WORKER_POLICY } from './no-cache';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/pwa/server/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,EAAE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/pwa/server/no-cache.ts
|
|
2
|
+
import { defineMiddleware, withHeaders } from "@alxia/core";
|
|
3
|
+
var WORKER_POLICY = "default-src 'none'; connect-src 'self'";
|
|
4
|
+
function revalidated(response, policy = WORKER_POLICY) {
|
|
5
|
+
return withHeaders(response, (headers) => {
|
|
6
|
+
headers.set("cache-control", "no-cache");
|
|
7
|
+
headers.set("content-security-policy", policy);
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
var noCache = defineMiddleware(async (_ctx, next) => revalidated(await next()));
|
|
11
|
+
export {
|
|
12
|
+
WORKER_POLICY,
|
|
13
|
+
noCache,
|
|
14
|
+
revalidated
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
//# debugId=D042AA38FD15BC2064756E2164756E21
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/pwa/server/no-cache.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { defineMiddleware, withHeaders } from '@alxia/core';\n\n/**\n * The service worker's own policy: a worker takes the policy its script\n * came with, not the pages'. It runs no other script and fetches this site\n * only (the precache, navigations); its routes never fetch another origin.\n */\nexport const WORKER_POLICY = \"default-src 'none'; connect-src 'self'\";\n\n/**\n * The worker's script with `Cache-Control: no-cache`: the client's\n * top-level files are served for an hour, and the worker's script must be\n * revalidated on every update check, or an update waits that long. And with\n * the worker's policy (`WORKER_POLICY`, or your own `policy`), which no\n * other static file needs: none of them is a document but an offline page,\n * which carries its own in a `<meta>`.\n */\nexport function revalidated(\n\tresponse: Response,\n\tpolicy: string = WORKER_POLICY,\n): Response {\n\treturn withHeaders(response, (headers) => {\n\t\theaders.set('cache-control', 'no-cache');\n\t\theaders.set('content-security-policy', policy);\n\t});\n}\n\n/**\n * For `use(SERVICE_WORKER_URL, noCache)` in `createServer({ beforeAll })`,\n * before the client's files: they answer without calling `next` for a\n * middleware declared after them.\n */\nexport const noCache = defineMiddleware(async (_ctx, next) =>\n\trevalidated(await next()),\n);\n"
|
|
6
|
+
],
|
|
7
|
+
"mappings": ";AAAA;AAOO,IAAM,gBAAgB;AAUtB,SAAS,WAAW,CAC1B,UACA,SAAiB,eACN;AAAA,EACX,OAAO,YAAY,UAAU,CAAC,YAAY;AAAA,IACzC,QAAQ,IAAI,iBAAiB,UAAU;AAAA,IACvC,QAAQ,IAAI,2BAA2B,MAAM;AAAA,GAC7C;AAAA;AAQK,IAAM,UAAU,iBAAiB,OAAO,MAAM,SACpD,YAAY,MAAM,KAAK,CAAC,CACzB;",
|
|
8
|
+
"debugId": "D042AA38FD15BC2064756E2164756E21",
|
|
9
|
+
"names": []
|
|
10
|
+
}
|