@cogenta/render 0.1.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/dist/astro/integration.d.ts +57 -0
- package/dist/astro/integration.d.ts.map +1 -0
- package/dist/astro/integration.js +72 -0
- package/dist/astro/integration.js.map +1 -0
- package/dist/build/index.d.ts +7 -0
- package/dist/build/index.d.ts.map +1 -0
- package/dist/build/index.js +7 -0
- package/dist/build/index.js.map +1 -0
- package/dist/build/integration.d.ts +25 -0
- package/dist/build/integration.d.ts.map +1 -0
- package/dist/build/integration.js +41 -0
- package/dist/build/integration.js.map +1 -0
- package/dist/build/plan.d.ts +41 -0
- package/dist/build/plan.d.ts.map +1 -0
- package/dist/build/plan.js +48 -0
- package/dist/build/plan.js.map +1 -0
- package/dist/build/refusal.d.ts +14 -0
- package/dist/build/refusal.d.ts.map +1 -0
- package/dist/build/refusal.js +110 -0
- package/dist/build/refusal.js.map +1 -0
- package/dist/build/requirements.d.ts +59 -0
- package/dist/build/requirements.d.ts.map +1 -0
- package/dist/build/requirements.js +74 -0
- package/dist/build/requirements.js.map +1 -0
- package/dist/build/run-build.d.ts +43 -0
- package/dist/build/run-build.d.ts.map +1 -0
- package/dist/build/run-build.js +73 -0
- package/dist/build/run-build.js.map +1 -0
- package/dist/build/targets.d.ts +47 -0
- package/dist/build/targets.d.ts.map +1 -0
- package/dist/build/targets.js +69 -0
- package/dist/build/targets.js.map +1 -0
- package/dist/cache/index.d.ts +7 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +4 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/page-cache.d.ts +64 -0
- package/dist/cache/page-cache.d.ts.map +1 -0
- package/dist/cache/page-cache.js +128 -0
- package/dist/cache/page-cache.js.map +1 -0
- package/dist/cache/recorder.d.ts +49 -0
- package/dist/cache/recorder.d.ts.map +1 -0
- package/dist/cache/recorder.js +77 -0
- package/dist/cache/recorder.js.map +1 -0
- package/dist/cache/tags.d.ts +55 -0
- package/dist/cache/tags.d.ts.map +1 -0
- package/dist/cache/tags.js +62 -0
- package/dist/cache/tags.js.map +1 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +44 -0
- package/dist/config.js.map +1 -0
- package/dist/content/client.d.ts +35 -0
- package/dist/content/client.d.ts.map +1 -0
- package/dist/content/client.js +155 -0
- package/dist/content/client.js.map +1 -0
- package/dist/content/types.d.ts +104 -0
- package/dist/content/types.d.ts.map +1 -0
- package/dist/content/types.js +16 -0
- package/dist/content/types.js.map +1 -0
- package/dist/context/render-context.d.ts +31 -0
- package/dist/context/render-context.d.ts.map +1 -0
- package/dist/context/render-context.js +152 -0
- package/dist/context/render-context.js.map +1 -0
- package/dist/context/types.d.ts +49 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +2 -0
- package/dist/context/types.js.map +1 -0
- package/dist/images/errors.d.ts +20 -0
- package/dist/images/errors.d.ts.map +1 -0
- package/dist/images/errors.js +58 -0
- package/dist/images/errors.js.map +1 -0
- package/dist/images/format.d.ts +18 -0
- package/dist/images/format.d.ts.map +1 -0
- package/dist/images/format.js +34 -0
- package/dist/images/format.js.map +1 -0
- package/dist/images/geometry.d.ts +46 -0
- package/dist/images/geometry.d.ts.map +1 -0
- package/dist/images/geometry.js +127 -0
- package/dist/images/geometry.js.map +1 -0
- package/dist/images/index.d.ts +29 -0
- package/dist/images/index.d.ts.map +1 -0
- package/dist/images/index.js +30 -0
- package/dist/images/index.js.map +1 -0
- package/dist/images/pipeline.d.ts +51 -0
- package/dist/images/pipeline.d.ts.map +1 -0
- package/dist/images/pipeline.js +105 -0
- package/dist/images/pipeline.js.map +1 -0
- package/dist/images/sharp.d.ts +57 -0
- package/dist/images/sharp.d.ts.map +1 -0
- package/dist/images/sharp.js +139 -0
- package/dist/images/sharp.js.map +1 -0
- package/dist/images/srcset.d.ts +33 -0
- package/dist/images/srcset.d.ts.map +1 -0
- package/dist/images/srcset.js +94 -0
- package/dist/images/srcset.js.map +1 -0
- package/dist/images/store.d.ts +32 -0
- package/dist/images/store.d.ts.map +1 -0
- package/dist/images/store.js +54 -0
- package/dist/images/store.js.map +1 -0
- package/dist/images/types.d.ts +128 -0
- package/dist/images/types.d.ts.map +1 -0
- package/dist/images/types.js +22 -0
- package/dist/images/types.js.map +1 -0
- package/dist/images/wasm.d.ts +43 -0
- package/dist/images/wasm.d.ts.map +1 -0
- package/dist/images/wasm.js +128 -0
- package/dist/images/wasm.js.map +1 -0
- package/dist/index.d.ts +29 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +29 -0
- package/dist/index.js.map +1 -0
- package/dist/pwa/build.d.ts +39 -0
- package/dist/pwa/build.d.ts.map +1 -0
- package/dist/pwa/build.js +38 -0
- package/dist/pwa/build.js.map +1 -0
- package/dist/pwa/client.d.ts +90 -0
- package/dist/pwa/client.d.ts.map +1 -0
- package/dist/pwa/client.js +76 -0
- package/dist/pwa/client.js.map +1 -0
- package/dist/pwa/index.d.ts +29 -0
- package/dist/pwa/index.d.ts.map +1 -0
- package/dist/pwa/index.js +22 -0
- package/dist/pwa/index.js.map +1 -0
- package/dist/pwa/manifest.d.ts +69 -0
- package/dist/pwa/manifest.d.ts.map +1 -0
- package/dist/pwa/manifest.js +108 -0
- package/dist/pwa/manifest.js.map +1 -0
- package/dist/pwa/offline.d.ts +44 -0
- package/dist/pwa/offline.d.ts.map +1 -0
- package/dist/pwa/offline.js +94 -0
- package/dist/pwa/offline.js.map +1 -0
- package/dist/pwa/service-worker.d.ts +71 -0
- package/dist/pwa/service-worker.d.ts.map +1 -0
- package/dist/pwa/service-worker.js +119 -0
- package/dist/pwa/service-worker.js.map +1 -0
- package/dist/pwa/strategy.d.ts +36 -0
- package/dist/pwa/strategy.d.ts.map +1 -0
- package/dist/pwa/strategy.js +180 -0
- package/dist/pwa/strategy.js.map +1 -0
- package/dist/pwa/sw-runtime.d.ts +109 -0
- package/dist/pwa/sw-runtime.d.ts.map +1 -0
- package/dist/pwa/sw-runtime.js +214 -0
- package/dist/pwa/sw-runtime.js.map +1 -0
- package/dist/pwa/types.d.ts +107 -0
- package/dist/pwa/types.d.ts.map +1 -0
- package/dist/pwa/types.js +18 -0
- package/dist/pwa/types.js.map +1 -0
- package/dist/pwa/version.d.ts +45 -0
- package/dist/pwa/version.d.ts.map +1 -0
- package/dist/pwa/version.js +89 -0
- package/dist/pwa/version.js.map +1 -0
- package/dist/skin/color.d.ts +50 -0
- package/dist/skin/color.d.ts.map +1 -0
- package/dist/skin/color.js +176 -0
- package/dist/skin/color.js.map +1 -0
- package/dist/skin/css.d.ts +19 -0
- package/dist/skin/css.d.ts.map +1 -0
- package/dist/skin/css.js +69 -0
- package/dist/skin/css.js.map +1 -0
- package/dist/skin/errors.d.ts +22 -0
- package/dist/skin/errors.d.ts.map +1 -0
- package/dist/skin/errors.js +59 -0
- package/dist/skin/errors.js.map +1 -0
- package/dist/skin/index.d.ts +15 -0
- package/dist/skin/index.d.ts.map +1 -0
- package/dist/skin/index.js +11 -0
- package/dist/skin/index.js.map +1 -0
- package/dist/skin/sheet.d.ts +42 -0
- package/dist/skin/sheet.d.ts.map +1 -0
- package/dist/skin/sheet.js +36 -0
- package/dist/skin/sheet.js.map +1 -0
- package/dist/skin/tokens.d.ts +90 -0
- package/dist/skin/tokens.d.ts.map +1 -0
- package/dist/skin/tokens.js +56 -0
- package/dist/skin/tokens.js.map +1 -0
- package/dist/skin/validate.d.ts +14 -0
- package/dist/skin/validate.d.ts.map +1 -0
- package/dist/skin/validate.js +272 -0
- package/dist/skin/validate.js.map +1 -0
- package/dist/theme/load-theme.d.ts +37 -0
- package/dist/theme/load-theme.d.ts.map +1 -0
- package/dist/theme/load-theme.js +102 -0
- package/dist/theme/load-theme.js.map +1 -0
- package/dist/theme/manifest.d.ts +48 -0
- package/dist/theme/manifest.d.ts.map +1 -0
- package/dist/theme/manifest.js +36 -0
- package/dist/theme/manifest.js.map +1 -0
- package/dist/theme/verify/forbidden.d.ts +39 -0
- package/dist/theme/verify/forbidden.d.ts.map +1 -0
- package/dist/theme/verify/forbidden.js +107 -0
- package/dist/theme/verify/forbidden.js.map +1 -0
- package/dist/theme/verify/scan-file.d.ts +15 -0
- package/dist/theme/verify/scan-file.d.ts.map +1 -0
- package/dist/theme/verify/scan-file.js +151 -0
- package/dist/theme/verify/scan-file.js.map +1 -0
- package/dist/theme/verify/scanner.d.ts +52 -0
- package/dist/theme/verify/scanner.d.ts.map +1 -0
- package/dist/theme/verify/scanner.js +304 -0
- package/dist/theme/verify/scanner.js.map +1 -0
- package/dist/theme/verify/verify-theme.d.ts +44 -0
- package/dist/theme/verify/verify-theme.d.ts.map +1 -0
- package/dist/theme/verify/verify-theme.js +179 -0
- package/dist/theme/verify/verify-theme.js.map +1 -0
- package/package.json +56 -0
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page side of the PWA: registration, and the way out.
|
|
3
|
+
*
|
|
4
|
+
* These functions take the browser objects as arguments instead of reaching for
|
|
5
|
+
* `navigator` and `caches`. That is what lets them be unit-tested at all — the
|
|
6
|
+
* package compiles without the DOM library — and it keeps the surface the code
|
|
7
|
+
* depends on down to the six methods declared here. A caller in the admin (L2)
|
|
8
|
+
* passes `navigator.serviceWorker` and `caches`; the real objects satisfy these
|
|
9
|
+
* interfaces structurally, with no cast.
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_SERVICE_WORKER_URL = '/sw.js';
|
|
12
|
+
/**
|
|
13
|
+
* Registers the worker at the root scope.
|
|
14
|
+
*
|
|
15
|
+
* Returns `null` rather than throwing when the browser has no service worker
|
|
16
|
+
* support, or when registration fails: a site that renders on the server does
|
|
17
|
+
* not stop working because an offline nicety could not be installed (rule R1
|
|
18
|
+
* applied to the browser).
|
|
19
|
+
*/
|
|
20
|
+
export async function registerServiceWorker(container, options = {}) {
|
|
21
|
+
if (container === undefined)
|
|
22
|
+
return null;
|
|
23
|
+
try {
|
|
24
|
+
return await container.register(options.scriptUrl ?? DEFAULT_SERVICE_WORKER_URL, {
|
|
25
|
+
scope: options.scope ?? '/',
|
|
26
|
+
updateViaCache: 'none',
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Deletes the caches this site owns. Names outside `cachePrefix` are left
|
|
35
|
+
* alone: another application may share the origin, and deleting a neighbour's
|
|
36
|
+
* cache is a worse incident than the stale content this is fixing.
|
|
37
|
+
*/
|
|
38
|
+
export async function purgeSiteCaches(cacheStorage, cachePrefix) {
|
|
39
|
+
if (cacheStorage === undefined)
|
|
40
|
+
return [];
|
|
41
|
+
const names = await cacheStorage.keys();
|
|
42
|
+
const mine = names.filter((name) => name.startsWith(`${cachePrefix}:`));
|
|
43
|
+
await Promise.all(mine.map((name) => cacheStorage.delete(name)));
|
|
44
|
+
return mine;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The "clear client cache" button of the admin, as a function.
|
|
48
|
+
*
|
|
49
|
+
* The order is deliberate. The active worker is asked to purge and unregister
|
|
50
|
+
* itself first, because it is the only party that can empty its caches while it
|
|
51
|
+
* is still running. Then the registrations are unregistered from the page side,
|
|
52
|
+
* which also covers a worker that is broken, hung, or from a build that never
|
|
53
|
+
* understood the message. Only then are the remaining caches deleted directly —
|
|
54
|
+
* doing that first would simply let the still-running worker refill them.
|
|
55
|
+
*
|
|
56
|
+
* Even after all of this the current page is still controlled by the old
|
|
57
|
+
* worker, which is why the report insists on a reload.
|
|
58
|
+
*/
|
|
59
|
+
export async function resetPwaClient(environment, cachePrefix) {
|
|
60
|
+
const container = environment.serviceWorker;
|
|
61
|
+
const cacheStorage = environment.caches;
|
|
62
|
+
const supported = container !== undefined && cacheStorage !== undefined;
|
|
63
|
+
const controlled = container?.controller != null;
|
|
64
|
+
let registrationsRemoved = 0;
|
|
65
|
+
if (container !== undefined) {
|
|
66
|
+
const registrations = await container.getRegistrations();
|
|
67
|
+
for (const registration of registrations) {
|
|
68
|
+
registration.active?.postMessage({ type: 'cogenta:unregister' });
|
|
69
|
+
}
|
|
70
|
+
const results = await Promise.all(registrations.map((registration) => registration.unregister().catch(() => false)));
|
|
71
|
+
registrationsRemoved = results.filter((removed) => removed).length;
|
|
72
|
+
}
|
|
73
|
+
const cachesDeleted = await purgeSiteCaches(cacheStorage, cachePrefix);
|
|
74
|
+
return { registrationsRemoved, cachesDeleted, reloadRequired: controlled, supported };
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/pwa/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAwCH,MAAM,CAAC,MAAM,0BAA0B,GAAG,QAAQ,CAAA;AAElD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,qBAAqB,CACzC,SAAmD,EACnD,OAAO,GAA6D,EAAE;IAEtE,IAAI,SAAS,KAAK,SAAS;QAAE,OAAO,IAAI,CAAA;IACxC,IAAI,CAAC;QACH,OAAO,MAAM,SAAS,CAAC,QAAQ,CAAC,OAAO,CAAC,SAAS,IAAI,0BAA0B,EAAE;YAC/E,KAAK,EAAE,OAAO,CAAC,KAAK,IAAI,GAAG;YAC3B,cAAc,EAAE,MAAM;SACvB,CAAC,CAAA;IACJ,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAA;IACb,CAAC;AACH,CAAC;AAiBD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,YAA4C,EAC5C,WAAmB;IAEnB,IAAI,YAAY,KAAK,SAAS;QAAE,OAAO,EAAE,CAAA;IACzC,MAAM,KAAK,GAAG,MAAM,YAAY,CAAC,IAAI,EAAE,CAAA;IACvC,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,CAAC,CAAC,CAAA;IACvE,MAAM,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IAChE,OAAO,IAAI,CAAA;AACb,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,KAAK,UAAU,cAAc,CAClC,WAAiC,EACjC,WAAmB;IAEnB,MAAM,SAAS,GAAG,WAAW,CAAC,aAAa,CAAA;IAC3C,MAAM,YAAY,GAAG,WAAW,CAAC,MAAM,CAAA;IACvC,MAAM,SAAS,GAAG,SAAS,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,CAAA;IACvE,MAAM,UAAU,GAAG,SAAS,EAAE,UAAU,IAAI,IAAI,CAAA;IAEhD,IAAI,oBAAoB,GAAG,CAAC,CAAA;IAC5B,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,MAAM,aAAa,GAAG,MAAM,SAAS,CAAC,gBAAgB,EAAE,CAAA;QACxD,KAAK,MAAM,YAAY,IAAI,aAAa,EAAE,CAAC;YACzC,YAAY,CAAC,MAAM,EAAE,WAAW,CAAC,EAAE,IAAI,EAAE,oBAAoB,EAAE,CAAC,CAAA;QAClE,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,aAAa,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC,CAClF,CAAA;QACD,oBAAoB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,MAAM,CAAA;IACpE,CAAC;IAED,MAAM,aAAa,GAAG,MAAM,eAAe,CAAC,YAAY,EAAE,WAAW,CAAC,CAAA;IAEtE,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,EAAE,SAAS,EAAE,CAAA;AACvF,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PWA layer: manifest, service worker, per-resource cache strategies, offline
|
|
3
|
+
* page, and — the part that has to exist before the first deploy, not after the
|
|
4
|
+
* first incident — a working way to purge the caches and remove the worker.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately dependency-free. Workbox would cover most of this, but it is a
|
|
7
|
+
* large dependency for code we would still have to understand line by line the
|
|
8
|
+
* day a visitor reports stale content, and rule R9 asks for zero dependencies
|
|
9
|
+
* before a small one. What is here is a route table, three caching strategies
|
|
10
|
+
* and a generational purge; the value Workbox adds over that is a build plugin
|
|
11
|
+
* we do not want and a precache manifest we deliberately keep tiny.
|
|
12
|
+
*/
|
|
13
|
+
export type { PwaArtefacts, PwaBuildInput } from './build.js';
|
|
14
|
+
export { buildPwaAssets, DEFAULT_OFFLINE_URL } from './build.js';
|
|
15
|
+
export type { ClientCacheStorage, ClientRegisterOptions, ClientRegistration, ClientServiceWorkerContainer, ClientWorker, PwaClientEnvironment, PwaResetReport, } from './client.js';
|
|
16
|
+
export { DEFAULT_SERVICE_WORKER_URL, purgeSiteCaches, registerServiceWorker, resetPwaClient, } from './client.js';
|
|
17
|
+
export type { DisplayMode, InstallabilityProblem, ManifestIcon, ManifestInput, WebAppManifest, } from './manifest.js';
|
|
18
|
+
export { assertInstallable, buildManifest, checkInstallability, MANIFEST_CONTENT_TYPE, renderManifest, } from './manifest.js';
|
|
19
|
+
export type { OfflinePageOptions } from './offline.js';
|
|
20
|
+
export { OFFLINE_PAGE_SCRIPT, renderOfflinePage } from './offline.js';
|
|
21
|
+
export { renderServiceWorker, renderTombstoneServiceWorker, SERVICE_WORKER_HEADERS, } from './service-worker.js';
|
|
22
|
+
export { chooseStrategy, DEFAULT_ROUTES, IMMUTABLE_ASSET_PATTERN } from './strategy.js';
|
|
23
|
+
export type { ServiceWorkerScope } from './sw-runtime.js';
|
|
24
|
+
export { SW_MESSAGE_ACTIVATED, SW_MESSAGE_PURGE, SW_MESSAGE_UNREGISTER, SW_MESSAGE_UNREGISTERED, serviceWorkerMain, tombstoneMain, } from './sw-runtime.js';
|
|
25
|
+
export type { CacheBucket, CacheStrategy, PwaConfig, RequestDescriptor, RouteRule, StrategyDecision, } from './types.js';
|
|
26
|
+
export { CACHE_BUCKETS } from './types.js';
|
|
27
|
+
export type { ParsedCacheName } from './version.js';
|
|
28
|
+
export { bucketCacheName, cacheNameFor, cachesToPurge, computeCacheVersion, DEFAULT_CACHE_PREFIX, parseCacheName, } from './version.js';
|
|
29
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pwa/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,YAAY,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC7D,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAChE,YAAY,EACV,kBAAkB,EAClB,qBAAqB,EACrB,kBAAkB,EAClB,4BAA4B,EAC5B,YAAY,EACZ,oBAAoB,EACpB,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,qBAAqB,EACrB,cAAc,GACf,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,WAAW,EACX,qBAAqB,EACrB,YAAY,EACZ,aAAa,EACb,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,GACf,MAAM,eAAe,CAAA;AACtB,YAAY,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAA;AACtD,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AACvF,YAAY,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,aAAa,GACd,MAAM,iBAAiB,CAAA;AACxB,YAAY,EACV,WAAW,EACX,aAAa,EACb,SAAS,EACT,iBAAiB,EACjB,SAAS,EACT,gBAAgB,GACjB,MAAM,YAAY,CAAA;AACnB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC1C,YAAY,EAAE,eAAe,EAAE,MAAM,cAAc,CAAA;AACnD,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,GACf,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PWA layer: manifest, service worker, per-resource cache strategies, offline
|
|
3
|
+
* page, and — the part that has to exist before the first deploy, not after the
|
|
4
|
+
* first incident — a working way to purge the caches and remove the worker.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately dependency-free. Workbox would cover most of this, but it is a
|
|
7
|
+
* large dependency for code we would still have to understand line by line the
|
|
8
|
+
* day a visitor reports stale content, and rule R9 asks for zero dependencies
|
|
9
|
+
* before a small one. What is here is a route table, three caching strategies
|
|
10
|
+
* and a generational purge; the value Workbox adds over that is a build plugin
|
|
11
|
+
* we do not want and a precache manifest we deliberately keep tiny.
|
|
12
|
+
*/
|
|
13
|
+
export { buildPwaAssets, DEFAULT_OFFLINE_URL } from './build.js';
|
|
14
|
+
export { DEFAULT_SERVICE_WORKER_URL, purgeSiteCaches, registerServiceWorker, resetPwaClient, } from './client.js';
|
|
15
|
+
export { assertInstallable, buildManifest, checkInstallability, MANIFEST_CONTENT_TYPE, renderManifest, } from './manifest.js';
|
|
16
|
+
export { OFFLINE_PAGE_SCRIPT, renderOfflinePage } from './offline.js';
|
|
17
|
+
export { renderServiceWorker, renderTombstoneServiceWorker, SERVICE_WORKER_HEADERS, } from './service-worker.js';
|
|
18
|
+
export { chooseStrategy, DEFAULT_ROUTES, IMMUTABLE_ASSET_PATTERN } from './strategy.js';
|
|
19
|
+
export { SW_MESSAGE_ACTIVATED, SW_MESSAGE_PURGE, SW_MESSAGE_UNREGISTER, SW_MESSAGE_UNREGISTERED, serviceWorkerMain, tombstoneMain, } from './sw-runtime.js';
|
|
20
|
+
export { CACHE_BUCKETS } from './types.js';
|
|
21
|
+
export { bucketCacheName, cacheNameFor, cachesToPurge, computeCacheVersion, DEFAULT_CACHE_PREFIX, parseCacheName, } from './version.js';
|
|
22
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pwa/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAGH,OAAO,EAAE,cAAc,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAA;AAUhE,OAAO,EACL,0BAA0B,EAC1B,eAAe,EACf,qBAAqB,EACrB,cAAc,GACf,MAAM,aAAa,CAAA;AAQpB,OAAO,EACL,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,GACf,MAAM,eAAe,CAAA;AAEtB,OAAO,EAAE,mBAAmB,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAA;AACrE,OAAO,EACL,mBAAmB,EACnB,4BAA4B,EAC5B,sBAAsB,GACvB,MAAM,qBAAqB,CAAA;AAC5B,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAA;AAEvF,OAAO,EACL,oBAAoB,EACpB,gBAAgB,EAChB,qBAAqB,EACrB,uBAAuB,EACvB,iBAAiB,EACjB,aAAa,GACd,MAAM,iBAAiB,CAAA;AASxB,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAE1C,OAAO,EACL,eAAe,EACf,YAAY,EACZ,aAAa,EACb,mBAAmB,EACnB,oBAAoB,EACpB,cAAc,GACf,MAAM,cAAc,CAAA"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The web application manifest, and the installability check that goes with it.
|
|
3
|
+
*
|
|
4
|
+
* Installability is not a property of the manifest alone — the browser also
|
|
5
|
+
* requires a registered service worker that answers navigations offline, which
|
|
6
|
+
* is what the document route and the precached offline page provide. What this
|
|
7
|
+
* module can check is the half that is data, and it checks it *before* the
|
|
8
|
+
* build ships rather than leaving the author to discover in a Lighthouse report
|
|
9
|
+
* that the install button never appeared.
|
|
10
|
+
*/
|
|
11
|
+
export type DisplayMode = 'fullscreen' | 'standalone' | 'minimal-ui' | 'browser';
|
|
12
|
+
export interface ManifestIcon {
|
|
13
|
+
readonly src: string;
|
|
14
|
+
/** `"192x192"`, or `"any"` for a vector. */
|
|
15
|
+
readonly sizes: string;
|
|
16
|
+
readonly type: string;
|
|
17
|
+
/**
|
|
18
|
+
* `any` is drawn as-is; `maskable` fills a platform-defined shape. A single
|
|
19
|
+
* icon may declare both, but then it must survive being cropped to a circle.
|
|
20
|
+
*/
|
|
21
|
+
readonly purpose?: 'any' | 'maskable' | 'any maskable';
|
|
22
|
+
}
|
|
23
|
+
export interface WebAppManifest {
|
|
24
|
+
/** Stable identity of the application. Changing it installs a *new* app. */
|
|
25
|
+
readonly id: string;
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly short_name: string;
|
|
28
|
+
readonly description?: string;
|
|
29
|
+
readonly start_url: string;
|
|
30
|
+
readonly scope: string;
|
|
31
|
+
readonly display: DisplayMode;
|
|
32
|
+
readonly background_color: string;
|
|
33
|
+
readonly theme_color: string;
|
|
34
|
+
readonly lang: string;
|
|
35
|
+
readonly dir: 'ltr' | 'rtl' | 'auto';
|
|
36
|
+
readonly icons: readonly ManifestIcon[];
|
|
37
|
+
}
|
|
38
|
+
export interface ManifestInput {
|
|
39
|
+
readonly name: string;
|
|
40
|
+
readonly shortName?: string;
|
|
41
|
+
readonly description?: string;
|
|
42
|
+
/** Absolute path within the origin. Where the installed app opens. */
|
|
43
|
+
readonly startUrl?: string;
|
|
44
|
+
readonly scope?: string;
|
|
45
|
+
readonly display?: DisplayMode;
|
|
46
|
+
readonly backgroundColor: string;
|
|
47
|
+
readonly themeColor: string;
|
|
48
|
+
readonly lang: string;
|
|
49
|
+
readonly dir?: 'ltr' | 'rtl' | 'auto';
|
|
50
|
+
readonly icons: readonly ManifestIcon[];
|
|
51
|
+
}
|
|
52
|
+
export declare function buildManifest(input: ManifestInput): WebAppManifest;
|
|
53
|
+
export interface InstallabilityProblem {
|
|
54
|
+
readonly field: string;
|
|
55
|
+
readonly problem: string;
|
|
56
|
+
readonly fix: string;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Reports everything that would stop a browser offering installation, in one
|
|
60
|
+
* pass. Reporting one problem per run is the loop that makes an author give up
|
|
61
|
+
* on the third iteration.
|
|
62
|
+
*/
|
|
63
|
+
export declare function checkInstallability(manifest: WebAppManifest): InstallabilityProblem[];
|
|
64
|
+
/** Throws a message an author can act on, listing every problem at once. */
|
|
65
|
+
export declare function assertInstallable(manifest: WebAppManifest): void;
|
|
66
|
+
/** Serialised manifest, ready to write to `manifest.webmanifest`. */
|
|
67
|
+
export declare function renderManifest(manifest: WebAppManifest): string;
|
|
68
|
+
export declare const MANIFEST_CONTENT_TYPE = "application/manifest+json";
|
|
69
|
+
//# sourceMappingURL=manifest.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/pwa/manifest.ts"],"names":[],"mappings":"AAEA;;;;;;;;;GASG;AAEH,MAAM,MAAM,WAAW,GAAG,YAAY,GAAG,YAAY,GAAG,YAAY,GAAG,SAAS,CAAA;AAEhF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB;;;OAGG;IACH,QAAQ,CAAC,OAAO,CAAC,EAAE,KAAK,GAAG,UAAU,GAAG,cAAc,CAAA;CACvD;AAED,MAAM,WAAW,cAAc;IAC7B,4EAA4E;IAC5E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAA;IAC7B,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAA;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;CACxC;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAA;IAC7B,sEAAsE;IACtE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAA;IAC9B,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAA;IAChC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;IACrC,QAAQ,CAAC,KAAK,EAAE,SAAS,YAAY,EAAE,CAAA;CACxC;AAQD,wBAAgB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAsBlE;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAQD;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,qBAAqB,EAAE,CA4DrF;AAED,4EAA4E;AAC5E,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAWhE;AAED,qEAAqE;AACrE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,cAAc,GAAG,MAAM,CAE/D;AAED,eAAO,MAAM,qBAAqB,8BAA8B,CAAA"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { CogentaError } from '@cogenta/core';
|
|
2
|
+
/**
|
|
3
|
+
* Android home-screen labels are truncated around twelve characters, so a long
|
|
4
|
+
* `short_name` is silently mangled rather than rejected.
|
|
5
|
+
*/
|
|
6
|
+
const SHORT_NAME_MAX = 12;
|
|
7
|
+
export function buildManifest(input) {
|
|
8
|
+
const scope = input.scope ?? '/';
|
|
9
|
+
const shortName = input.shortName ?? input.name.slice(0, SHORT_NAME_MAX);
|
|
10
|
+
const manifest = {
|
|
11
|
+
// `id` defaults to `start_url` in browsers, which means changing the start
|
|
12
|
+
// URL orphans the installed app. Pinning it to the scope makes the identity
|
|
13
|
+
// survive a routing change.
|
|
14
|
+
id: scope,
|
|
15
|
+
name: input.name,
|
|
16
|
+
short_name: shortName,
|
|
17
|
+
start_url: input.startUrl ?? scope,
|
|
18
|
+
scope,
|
|
19
|
+
display: input.display ?? 'standalone',
|
|
20
|
+
background_color: input.backgroundColor,
|
|
21
|
+
theme_color: input.themeColor,
|
|
22
|
+
lang: input.lang,
|
|
23
|
+
dir: input.dir ?? 'ltr',
|
|
24
|
+
icons: input.icons,
|
|
25
|
+
};
|
|
26
|
+
return input.description === undefined
|
|
27
|
+
? manifest
|
|
28
|
+
: { ...manifest, description: input.description };
|
|
29
|
+
}
|
|
30
|
+
function hasSquareIcon(icons, edge) {
|
|
31
|
+
return icons.some((icon) => icon.sizes.split(/\s+/).some((size) => size.toLowerCase() === `${edge}x${edge}`));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Reports everything that would stop a browser offering installation, in one
|
|
35
|
+
* pass. Reporting one problem per run is the loop that makes an author give up
|
|
36
|
+
* on the third iteration.
|
|
37
|
+
*/
|
|
38
|
+
export function checkInstallability(manifest) {
|
|
39
|
+
const problems = [];
|
|
40
|
+
if (manifest.name.trim() === '') {
|
|
41
|
+
problems.push({
|
|
42
|
+
field: 'name',
|
|
43
|
+
problem: 'the manifest has no name',
|
|
44
|
+
fix: 'Set the site name: it is the label of the install prompt.',
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
if (manifest.short_name.length > SHORT_NAME_MAX) {
|
|
48
|
+
problems.push({
|
|
49
|
+
field: 'short_name',
|
|
50
|
+
problem: `short_name is ${manifest.short_name.length} characters`,
|
|
51
|
+
fix: `Keep it to ${SHORT_NAME_MAX} characters or fewer, or the home screen truncates it.`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (manifest.display === 'browser') {
|
|
55
|
+
problems.push({
|
|
56
|
+
field: 'display',
|
|
57
|
+
problem: 'display is "browser", which no browser treats as installable',
|
|
58
|
+
fix: 'Use "standalone", "minimal-ui" or "fullscreen".',
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (!manifest.start_url.startsWith(manifest.scope)) {
|
|
62
|
+
problems.push({
|
|
63
|
+
field: 'start_url',
|
|
64
|
+
problem: `start_url "${manifest.start_url}" is outside scope "${manifest.scope}"`,
|
|
65
|
+
fix: 'The start URL must be inside the scope, or the installed app opens outside itself.',
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
if (!hasSquareIcon(manifest.icons, 192)) {
|
|
69
|
+
problems.push({
|
|
70
|
+
field: 'icons',
|
|
71
|
+
problem: 'no 192x192 icon',
|
|
72
|
+
fix: 'Add a 192x192 PNG: it is the minimum Android requires for the install prompt.',
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (!hasSquareIcon(manifest.icons, 512)) {
|
|
76
|
+
problems.push({
|
|
77
|
+
field: 'icons',
|
|
78
|
+
problem: 'no 512x512 icon',
|
|
79
|
+
fix: 'Add a 512x512 PNG: it is used for the splash screen.',
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
if (!manifest.icons.some((icon) => icon.purpose?.includes('maskable') === true)) {
|
|
83
|
+
problems.push({
|
|
84
|
+
field: 'icons',
|
|
85
|
+
problem: 'no maskable icon',
|
|
86
|
+
fix: 'Add an icon with purpose "maskable" and 20% safe padding, or Android draws your square icon inside a white circle.',
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
return problems;
|
|
90
|
+
}
|
|
91
|
+
/** Throws a message an author can act on, listing every problem at once. */
|
|
92
|
+
export function assertInstallable(manifest) {
|
|
93
|
+
const problems = checkInstallability(manifest);
|
|
94
|
+
if (problems.length === 0)
|
|
95
|
+
return;
|
|
96
|
+
throw new CogentaError({
|
|
97
|
+
code: 'CONFIG_INVALID',
|
|
98
|
+
message: `The web app manifest is not installable (${problems.length} problem${problems.length === 1 ? '' : 's'}):\n${problems.map((p) => ` ${p.field}: ${p.problem} — ${p.fix}`).join('\n')}`,
|
|
99
|
+
hint: 'A browser offers installation only when the manifest is complete, the display mode is not "browser", and a service worker answers navigations offline.',
|
|
100
|
+
details: { problems },
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** Serialised manifest, ready to write to `manifest.webmanifest`. */
|
|
104
|
+
export function renderManifest(manifest) {
|
|
105
|
+
return `${JSON.stringify(manifest, null, 2)}\n`;
|
|
106
|
+
}
|
|
107
|
+
export const MANIFEST_CONTENT_TYPE = 'application/manifest+json';
|
|
108
|
+
//# sourceMappingURL=manifest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"manifest.js","sourceRoot":"","sources":["../../src/pwa/manifest.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AA0D5C;;;GAGG;AACH,MAAM,cAAc,GAAG,EAAE,CAAA;AAEzB,MAAM,UAAU,aAAa,CAAC,KAAoB;IAChD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,IAAI,GAAG,CAAA;IAChC,MAAM,SAAS,GAAG,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,cAAc,CAAC,CAAA;IACxE,MAAM,QAAQ,GAAmB;QAC/B,2EAA2E;QAC3E,4EAA4E;QAC5E,4BAA4B;QAC5B,EAAE,EAAE,KAAK;QACT,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,UAAU,EAAE,SAAS;QACrB,SAAS,EAAE,KAAK,CAAC,QAAQ,IAAI,KAAK;QAClC,KAAK;QACL,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,YAAY;QACtC,gBAAgB,EAAE,KAAK,CAAC,eAAe;QACvC,WAAW,EAAE,KAAK,CAAC,UAAU;QAC7B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,GAAG,EAAE,KAAK,CAAC,GAAG,IAAI,KAAK;QACvB,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAA;IACD,OAAO,KAAK,CAAC,WAAW,KAAK,SAAS;QACpC,CAAC,CAAC,QAAQ;QACV,CAAC,CAAC,EAAE,GAAG,QAAQ,EAAE,WAAW,EAAE,KAAK,CAAC,WAAW,EAAE,CAAA;AACrD,CAAC;AAQD,SAAS,aAAa,CAAC,KAA8B,EAAE,IAAY;IACjE,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CACzB,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,GAAG,IAAI,IAAI,IAAI,EAAE,CAAC,CACjF,CAAA;AACH,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,QAAwB;IAC1D,MAAM,QAAQ,GAA4B,EAAE,CAAA;IAE5C,IAAI,QAAQ,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QAChC,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,MAAM;YACb,OAAO,EAAE,0BAA0B;YACnC,GAAG,EAAE,2DAA2D;SACjE,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,UAAU,CAAC,MAAM,GAAG,cAAc,EAAE,CAAC;QAChD,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,YAAY;YACnB,OAAO,EAAE,iBAAiB,QAAQ,CAAC,UAAU,CAAC,MAAM,aAAa;YACjE,GAAG,EAAE,cAAc,cAAc,wDAAwD;SAC1F,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,QAAQ,CAAC,OAAO,KAAK,SAAS,EAAE,CAAC;QACnC,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,SAAS;YAChB,OAAO,EAAE,8DAA8D;YACvE,GAAG,EAAE,iDAAiD;SACvD,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,UAAU,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACnD,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,WAAW;YAClB,OAAO,EAAE,cAAc,QAAQ,CAAC,SAAS,uBAAuB,QAAQ,CAAC,KAAK,GAAG;YACjF,GAAG,EAAE,oFAAoF;SAC1F,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,iBAAiB;YAC1B,GAAG,EAAE,+EAA+E;SACrF,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,aAAa,CAAC,QAAQ,CAAC,KAAK,EAAE,GAAG,CAAC,EAAE,CAAC;QACxC,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,iBAAiB;YAC1B,GAAG,EAAE,sDAAsD;SAC5D,CAAC,CAAA;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,QAAQ,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QAChF,QAAQ,CAAC,IAAI,CAAC;YACZ,KAAK,EAAE,OAAO;YACd,OAAO,EAAE,kBAAkB;YAC3B,GAAG,EAAE,oHAAoH;SAC1H,CAAC,CAAA;IACJ,CAAC;IAED,OAAO,QAAQ,CAAA;AACjB,CAAC;AAED,4EAA4E;AAC5E,MAAM,UAAU,iBAAiB,CAAC,QAAwB;IACxD,MAAM,QAAQ,GAAG,mBAAmB,CAAC,QAAQ,CAAC,CAAA;IAC9C,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAM;IACjC,MAAM,IAAI,YAAY,CAAC;QACrB,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,4CAA4C,QAAQ,CAAC,MAAM,WAClE,QAAQ,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAC/B,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,OAAO,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;QAChF,IAAI,EAAE,wJAAwJ;QAC9J,OAAO,EAAE,EAAE,QAAQ,EAAE;KACtB,CAAC,CAAA;AACJ,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,cAAc,CAAC,QAAwB;IACrD,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAA;AACjD,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,2BAA2B,CAAA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The offline page.
|
|
3
|
+
*
|
|
4
|
+
* It has one job and one honesty requirement: say that the *network* is
|
|
5
|
+
* missing, not that the site is broken, and never render an empty shell. A
|
|
6
|
+
* blank page is indistinguishable from a crashed site, so a visitor reloads,
|
|
7
|
+
* blames the site, and leaves.
|
|
8
|
+
*
|
|
9
|
+
* It is fully self-contained — inline styles, no font, no image, no external
|
|
10
|
+
* stylesheet — because every external reference is one more thing that must
|
|
11
|
+
* have been cached for the page to render at the exact moment nothing can be
|
|
12
|
+
* fetched. It styles itself from the skin custom properties when they happen to
|
|
13
|
+
* be defined, and falls back to literal values when they are not.
|
|
14
|
+
*/
|
|
15
|
+
export interface OfflinePageOptions {
|
|
16
|
+
readonly siteName: string;
|
|
17
|
+
/** BCP 47 tag. Drives the `lang` attribute, so screen readers pronounce it. */
|
|
18
|
+
readonly lang: string;
|
|
19
|
+
readonly dir?: 'ltr' | 'rtl' | 'auto';
|
|
20
|
+
/** Heading. Must state that the visitor is offline. */
|
|
21
|
+
readonly title?: string;
|
|
22
|
+
readonly message?: string;
|
|
23
|
+
readonly retryLabel?: string;
|
|
24
|
+
/** Shown once the browser reports the connection is back. */
|
|
25
|
+
readonly restoredLabel?: string;
|
|
26
|
+
/**
|
|
27
|
+
* Whether to include the retry script. Disable it on a site whose CSP has no
|
|
28
|
+
* hash or nonce for inline scripts; the page still renders and still tells
|
|
29
|
+
* the truth, it just cannot retry by itself.
|
|
30
|
+
*/
|
|
31
|
+
readonly interactive?: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* The inline script, exported so a strict CSP can hash it
|
|
35
|
+
* (`script-src 'sha256-…'`) instead of loosening to `unsafe-inline`.
|
|
36
|
+
*
|
|
37
|
+
* It does two things and nothing else: reload on demand, and notice when the
|
|
38
|
+
* connection returns. `navigator.onLine` is famously optimistic — it reports a
|
|
39
|
+
* live network adapter, not a reachable site — so it is used only to *offer* a
|
|
40
|
+
* reload, never to claim the site is back.
|
|
41
|
+
*/
|
|
42
|
+
export declare const OFFLINE_PAGE_SCRIPT: string;
|
|
43
|
+
export declare function renderOfflinePage(options: OfflinePageOptions): string;
|
|
44
|
+
//# sourceMappingURL=offline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline.d.ts","sourceRoot":"","sources":["../../src/pwa/offline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAcH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,+EAA+E;IAC/E,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,GAAG,CAAC,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAA;IACrC,uDAAuD;IACvD,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;IAC5B,6DAA6D;IAC7D,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAA;IAC/B;;;;OAIG;IACH,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAC/B;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,mBAAmB,QAOpB,CAAA;AAkBZ,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAuCrE"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The offline page.
|
|
3
|
+
*
|
|
4
|
+
* It has one job and one honesty requirement: say that the *network* is
|
|
5
|
+
* missing, not that the site is broken, and never render an empty shell. A
|
|
6
|
+
* blank page is indistinguishable from a crashed site, so a visitor reloads,
|
|
7
|
+
* blames the site, and leaves.
|
|
8
|
+
*
|
|
9
|
+
* It is fully self-contained — inline styles, no font, no image, no external
|
|
10
|
+
* stylesheet — because every external reference is one more thing that must
|
|
11
|
+
* have been cached for the page to render at the exact moment nothing can be
|
|
12
|
+
* fetched. It styles itself from the skin custom properties when they happen to
|
|
13
|
+
* be defined, and falls back to literal values when they are not.
|
|
14
|
+
*/
|
|
15
|
+
const ESCAPES = {
|
|
16
|
+
'&': '&',
|
|
17
|
+
'<': '<',
|
|
18
|
+
'>': '>',
|
|
19
|
+
'"': '"',
|
|
20
|
+
"'": ''',
|
|
21
|
+
};
|
|
22
|
+
function escapeHtml(value) {
|
|
23
|
+
return value.replace(/[&<>"']/g, (char) => ESCAPES[char] ?? char);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* The inline script, exported so a strict CSP can hash it
|
|
27
|
+
* (`script-src 'sha256-…'`) instead of loosening to `unsafe-inline`.
|
|
28
|
+
*
|
|
29
|
+
* It does two things and nothing else: reload on demand, and notice when the
|
|
30
|
+
* connection returns. `navigator.onLine` is famously optimistic — it reports a
|
|
31
|
+
* live network adapter, not a reachable site — so it is used only to *offer* a
|
|
32
|
+
* reload, never to claim the site is back.
|
|
33
|
+
*/
|
|
34
|
+
export const OFFLINE_PAGE_SCRIPT = [
|
|
35
|
+
'(function () {',
|
|
36
|
+
' var button = document.getElementById("cogenta-offline-retry")',
|
|
37
|
+
' var status = document.getElementById("cogenta-offline-status")',
|
|
38
|
+
' if (button) button.addEventListener("click", function () { location.reload() })',
|
|
39
|
+
' addEventListener("online", function () { if (status) status.hidden = false })',
|
|
40
|
+
'})()',
|
|
41
|
+
].join('\n');
|
|
42
|
+
const STYLE = [
|
|
43
|
+
':root{color-scheme:light dark}',
|
|
44
|
+
'body{margin:0;min-height:100vh;display:grid;place-items:center;',
|
|
45
|
+
'padding:var(--cogenta-space-unit,1rem);',
|
|
46
|
+
'background:var(--cogenta-color-bg,#fff);color:var(--cogenta-color-fg,#111);',
|
|
47
|
+
'font-family:var(--cogenta-font-sans,system-ui,sans-serif);line-height:1.5}',
|
|
48
|
+
'main{max-width:34rem;text-align:center}',
|
|
49
|
+
'h1{font-size:var(--cogenta-font-size-xl,1.75rem);margin:0 0 .5rem}',
|
|
50
|
+
'p{margin:0 0 1.5rem;color:var(--cogenta-color-muted-fg,#555)}',
|
|
51
|
+
'button{font:inherit;cursor:pointer;padding:.6rem 1.2rem;',
|
|
52
|
+
'border:1px solid var(--cogenta-color-border,#ccc);',
|
|
53
|
+
'border-radius:var(--cogenta-radius-md,.375rem);',
|
|
54
|
+
'background:var(--cogenta-color-accent,#111);color:var(--cogenta-color-accent-fg,#fff)}',
|
|
55
|
+
'[hidden]{display:none}',
|
|
56
|
+
].join('');
|
|
57
|
+
export function renderOfflinePage(options) {
|
|
58
|
+
const lang = escapeHtml(options.lang);
|
|
59
|
+
const dir = options.dir ?? 'ltr';
|
|
60
|
+
const siteName = escapeHtml(options.siteName);
|
|
61
|
+
const title = escapeHtml(options.title ?? 'You are offline');
|
|
62
|
+
const message = escapeHtml(options.message ??
|
|
63
|
+
'This page has not been downloaded yet, and there is no connection to fetch it. Everything you have already visited is still available.');
|
|
64
|
+
const retryLabel = escapeHtml(options.retryLabel ?? 'Try again');
|
|
65
|
+
const restoredLabel = escapeHtml(options.restoredLabel ?? 'The connection is back.');
|
|
66
|
+
const interactive = options.interactive ?? true;
|
|
67
|
+
return [
|
|
68
|
+
'<!doctype html>',
|
|
69
|
+
`<html lang="${lang}" dir="${dir}">`,
|
|
70
|
+
'<head>',
|
|
71
|
+
'<meta charset="utf-8">',
|
|
72
|
+
'<meta name="viewport" content="width=device-width, initial-scale=1">',
|
|
73
|
+
// Not indexable, and never a canonical answer for a real URL.
|
|
74
|
+
'<meta name="robots" content="noindex">',
|
|
75
|
+
`<title>${title} · ${siteName}</title>`,
|
|
76
|
+
`<style>${STYLE}</style>`,
|
|
77
|
+
'</head>',
|
|
78
|
+
'<body>',
|
|
79
|
+
'<main>',
|
|
80
|
+
`<h1>${title}</h1>`,
|
|
81
|
+
`<p>${message}</p>`,
|
|
82
|
+
interactive
|
|
83
|
+
? `<p id="cogenta-offline-status" hidden>${restoredLabel}</p><button id="cogenta-offline-retry" type="button">${retryLabel}</button>`
|
|
84
|
+
: '',
|
|
85
|
+
'</main>',
|
|
86
|
+
interactive ? `<script>${OFFLINE_PAGE_SCRIPT}</script>` : '',
|
|
87
|
+
'</body>',
|
|
88
|
+
'</html>',
|
|
89
|
+
'',
|
|
90
|
+
]
|
|
91
|
+
.filter((line) => line !== '')
|
|
92
|
+
.join('\n');
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=offline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"offline.js","sourceRoot":"","sources":["../../src/pwa/offline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,MAAM,OAAO,GAAqC;IAChD,GAAG,EAAE,OAAO;IACZ,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,MAAM;IACX,GAAG,EAAE,QAAQ;IACb,GAAG,EAAE,OAAO;CACb,CAAA;AAED,SAAS,UAAU,CAAC,KAAa;IAC/B,OAAO,KAAK,CAAC,OAAO,CAAC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC,CAAA;AACnE,CAAC;AAqBD;;;;;;;;GAQG;AACH,MAAM,CAAC,MAAM,mBAAmB,GAAG;IACjC,gBAAgB;IAChB,iEAAiE;IACjE,kEAAkE;IAClE,mFAAmF;IACnF,iFAAiF;IACjF,MAAM;CACP,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;AAEZ,MAAM,KAAK,GAAG;IACZ,gCAAgC;IAChC,iEAAiE;IACjE,yCAAyC;IACzC,6EAA6E;IAC7E,4EAA4E;IAC5E,yCAAyC;IACzC,oEAAoE;IACpE,+DAA+D;IAC/D,0DAA0D;IAC1D,oDAAoD;IACpD,iDAAiD;IACjD,wFAAwF;IACxF,wBAAwB;CACzB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;AAEV,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IAC3D,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,KAAK,CAAA;IAChC,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC7C,MAAM,KAAK,GAAG,UAAU,CAAC,OAAO,CAAC,KAAK,IAAI,iBAAiB,CAAC,CAAA;IAC5D,MAAM,OAAO,GAAG,UAAU,CACxB,OAAO,CAAC,OAAO;QACb,wIAAwI,CAC3I,CAAA;IACD,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,CAAC,UAAU,IAAI,WAAW,CAAC,CAAA;IAChE,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,CAAC,aAAa,IAAI,yBAAyB,CAAC,CAAA;IACpF,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAA;IAE/C,OAAO;QACL,iBAAiB;QACjB,eAAe,IAAI,UAAU,GAAG,IAAI;QACpC,QAAQ;QACR,wBAAwB;QACxB,sEAAsE;QACtE,8DAA8D;QAC9D,wCAAwC;QACxC,UAAU,KAAK,MAAM,QAAQ,UAAU;QACvC,UAAU,KAAK,UAAU;QACzB,SAAS;QACT,QAAQ;QACR,QAAQ;QACR,OAAO,KAAK,OAAO;QACnB,MAAM,OAAO,MAAM;QACnB,WAAW;YACT,CAAC,CAAC,yCAAyC,aAAa,wDAAwD,UAAU,WAAW;YACrI,CAAC,CAAC,EAAE;QACN,SAAS;QACT,WAAW,CAAC,CAAC,CAAC,WAAW,mBAAmB,WAAW,CAAC,CAAC,CAAC,EAAE;QAC5D,SAAS;QACT,SAAS;QACT,EAAE;KACH;SACE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,KAAK,EAAE,CAAC;SAC7B,IAAI,CAAC,IAAI,CAAC,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { PwaConfig } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generation of the service worker script.
|
|
4
|
+
*
|
|
5
|
+
* ## The update strategy, stated in full
|
|
6
|
+
*
|
|
7
|
+
* `skipWaiting()` and `clients.claim()` are two calls; the strategy is the four
|
|
8
|
+
* properties that make those two calls safe.
|
|
9
|
+
*
|
|
10
|
+
* **1. Every generation owns its caches.** Cache names embed a version derived
|
|
11
|
+
* from the offline page, the precache list and the route table
|
|
12
|
+
* (`computeCacheVersion`). A new deploy therefore writes into new caches and
|
|
13
|
+
* cannot mix generations, which is what makes immediate activation safe: there
|
|
14
|
+
* is no window in which half the entries are old.
|
|
15
|
+
*
|
|
16
|
+
* **2. Activation purges the previous generations.** `cachesToPurge` returns
|
|
17
|
+
* every cache of ours whose version is not the current one, and activation
|
|
18
|
+
* deletes them before claiming clients. Storage does not accumulate one
|
|
19
|
+
* generation per deploy, and — the part that matters — yesterday's HTML is
|
|
20
|
+
* *gone*, not merely lower priority.
|
|
21
|
+
*
|
|
22
|
+
* **3. Documents are never served from cache while the network works.** Even a
|
|
23
|
+
* worker that somehow survived a bad deploy cannot pin an old page: the
|
|
24
|
+
* document strategy is network-first, so the cache answers only when the
|
|
25
|
+
* network has already failed. This is the property that keeps a mistake
|
|
26
|
+
* recoverable by deploying a fix, rather than requiring every visitor to clear
|
|
27
|
+
* their site data.
|
|
28
|
+
*
|
|
29
|
+
* **4. There is an exit.** The worker answers a message by purging every cache
|
|
30
|
+
* it owns and calling `registration.unregister()` — that is the admin's "clear
|
|
31
|
+
* client cache" button. For clients we can no longer run code on,
|
|
32
|
+
* `renderTombstoneServiceWorker` produces a worker that unregisters itself on
|
|
33
|
+
* activation; deploying it at the same URL removes the PWA from every browser
|
|
34
|
+
* that re-fetches the script, which browsers do at least daily.
|
|
35
|
+
*
|
|
36
|
+
* ## What is deliberately *not* done
|
|
37
|
+
*
|
|
38
|
+
* The worker does not reload open pages when it activates. A forced reload
|
|
39
|
+
* destroys scroll position, half-typed comments and video playback, and it
|
|
40
|
+
* turns one bad deploy into a reload loop. It posts `cogenta:activated`
|
|
41
|
+
* instead, and the page decides — the registration helper in `client.ts` turns
|
|
42
|
+
* that into an "a new version is available" prompt.
|
|
43
|
+
*
|
|
44
|
+
* The worker is a *classic* script, not an ES module: module workers are still
|
|
45
|
+
* unevenly supported, and there is nothing to import — the script is
|
|
46
|
+
* self-contained by construction.
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* Headers the server must send for the service worker script.
|
|
50
|
+
*
|
|
51
|
+
* This is not decoration. A worker script cached by the HTTP cache for a year
|
|
52
|
+
* is the canonical unkillable zombie: the browser's daily update check reads
|
|
53
|
+
* the stale copy from disk, sees no change, and keeps the old worker alive —
|
|
54
|
+
* including after the tombstone has been deployed. `no-cache` (revalidate, do
|
|
55
|
+
* not blindly reuse) is the minimum, and the scope header lets the script live
|
|
56
|
+
* anywhere while controlling the whole origin.
|
|
57
|
+
*/
|
|
58
|
+
export declare const SERVICE_WORKER_HEADERS: Readonly<Record<string, string>>;
|
|
59
|
+
export declare function renderServiceWorker(config: PwaConfig): string;
|
|
60
|
+
/**
|
|
61
|
+
* The kill switch, as a deployable artefact.
|
|
62
|
+
*
|
|
63
|
+
* Deploy this at the URL the real worker was served from. It caches nothing,
|
|
64
|
+
* intercepts nothing, deletes every cache under `cachePrefix` and unregisters
|
|
65
|
+
* itself. Keep it deployed for at least one update cycle — twenty-four hours
|
|
66
|
+
* covers browsers that only check the script once a day — before removing the
|
|
67
|
+
* route entirely, because a 404 on the worker URL also unregisters the worker
|
|
68
|
+
* but tells the caches nothing.
|
|
69
|
+
*/
|
|
70
|
+
export declare function renderTombstoneServiceWorker(cachePrefix: string): string;
|
|
71
|
+
//# sourceMappingURL=service-worker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service-worker.d.ts","sourceRoot":"","sources":["../../src/pwa/service-worker.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAG3C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AAEH;;;;;;;;;GASG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAInE,CAAA;AA0BD,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,SAAS,GAAG,MAAM,CAiB7D;AAED;;;;;;;;;GASG;AACH,wBAAgB,4BAA4B,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CASxE"}
|