@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,214 @@
|
|
|
1
|
+
import { chooseStrategy } from './strategy.js';
|
|
2
|
+
import { cacheNameFor, cachesToPurge } from './version.js';
|
|
3
|
+
/** Messages the page may send the worker. Part of the public PWA contract. */
|
|
4
|
+
export const SW_MESSAGE_PURGE = 'cogenta:purge';
|
|
5
|
+
export const SW_MESSAGE_UNREGISTER = 'cogenta:unregister';
|
|
6
|
+
export const SW_MESSAGE_ACTIVATED = 'cogenta:activated';
|
|
7
|
+
export const SW_MESSAGE_UNREGISTERED = 'cogenta:unregistered';
|
|
8
|
+
export function serviceWorkerMain(scope, config) {
|
|
9
|
+
const shellCacheName = cacheNameFor(config.cachePrefix, config.version, 'shell');
|
|
10
|
+
// Declared inside the function, like everything else here, because the
|
|
11
|
+
// function body is inlined into the generated script and module-scope
|
|
12
|
+
// constants would not travel with it. Used only when the network is down
|
|
13
|
+
// *and* the precached offline page is missing: it says what happened instead
|
|
14
|
+
// of rendering nothing, since a blank tab reads as "this site is broken",
|
|
15
|
+
// which is a different and unactionable claim from "you are offline".
|
|
16
|
+
const lastResortOfflineHtml = '<!doctype html><html lang="en"><meta charset="utf-8">' +
|
|
17
|
+
'<meta name="viewport" content="width=device-width,initial-scale=1">' +
|
|
18
|
+
'<title>Offline</title><body style="font:1rem/1.5 system-ui;margin:2rem">' +
|
|
19
|
+
'<h1>You are offline</h1><p>This page is not available without a network ' +
|
|
20
|
+
'connection. It will load again once you are back online.</p>';
|
|
21
|
+
const tellClients = async (message) => {
|
|
22
|
+
const windows = await scope.clients.matchAll({ type: 'window' });
|
|
23
|
+
for (const client of windows)
|
|
24
|
+
client.postMessage(message);
|
|
25
|
+
};
|
|
26
|
+
const purge = async (everything) => {
|
|
27
|
+
const names = await scope.caches.keys();
|
|
28
|
+
const doomed = everything
|
|
29
|
+
? names.filter((name) => name.startsWith(`${config.cachePrefix}:`))
|
|
30
|
+
: cachesToPurge(names, config.cachePrefix, config.version);
|
|
31
|
+
await Promise.all(doomed.map((name) => scope.caches.delete(name)));
|
|
32
|
+
return doomed;
|
|
33
|
+
};
|
|
34
|
+
const describe = (request) => ({
|
|
35
|
+
method: request.method,
|
|
36
|
+
url: request.url,
|
|
37
|
+
origin: scope.location.origin,
|
|
38
|
+
destination: request.destination,
|
|
39
|
+
mode: request.mode,
|
|
40
|
+
cacheMode: request.cache,
|
|
41
|
+
hasRange: request.headers.has('range'),
|
|
42
|
+
});
|
|
43
|
+
const store = async (cache, request, response, maxEntries) => {
|
|
44
|
+
// `ok` is false for opaque cross-origin responses (status 0) and for every
|
|
45
|
+
// error status, so neither can be stored. Caching a 404 is how a site keeps
|
|
46
|
+
// showing "not found" long after the page came back.
|
|
47
|
+
if (!response.ok)
|
|
48
|
+
return;
|
|
49
|
+
await cache.put(request, response.clone());
|
|
50
|
+
if (maxEntries === null)
|
|
51
|
+
return;
|
|
52
|
+
// `keys()` is in insertion order, so the head of the list is the least
|
|
53
|
+
// recently *added*. A true LRU would need a side index in IndexedDB; the
|
|
54
|
+
// cap exists to bound disk use, not to be optimal.
|
|
55
|
+
const keys = await cache.keys();
|
|
56
|
+
const excess = keys.length - maxEntries;
|
|
57
|
+
for (let i = 0; i < excess; i++) {
|
|
58
|
+
const key = keys[i];
|
|
59
|
+
if (key !== undefined)
|
|
60
|
+
await cache.delete(key);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const offlineFallback = async () => {
|
|
64
|
+
const shell = await scope.caches.open(shellCacheName);
|
|
65
|
+
const page = await shell.match(config.offlineUrl);
|
|
66
|
+
if (page !== undefined)
|
|
67
|
+
return page;
|
|
68
|
+
return new scope.Response(lastResortOfflineHtml, {
|
|
69
|
+
status: 503,
|
|
70
|
+
headers: { 'content-type': 'text/html; charset=utf-8' },
|
|
71
|
+
});
|
|
72
|
+
};
|
|
73
|
+
/**
|
|
74
|
+
* Races the network against a timer rather than aborting it. The losing
|
|
75
|
+
* request is deliberately left running: it still fills the cache, so the
|
|
76
|
+
* *next* view gets fresh content even though this one was served stale.
|
|
77
|
+
*/
|
|
78
|
+
const withTimeout = async (network, ms) => {
|
|
79
|
+
if (ms === null)
|
|
80
|
+
return network;
|
|
81
|
+
return Promise.race([
|
|
82
|
+
network,
|
|
83
|
+
new Promise((_resolve, reject) => {
|
|
84
|
+
scope.setTimeout(() => reject(new Error('network timeout')), ms);
|
|
85
|
+
}),
|
|
86
|
+
]);
|
|
87
|
+
};
|
|
88
|
+
const handle = async (event, request, decision) => {
|
|
89
|
+
const cache = await scope.caches.open(cacheNameFor(config.cachePrefix, config.version, decision.bucket));
|
|
90
|
+
if (decision.strategy === 'cache-first') {
|
|
91
|
+
const hit = await cache.match(request);
|
|
92
|
+
if (hit !== undefined)
|
|
93
|
+
return hit;
|
|
94
|
+
const response = await scope.fetch(request);
|
|
95
|
+
await store(cache, request, response, decision.maxEntries);
|
|
96
|
+
return response;
|
|
97
|
+
}
|
|
98
|
+
if (decision.strategy === 'stale-while-revalidate') {
|
|
99
|
+
const hit = await cache.match(request);
|
|
100
|
+
const refresh = scope
|
|
101
|
+
.fetch(request)
|
|
102
|
+
.then(async (response) => {
|
|
103
|
+
await store(cache, request, response, decision.maxEntries);
|
|
104
|
+
return response;
|
|
105
|
+
})
|
|
106
|
+
.catch(() => undefined);
|
|
107
|
+
if (hit !== undefined) {
|
|
108
|
+
// The page is already answered; keep the worker alive until the
|
|
109
|
+
// background refresh lands, or the next view is stale again.
|
|
110
|
+
event.waitUntil(refresh);
|
|
111
|
+
return hit;
|
|
112
|
+
}
|
|
113
|
+
const fresh = await refresh;
|
|
114
|
+
if (fresh !== undefined)
|
|
115
|
+
return fresh;
|
|
116
|
+
return offlineFallback();
|
|
117
|
+
}
|
|
118
|
+
// network-first
|
|
119
|
+
try {
|
|
120
|
+
const response = await withTimeout(scope.fetch(request).then(async (fetched) => {
|
|
121
|
+
await store(cache, request, fetched, decision.maxEntries);
|
|
122
|
+
return fetched;
|
|
123
|
+
}), decision.networkTimeoutMs);
|
|
124
|
+
return response;
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
const hit = await cache.match(request);
|
|
128
|
+
if (hit !== undefined)
|
|
129
|
+
return hit;
|
|
130
|
+
return offlineFallback();
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
scope.addEventListener('install', (event) => {
|
|
134
|
+
event.waitUntil((async () => {
|
|
135
|
+
// Precache into this generation's own cache. A half-written precache
|
|
136
|
+
// therefore never mixes with the previous generation's entries.
|
|
137
|
+
const cache = await scope.caches.open(shellCacheName);
|
|
138
|
+
await cache.addAll(config.precache);
|
|
139
|
+
// Activate as soon as the new generation is complete. See the update
|
|
140
|
+
// strategy documented in `renderServiceWorker`.
|
|
141
|
+
await scope.skipWaiting();
|
|
142
|
+
})());
|
|
143
|
+
});
|
|
144
|
+
scope.addEventListener('activate', (event) => {
|
|
145
|
+
event.waitUntil((async () => {
|
|
146
|
+
await purge(false);
|
|
147
|
+
await scope.clients.claim();
|
|
148
|
+
// Tell the pages, do not reload them. A forced reload loses scroll
|
|
149
|
+
// position, form input and video playback; the page decides what to do
|
|
150
|
+
// with the news.
|
|
151
|
+
await tellClients({ type: 'cogenta:activated', version: config.version });
|
|
152
|
+
})());
|
|
153
|
+
});
|
|
154
|
+
scope.addEventListener('fetch', (event) => {
|
|
155
|
+
const decision = chooseStrategy(describe(event.request), config.routes);
|
|
156
|
+
// Not calling respondWith at all is not the same as fetching and returning:
|
|
157
|
+
// it hands the request back to the browser untouched, with its own HTTP
|
|
158
|
+
// cache, its own range handling and no worker in the path.
|
|
159
|
+
if (decision.strategy === 'network-only')
|
|
160
|
+
return;
|
|
161
|
+
event.respondWith(handle(event, event.request, decision));
|
|
162
|
+
});
|
|
163
|
+
scope.addEventListener('message', (event) => {
|
|
164
|
+
const data = event.data;
|
|
165
|
+
if (typeof data !== 'object' || data === null)
|
|
166
|
+
return;
|
|
167
|
+
const type = data.type;
|
|
168
|
+
// The exit path, callable from the admin. It must live in the worker
|
|
169
|
+
// itself: once a worker controls a page, only the worker can empty the
|
|
170
|
+
// caches it owns without a full "clear site data".
|
|
171
|
+
if (type === 'cogenta:unregister') {
|
|
172
|
+
event.waitUntil((async () => {
|
|
173
|
+
await purge(true);
|
|
174
|
+
await scope.registration.unregister();
|
|
175
|
+
await tellClients({ type: 'cogenta:unregistered' });
|
|
176
|
+
})());
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
if (type === 'cogenta:purge') {
|
|
180
|
+
event.waitUntil((async () => {
|
|
181
|
+
const deleted = await purge(true);
|
|
182
|
+
await tellClients({ type: 'cogenta:purged', caches: deleted });
|
|
183
|
+
})());
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* The tombstone worker.
|
|
189
|
+
*
|
|
190
|
+
* Deployed at the *same URL* as the real worker when the PWA has to be turned
|
|
191
|
+
* off. It registers no fetch handler, empties every cache the site owns and
|
|
192
|
+
* unregisters itself. Browsers re-fetch a service worker script at least every
|
|
193
|
+
* 24 hours, so shipping this file is the only mechanism that reaches clients we
|
|
194
|
+
* can no longer run code on — which is precisely the situation the "zombie
|
|
195
|
+
* worker" incident describes.
|
|
196
|
+
*/
|
|
197
|
+
export function tombstoneMain(scope, cachePrefix) {
|
|
198
|
+
scope.addEventListener('install', (event) => {
|
|
199
|
+
event.waitUntil(scope.skipWaiting());
|
|
200
|
+
});
|
|
201
|
+
scope.addEventListener('activate', (event) => {
|
|
202
|
+
event.waitUntil((async () => {
|
|
203
|
+
const names = await scope.caches.keys();
|
|
204
|
+
await Promise.all(names
|
|
205
|
+
.filter((name) => name.startsWith(`${cachePrefix}:`))
|
|
206
|
+
.map((name) => scope.caches.delete(name)));
|
|
207
|
+
await scope.registration.unregister();
|
|
208
|
+
const windows = await scope.clients.matchAll({ type: 'window' });
|
|
209
|
+
for (const client of windows)
|
|
210
|
+
client.postMessage({ type: 'cogenta:unregistered' });
|
|
211
|
+
})());
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
//# sourceMappingURL=sw-runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sw-runtime.js","sourceRoot":"","sources":["../../src/pwa/sw-runtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary of the PWA layer.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is *data*, and that is the whole design. A route rule is a
|
|
5
|
+
* JSON-serialisable record, not a closure, because the same rule table has to
|
|
6
|
+
* be readable by three different readers: the unit tests in Node, the generated
|
|
7
|
+
* service worker running in a browser, and a human trying to work out why a
|
|
8
|
+
* page served stale content. A table of closures would only be readable by the
|
|
9
|
+
* first two, and only the first one would be testable.
|
|
10
|
+
*/
|
|
11
|
+
/** How a response is obtained, and whether the cache may answer instead. */
|
|
12
|
+
export type CacheStrategy =
|
|
13
|
+
/** Ask the network; fall back to the cache only when the network fails. */
|
|
14
|
+
'network-first'
|
|
15
|
+
/** Answer from the cache; only go to the network on a miss. */
|
|
16
|
+
| 'cache-first'
|
|
17
|
+
/** Answer from the cache and refresh it in the background. */
|
|
18
|
+
| 'stale-while-revalidate'
|
|
19
|
+
/** Never touch the cache. The service worker does not even intercept. */
|
|
20
|
+
| 'network-only';
|
|
21
|
+
/**
|
|
22
|
+
* Caches are split by bucket so that one kind of resource can be evicted or
|
|
23
|
+
* capped without touching another: images must be capped (they are unbounded),
|
|
24
|
+
* documents must not (they are the only thing that works offline).
|
|
25
|
+
*/
|
|
26
|
+
export type CacheBucket = 'shell' | 'documents' | 'assets' | 'images' | 'data';
|
|
27
|
+
export declare const CACHE_BUCKETS: readonly CacheBucket[];
|
|
28
|
+
/**
|
|
29
|
+
* One routing rule. Fields that may be absent are typed `| null` rather than
|
|
30
|
+
* optional: this record is serialised into the generated service worker, and
|
|
31
|
+
* `undefined` does not survive `JSON.stringify`. A missing field would then
|
|
32
|
+
* mean something different on each side of the wire.
|
|
33
|
+
*/
|
|
34
|
+
export interface RouteRule {
|
|
35
|
+
/** Stable identifier, reported in the decision so a miss can be traced. */
|
|
36
|
+
readonly id: string;
|
|
37
|
+
/**
|
|
38
|
+
* `Request.destination` values this rule answers for. Empty means "any
|
|
39
|
+
* destination". `''` is the destination of a plain `fetch()`.
|
|
40
|
+
*/
|
|
41
|
+
readonly destinations: readonly string[];
|
|
42
|
+
/** Regular expression source, matched against `pathname + search`. */
|
|
43
|
+
readonly pattern: string | null;
|
|
44
|
+
/** Whether the rule also applies to cross-origin requests. */
|
|
45
|
+
readonly crossOrigin: boolean;
|
|
46
|
+
readonly strategy: CacheStrategy;
|
|
47
|
+
readonly bucket: CacheBucket;
|
|
48
|
+
/** Cap on stored entries for this bucket. `null` means uncapped. */
|
|
49
|
+
readonly maxEntries: number | null;
|
|
50
|
+
/** How long the network gets before the cache answers. `null` means no cap. */
|
|
51
|
+
readonly networkTimeoutMs: number | null;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* A request reduced to the fields a routing decision actually needs.
|
|
55
|
+
*
|
|
56
|
+
* This is not `Request`. The render package compiles against ES2023 without the
|
|
57
|
+
* DOM library, and more importantly a decision function that takes a real
|
|
58
|
+
* `Request` can only be tested inside a browser. Reducing the request to seven
|
|
59
|
+
* scalars is what moves the routing decision into a unit test.
|
|
60
|
+
*/
|
|
61
|
+
export interface RequestDescriptor {
|
|
62
|
+
readonly method: string;
|
|
63
|
+
readonly url: string;
|
|
64
|
+
/** Origin of the service worker itself, used to detect cross-origin traffic. */
|
|
65
|
+
readonly origin: string;
|
|
66
|
+
/** `Request.destination`: `'document'`, `'script'`, `'image'`, `''`… */
|
|
67
|
+
readonly destination: string;
|
|
68
|
+
/** `Request.mode`: `'navigate'` marks a page load. */
|
|
69
|
+
readonly mode: string;
|
|
70
|
+
/** `Request.cache`: `'no-store'` and `'reload'` are the user overruling us. */
|
|
71
|
+
readonly cacheMode: string;
|
|
72
|
+
/** A ranged request asks for part of a body; a cache entry is a whole body. */
|
|
73
|
+
readonly hasRange: boolean;
|
|
74
|
+
}
|
|
75
|
+
export interface StrategyDecision {
|
|
76
|
+
readonly strategy: CacheStrategy;
|
|
77
|
+
readonly bucket: CacheBucket;
|
|
78
|
+
readonly maxEntries: number | null;
|
|
79
|
+
readonly networkTimeoutMs: number | null;
|
|
80
|
+
/** Rule that matched, or a `bypass:*` marker when nothing did. */
|
|
81
|
+
readonly ruleId: string;
|
|
82
|
+
/** Why this decision was reached. Present so a log line is diagnosable. */
|
|
83
|
+
readonly reason: string;
|
|
84
|
+
}
|
|
85
|
+
export interface PwaConfig {
|
|
86
|
+
/**
|
|
87
|
+
* Namespace for every cache this site owns. Other applications may share the
|
|
88
|
+
* origin; purging must never touch their caches.
|
|
89
|
+
*/
|
|
90
|
+
readonly cachePrefix: string;
|
|
91
|
+
/**
|
|
92
|
+
* Cache generation. Changing it makes the previous generation garbage, which
|
|
93
|
+
* is what the activation purge collects. Derive it with `computeCacheVersion`
|
|
94
|
+
* rather than bumping it by hand.
|
|
95
|
+
*/
|
|
96
|
+
readonly version: string;
|
|
97
|
+
/** URL of the offline page. Precached, and served when a navigation fails. */
|
|
98
|
+
readonly offlineUrl: string;
|
|
99
|
+
/**
|
|
100
|
+
* Everything precached at install. Keep it to the offline page and the shell:
|
|
101
|
+
* a large precache turns every deploy into a full re-download, which is the
|
|
102
|
+
* main reason PWAs feel heavier than the site they wrap.
|
|
103
|
+
*/
|
|
104
|
+
readonly precache: readonly string[];
|
|
105
|
+
readonly routes: readonly RouteRule[];
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/pwa/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,4EAA4E;AAC5E,MAAM,MAAM,aAAa;AACvB,2EAA2E;AACzE,eAAe;AACjB,+DAA+D;GAC7D,aAAa;AACf,8DAA8D;GAC5D,wBAAwB;AAC1B,yEAAyE;GACvE,cAAc,CAAA;AAElB;;;;GAIG;AACH,MAAM,MAAM,WAAW,GAAG,OAAO,GAAG,WAAW,GAAG,QAAQ,GAAG,QAAQ,GAAG,MAAM,CAAA;AAE9E,eAAO,MAAM,aAAa,EAAE,SAAS,WAAW,EAM/C,CAAA;AAED;;;;;GAKG;AACH,MAAM,WAAW,SAAS;IACxB,2EAA2E;IAC3E,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB;;;OAGG;IACH,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,sEAAsE;IACtE,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,8DAA8D;IAC9D,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAA;IAC7B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,oEAAoE;IACpE,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,+EAA+E;IAC/E,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CACzC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,wEAAwE;IACxE,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,sDAAsD;IACtD,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,+EAA+E;IAC/E,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,+EAA+E;IAC/E,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;CAC3B;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAA;IAC5B,QAAQ,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IACxC,kEAAkE;IAClE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,2EAA2E;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,SAAS;IACxB;;;OAGG;IACH,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B;;;;OAIG;IACH,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,8EAA8E;IAC9E,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,SAAS,MAAM,EAAE,CAAA;IACpC,QAAQ,CAAC,MAAM,EAAE,SAAS,SAAS,EAAE,CAAA;CACtC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The vocabulary of the PWA layer.
|
|
3
|
+
*
|
|
4
|
+
* Everything here is *data*, and that is the whole design. A route rule is a
|
|
5
|
+
* JSON-serialisable record, not a closure, because the same rule table has to
|
|
6
|
+
* be readable by three different readers: the unit tests in Node, the generated
|
|
7
|
+
* service worker running in a browser, and a human trying to work out why a
|
|
8
|
+
* page served stale content. A table of closures would only be readable by the
|
|
9
|
+
* first two, and only the first one would be testable.
|
|
10
|
+
*/
|
|
11
|
+
export const CACHE_BUCKETS = [
|
|
12
|
+
'shell',
|
|
13
|
+
'documents',
|
|
14
|
+
'assets',
|
|
15
|
+
'images',
|
|
16
|
+
'data',
|
|
17
|
+
];
|
|
18
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/pwa/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAoBH,MAAM,CAAC,MAAM,aAAa,GAA2B;IACnD,OAAO;IACP,WAAW;IACX,QAAQ;IACR,QAAQ;IACR,MAAM;CACP,CAAA"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { CacheBucket, PwaConfig } from './types.js';
|
|
2
|
+
export declare const DEFAULT_CACHE_PREFIX = "cogenta:pwa";
|
|
3
|
+
/**
|
|
4
|
+
* Derives the cache generation from everything that changes how a response is
|
|
5
|
+
* produced or stored: the offline page, the precache list and the whole route
|
|
6
|
+
* table. Editing a rule therefore invalidates the caches that rule filled,
|
|
7
|
+
* which is the case people forget when they version by build number alone.
|
|
8
|
+
*
|
|
9
|
+
* `buildId` is whatever the deployment already has — a commit sha, a build
|
|
10
|
+
* timestamp. Passing it is what makes a content change bump the generation.
|
|
11
|
+
*/
|
|
12
|
+
export declare function computeCacheVersion(input: Pick<PwaConfig, 'offlineUrl' | 'precache' | 'routes'> & {
|
|
13
|
+
readonly buildId?: string;
|
|
14
|
+
}): string;
|
|
15
|
+
/**
|
|
16
|
+
* Builds a cache name.
|
|
17
|
+
*
|
|
18
|
+
* Inlined verbatim into the generated service worker (see `runtime-source.ts`),
|
|
19
|
+
* so it must stay free of any reference to module scope.
|
|
20
|
+
*/
|
|
21
|
+
export declare function cacheNameFor(prefix: string, version: string, bucket: string): string;
|
|
22
|
+
/**
|
|
23
|
+
* Selects the caches to delete at activation: ours, and not of this generation.
|
|
24
|
+
*
|
|
25
|
+
* Two properties matter and both are tested. Caches belonging to another
|
|
26
|
+
* application on the same origin are never returned — a purge that deletes a
|
|
27
|
+
* neighbour's cache is a far worse incident than the one it fixes. And a cache
|
|
28
|
+
* of the current generation is never returned, including when the bucket is one
|
|
29
|
+
* this build no longer uses, because the fetch handler may be filling it right
|
|
30
|
+
* now.
|
|
31
|
+
*
|
|
32
|
+
* Inlined verbatim into the generated service worker: no module-scope
|
|
33
|
+
* references allowed.
|
|
34
|
+
*/
|
|
35
|
+
export declare function cachesToPurge(existing: readonly string[], prefix: string, version: string): string[];
|
|
36
|
+
/** Parsed form of a cache name we own. `null` for anything that is not ours. */
|
|
37
|
+
export interface ParsedCacheName {
|
|
38
|
+
readonly prefix: string;
|
|
39
|
+
readonly version: string;
|
|
40
|
+
readonly bucket: string;
|
|
41
|
+
}
|
|
42
|
+
export declare function parseCacheName(name: string, prefix: string): ParsedCacheName | null;
|
|
43
|
+
/** Typed convenience over `cacheNameFor` for call sites that know the bucket. */
|
|
44
|
+
export declare function bucketCacheName(config: PwaConfig, bucket: CacheBucket): string;
|
|
45
|
+
//# sourceMappingURL=version.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/pwa/version.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,YAAY,CAAA;AAmBxD,eAAO,MAAM,oBAAoB,gBAAgB,CAAA;AAiBjD;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,IAAI,CAAC,SAAS,EAAE,YAAY,GAAG,UAAU,GAAG,QAAQ,CAAC,GAAG;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC3F,MAAM,CAQR;AAED;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEpF;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,SAAS,MAAM,EAAE,EAC3B,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,MAAM,EAAE,CAIV;AAED,gFAAgF;AAChF,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,eAAe,GAAG,IAAI,CAMnF;AAED,iFAAiF;AACjF,wBAAgB,eAAe,CAAC,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,WAAW,GAAG,MAAM,CAE9E"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache naming and generational purge.
|
|
3
|
+
*
|
|
4
|
+
* A cache name carries its generation: `cogenta:pwa:3f2a91cc:images`. That one
|
|
5
|
+
* decision is what makes the purge trivial and total — "delete every cache of
|
|
6
|
+
* mine whose generation is not the current one" needs no bookkeeping, no
|
|
7
|
+
* manifest of what was stored, and no per-entry expiry. Caches from a deploy
|
|
8
|
+
* three versions ago cannot survive, because their name says so.
|
|
9
|
+
*
|
|
10
|
+
* The alternative — one cache reused across deploys, entries expired by age —
|
|
11
|
+
* is the design that produces the incident this module exists to prevent: the
|
|
12
|
+
* cache still holds last month's HTML and nothing in the system knows it.
|
|
13
|
+
*/
|
|
14
|
+
/** Separator between the prefix, the version and the bucket. */
|
|
15
|
+
const SEPARATOR = ':';
|
|
16
|
+
export const DEFAULT_CACHE_PREFIX = 'cogenta:pwa';
|
|
17
|
+
/**
|
|
18
|
+
* FNV-1a, 32 bits. Not `node:crypto`: this helper is also used from the edge
|
|
19
|
+
* build target, where the Node crypto module may be absent. The hash is a cache
|
|
20
|
+
* generation marker, never a security boundary — a collision costs a stale
|
|
21
|
+
* asset, not an exploit.
|
|
22
|
+
*/
|
|
23
|
+
function hash(input) {
|
|
24
|
+
let value = 0x811c9dc5;
|
|
25
|
+
for (let i = 0; i < input.length; i++) {
|
|
26
|
+
value ^= input.charCodeAt(i);
|
|
27
|
+
value = Math.imul(value, 0x01000193) >>> 0;
|
|
28
|
+
}
|
|
29
|
+
return value.toString(16).padStart(8, '0');
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Derives the cache generation from everything that changes how a response is
|
|
33
|
+
* produced or stored: the offline page, the precache list and the whole route
|
|
34
|
+
* table. Editing a rule therefore invalidates the caches that rule filled,
|
|
35
|
+
* which is the case people forget when they version by build number alone.
|
|
36
|
+
*
|
|
37
|
+
* `buildId` is whatever the deployment already has — a commit sha, a build
|
|
38
|
+
* timestamp. Passing it is what makes a content change bump the generation.
|
|
39
|
+
*/
|
|
40
|
+
export function computeCacheVersion(input) {
|
|
41
|
+
const material = JSON.stringify([
|
|
42
|
+
input.buildId ?? '',
|
|
43
|
+
input.offlineUrl,
|
|
44
|
+
[...input.precache].sort(),
|
|
45
|
+
input.routes,
|
|
46
|
+
]);
|
|
47
|
+
return hash(material);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Builds a cache name.
|
|
51
|
+
*
|
|
52
|
+
* Inlined verbatim into the generated service worker (see `runtime-source.ts`),
|
|
53
|
+
* so it must stay free of any reference to module scope.
|
|
54
|
+
*/
|
|
55
|
+
export function cacheNameFor(prefix, version, bucket) {
|
|
56
|
+
return `${prefix}:${version}:${bucket}`;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Selects the caches to delete at activation: ours, and not of this generation.
|
|
60
|
+
*
|
|
61
|
+
* Two properties matter and both are tested. Caches belonging to another
|
|
62
|
+
* application on the same origin are never returned — a purge that deletes a
|
|
63
|
+
* neighbour's cache is a far worse incident than the one it fixes. And a cache
|
|
64
|
+
* of the current generation is never returned, including when the bucket is one
|
|
65
|
+
* this build no longer uses, because the fetch handler may be filling it right
|
|
66
|
+
* now.
|
|
67
|
+
*
|
|
68
|
+
* Inlined verbatim into the generated service worker: no module-scope
|
|
69
|
+
* references allowed.
|
|
70
|
+
*/
|
|
71
|
+
export function cachesToPurge(existing, prefix, version) {
|
|
72
|
+
const mine = `${prefix}:`;
|
|
73
|
+
const current = `${prefix}:${version}:`;
|
|
74
|
+
return existing.filter((name) => name.startsWith(mine) && !name.startsWith(current));
|
|
75
|
+
}
|
|
76
|
+
export function parseCacheName(name, prefix) {
|
|
77
|
+
if (!name.startsWith(`${prefix}${SEPARATOR}`))
|
|
78
|
+
return null;
|
|
79
|
+
const rest = name.slice(prefix.length + SEPARATOR.length);
|
|
80
|
+
const cut = rest.indexOf(SEPARATOR);
|
|
81
|
+
if (cut <= 0 || cut === rest.length - 1)
|
|
82
|
+
return null;
|
|
83
|
+
return { prefix, version: rest.slice(0, cut), bucket: rest.slice(cut + 1) };
|
|
84
|
+
}
|
|
85
|
+
/** Typed convenience over `cacheNameFor` for call sites that know the bucket. */
|
|
86
|
+
export function bucketCacheName(config, bucket) {
|
|
87
|
+
return cacheNameFor(config.cachePrefix, config.version, bucket);
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/pwa/version.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;GAYG;AAEH,gEAAgE;AAChE,MAAM,SAAS,GAAG,GAAG,CAAA;AAErB,MAAM,CAAC,MAAM,oBAAoB,GAAG,aAAa,CAAA;AAEjD;;;;;GAKG;AACH,SAAS,IAAI,CAAC,KAAa;IACzB,IAAI,KAAK,GAAG,UAAU,CAAA;IACtB,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,KAAK,IAAI,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAA;QAC5B,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,CAAA;IAC5C,CAAC;IACD,OAAO,KAAK,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAA;AAC5C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,mBAAmB,CACjC,KAA4F;IAE5F,MAAM,QAAQ,GAAG,IAAI,CAAC,SAAS,CAAC;QAC9B,KAAK,CAAC,OAAO,IAAI,EAAE;QACnB,KAAK,CAAC,UAAU;QAChB,CAAC,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,EAAE;QAC1B,KAAK,CAAC,MAAM;KACb,CAAC,CAAA;IACF,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAA;AACvB,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,OAAe,EAAE,MAAc;IAC1E,OAAO,GAAG,MAAM,IAAI,OAAO,IAAI,MAAM,EAAE,CAAA;AACzC,CAAC;AAED;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,aAAa,CAC3B,QAA2B,EAC3B,MAAc,EACd,OAAe;IAEf,MAAM,IAAI,GAAG,GAAG,MAAM,GAAG,CAAA;IACzB,MAAM,OAAO,GAAG,GAAG,MAAM,IAAI,OAAO,GAAG,CAAA;IACvC,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,CAAA;AACtF,CAAC;AASD,MAAM,UAAU,cAAc,CAAC,IAAY,EAAE,MAAc;IACzD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,MAAM,GAAG,SAAS,EAAE,CAAC;QAAE,OAAO,IAAI,CAAA;IAC1D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAA;IACzD,MAAM,GAAG,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAA;IACnC,IAAI,GAAG,IAAI,CAAC,IAAI,GAAG,KAAK,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAA;IACpD,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,EAAE,CAAA;AAC7E,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,eAAe,CAAC,MAAiB,EAAE,MAAmB;IACpE,OAAO,YAAY,CAAC,MAAM,CAAC,WAAW,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;AACjE,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colour parsing and WCAG contrast, written by hand.
|
|
3
|
+
*
|
|
4
|
+
* Why no dependency: a colour library is a supply-chain surface, and the whole
|
|
5
|
+
* point of the skin validator is that a skin generated by a model in L9 is safe
|
|
6
|
+
* *by construction*. The rules it enforces must be auditable in one file, not
|
|
7
|
+
* inherited from a package that may change its rounding next minor version.
|
|
8
|
+
*/
|
|
9
|
+
export interface Rgb {
|
|
10
|
+
readonly r: number;
|
|
11
|
+
readonly g: number;
|
|
12
|
+
readonly b: number;
|
|
13
|
+
}
|
|
14
|
+
export interface Rgba extends Rgb {
|
|
15
|
+
/** 0 = fully transparent, 1 = fully opaque. */
|
|
16
|
+
readonly a: number;
|
|
17
|
+
}
|
|
18
|
+
/** WCAG 2.2 AA thresholds. Large text is >=18.66px bold or >=24px regular. */
|
|
19
|
+
export declare const AA_NORMAL_TEXT = 4.5;
|
|
20
|
+
export declare const AA_LARGE_TEXT = 3;
|
|
21
|
+
export type TextSize = 'normal' | 'large';
|
|
22
|
+
export declare function aaThreshold(size: TextSize): number;
|
|
23
|
+
/**
|
|
24
|
+
* Parses a CSS colour. The accepted set is deliberately closed to hex, `rgb()`
|
|
25
|
+
* and `hsl()`: named colours, `color()` and `oklch()` would each need their own
|
|
26
|
+
* conversion path before a contrast ratio could be computed, and a colour whose
|
|
27
|
+
* luminance cannot be computed cannot be validated — so it must not be accepted.
|
|
28
|
+
*
|
|
29
|
+
* Returns `null` rather than throwing: the caller owns the error, because only
|
|
30
|
+
* the caller knows which token the value came from.
|
|
31
|
+
*/
|
|
32
|
+
export declare function parseColor(value: string): Rgba | null;
|
|
33
|
+
/** WCAG 2.2 relative luminance. */
|
|
34
|
+
export declare function relativeLuminance(color: Rgb): number;
|
|
35
|
+
/** WCAG 2.2 contrast ratio, between 1 and 21. */
|
|
36
|
+
export declare function contrastRatio(a: Rgb, b: Rgb): number;
|
|
37
|
+
/**
|
|
38
|
+
* Composites a translucent foreground over an opaque background. A contrast
|
|
39
|
+
* ratio computed on the raw foreground of a translucent colour is wrong — and
|
|
40
|
+
* wrong in the optimistic direction — so it is composited first.
|
|
41
|
+
*/
|
|
42
|
+
export declare function compositeOver(foreground: Rgba, background: Rgb): Rgb;
|
|
43
|
+
/**
|
|
44
|
+
* No epsilon on purpose. A pair at 4.4999:1 is below AA; rounding it up to
|
|
45
|
+
* "4.50" is exactly the kind of leniency that makes the guarantee a lie.
|
|
46
|
+
*/
|
|
47
|
+
export declare function meetsContrastAa(ratio: number, size: TextSize): boolean;
|
|
48
|
+
/** Two decimals, the precision every WCAG reporting tool uses. */
|
|
49
|
+
export declare function formatRatio(ratio: number): string;
|
|
50
|
+
//# sourceMappingURL=color.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color.d.ts","sourceRoot":"","sources":["../../src/skin/color.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,WAAW,GAAG;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;IAClB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,IAAK,SAAQ,GAAG;IAC/B,+CAA+C;IAC/C,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,8EAA8E;AAC9E,eAAO,MAAM,cAAc,MAAM,CAAA;AACjC,eAAO,MAAM,aAAa,IAAI,CAAA;AAE9B,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,OAAO,CAAA;AAEzC,wBAAgB,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,MAAM,CAElD;AAKD;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAmBrD;AAwGD,mCAAmC;AACnC,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,GAAG,GAAG,MAAM,CAMpD;AAED,iDAAiD;AACjD,wBAAgB,aAAa,CAAC,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,GAAG,MAAM,CAMpD;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,GAAG,GAAG,GAAG,CAOpE;AAED;;;GAGG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,QAAQ,GAAG,OAAO,CAEtE;AAED,kEAAkE;AAClE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD"}
|