@braidlabs/sw 0.7.1 → 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +149 -52
- package/package.json +7 -3
- package/src/gateway.d.ts +10 -0
- package/src/gateway.js +10 -0
- package/src/gateway.js.map +1 -0
- package/src/index.d.ts +1 -1
- package/src/index.js +1 -1
- package/src/index.js.map +1 -1
- package/src/lib/asset-handler.d.ts +3 -5
- package/src/lib/asset-handler.js +9 -7
- package/src/lib/asset-handler.js.map +1 -1
- package/src/lib/compose.d.ts +9 -0
- package/src/lib/compose.js +63 -8
- package/src/lib/compose.js.map +1 -1
- package/src/lib/fragment-caches.d.ts +55 -0
- package/src/lib/fragment-caches.js +100 -0
- package/src/lib/fragment-caches.js.map +1 -0
- package/src/lib/gateway.d.ts +92 -0
- package/src/lib/gateway.js +164 -0
- package/src/lib/gateway.js.map +1 -0
- package/src/lib/precache.d.ts +2 -3
- package/src/lib/precache.js +4 -1
- package/src/lib/precache.js.map +1 -1
- package/src/lib/static-routes.d.ts +56 -0
- package/src/lib/static-routes.js +76 -0
- package/src/lib/static-routes.js.map +1 -0
- package/src/lib/worker.d.ts +10 -0
- package/src/lib/worker.js +35 -2
- package/src/lib/worker.js.map +1 -1
package/README.md
CHANGED
|
@@ -109,63 +109,160 @@ and cache a composed response (a page pierced against a registry that has since
|
|
|
109
109
|
the stale artifact this package exists to avoid). Composed pages carry
|
|
110
110
|
`x-braid-composed: offline` so a shell can say so rather than look merely slow.
|
|
111
111
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
112
|
+
**What it keeps is one person's.** The parts are fetched with the user's cookies, and the cache
|
|
113
|
+
outlives the session. It keeps nothing the origin marked `Cache-Control: no-store` (and drops a copy
|
|
114
|
+
it kept before the origin said so), and nothing for a route no fragment pierces. `private` pages are
|
|
115
|
+
kept: that directive is about shared caches, this one is the browser's own, and the gateway marks
|
|
116
|
+
every pierced page `private` by default. So clear it at sign-out, or the next person on the device
|
|
117
|
+
is served the last one's pages whenever the network drops. A refresh still running when the clear
|
|
118
|
+
lands does not write afterwards:
|
|
119
|
+
|
|
120
|
+
```ts
|
|
121
|
+
// in the page, at sign-out
|
|
122
|
+
navigator.serviceWorker.controller?.postMessage({ type: 'braid-sw:clear' }); // replies { type: 'braid-sw:cleared', ok }
|
|
123
|
+
|
|
124
|
+
// or in your own worker
|
|
125
|
+
import { clearBraidCaches } from '@braidlabs/sw';
|
|
126
|
+
await clearBraidCaches();
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## The gateway in the worker, for static hosting (`@braidlabs/sw/gateway`, proof of concept)
|
|
130
|
+
|
|
131
|
+
Offline composition runs the gateway's piercing in the worker against the cache. The
|
|
132
|
+
`@braidlabs/sw/gateway` entry runs the **whole gateway** in the worker against the network. That's
|
|
133
|
+
for a shell on purely static hosting (a bucket, a CDN, GitHub Pages) with no server to put a gateway
|
|
134
|
+
on. It's the same `createGateway` the server runs, with the network as its shell.
|
|
135
|
+
|
|
136
|
+
```js
|
|
137
|
+
// sw.js — bundled, and served from the root so its scope covers the shell
|
|
138
|
+
import { setupBraidGateway } from '@braidlabs/sw/gateway';
|
|
139
|
+
|
|
140
|
+
setupBraidGateway({
|
|
141
|
+
registry: [{ id: 'billing', endpoint: new URL('/billing-app/', self.location.origin).href, pierce: ['/billing/*'] }],
|
|
142
|
+
claimClients: true, // the installing page composes without a reload
|
|
143
|
+
assets: {}, // optional: the skew cache, in front of the in-worker gateway
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
Its own entry, rather than part of `@braidlabs/sw`, because it is a different decision. The main
|
|
148
|
+
entry keeps a *server* gateway's assets alive and stays small enough to compose into any worker. It
|
|
149
|
+
bundles none of the gateway's router. This entry *is* the router, rewriter and piercing.
|
|
150
|
+
|
|
151
|
+
To compose it into a worker you already own, use `braidGatewayHandler(options)`. It returns a handler
|
|
152
|
+
that decides synchronously, as `respondWith` requires:
|
|
153
|
+
- It claims same-origin requests under `/__braid/` (or under `basePath`) and the browser's own GET
|
|
154
|
+
navigations.
|
|
155
|
+
- Everything else, including a page's own `fetch` for HTML, returns `null`.
|
|
156
|
+
|
|
157
|
+
Its `.fetch` routes any request through the gateway. Hand it to the asset handler, and the skew
|
|
158
|
+
cache sits in front of the in-worker gateway as it would in front of a server one. Without it, the
|
|
159
|
+
asset handler's misses go to the network, and on a static host that's a 404:
|
|
160
|
+
|
|
161
|
+
```js
|
|
162
|
+
const braid = braidGatewayHandler({ registry });
|
|
163
|
+
const assets = braidFetchHandler({ fetch: braid.fetch });
|
|
164
|
+
self.addEventListener('fetch', (event) => {
|
|
165
|
+
if (!braid.claims(event.request)) return; // the asset handler matches by path alone, on any origin
|
|
166
|
+
const handled = assets(event.request) ?? braid(event.request);
|
|
167
|
+
if (handled) event.respondWith(handled);
|
|
168
|
+
});
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
The page starts Braid once the worker controls it. Until then `/__braid/` is a 404 on a static host.
|
|
172
|
+
A hard reload loads the page past a worker that is already active, and an active worker doesn't
|
|
173
|
+
claim a page unasked, so the page asks:
|
|
174
|
+
|
|
175
|
+
```js
|
|
176
|
+
if (!navigator.serviceWorker.controller) {
|
|
177
|
+
const changed = new Promise((resolve) => navigator.serviceWorker.addEventListener('controllerchange', resolve, { once: true }));
|
|
178
|
+
const registration = await navigator.serviceWorker.register('/sw.js', { type: 'module', scope: '/' });
|
|
179
|
+
registration.active?.postMessage({ type: 'braid-sw:claim' }); // BRAID_CLAIM_MESSAGE, honoured with claimClients
|
|
180
|
+
await changed;
|
|
181
|
+
}
|
|
182
|
+
initBraid();
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
[`examples/sw-gateway`](../../examples/sw-gateway) is the proof of concept. It's a static server with
|
|
186
|
+
no gateway, serving the fragment zstd-compressed as a CDN would. The fragment composes, boots in its
|
|
187
|
+
realm, pierces, and comes back after a hard reload. The repository's e2e runs that example in
|
|
188
|
+
Chromium and asserts no `/__braid/` request ever reaches the server.
|
|
189
|
+
|
|
190
|
+
### What running in a browser changed
|
|
191
|
+
|
|
192
|
+
The server never meets any of these. The e2e found each one, and fails without its fix:
|
|
193
|
+
|
|
194
|
+
- **Navigation requests can't be copied.** The gateway derives fragment requests with
|
|
195
|
+
`new Request(url, request)`, which a browser refuses for a `navigate`-mode request. The gateway
|
|
196
|
+
caught the error and pierced a placeholder.
|
|
197
|
+
- The handler now hands the gateway a non-navigation copy, and the shell still loads through the
|
|
198
|
+
browser's own navigation.
|
|
199
|
+
- The copy sends credentials as a page's own `fetch` would (same-origin), not as the navigation's
|
|
200
|
+
`include`.
|
|
201
|
+
- It keeps a POST's body.
|
|
202
|
+
- **`accept-encoding` is a forbidden header.** The gateway pins it on a server so it knows which
|
|
203
|
+
codings `fetch` decoded, and a browser silently drops the pin. The gateway now notices the header
|
|
204
|
+
didn't take and trusts the browser to have decoded whatever it asked for, instead of refusing
|
|
205
|
+
`zstd` with a 502.
|
|
206
|
+
- **Navigation preload is off.** The browser preloads *every* navigation in scope, including the
|
|
207
|
+
realm frames' navigations, which the worker answers itself. Each would also go to a server with no
|
|
208
|
+
answer for it. `setupBraidGateway` turns preload off. A worker of your own that enables it should
|
|
209
|
+
expect those requests.
|
|
210
|
+
|
|
211
|
+
Two came from review rather than the e2e, and have specs:
|
|
212
|
+
- **A redirecting shell is handed back to the browser.** A navigation's own `fetch` returns an
|
|
213
|
+
opaque redirect, for example when the host answers `/billing` with `/billing/`. It can't be copied
|
|
214
|
+
into a new response, so the gateway passes any status-0 shell through untouched.
|
|
215
|
+
- **A page's `fetch` for HTML isn't a navigation.** The gateway can't see `Sec-Fetch-Dest` from a
|
|
216
|
+
worker. Claiming an htmx or Turbo request by its `Accept` header would pierce it as a document.
|
|
217
|
+
|
|
218
|
+
### Requirements, and where each stands
|
|
219
|
+
|
|
220
|
+
1. **The first visit must work without a reload.** *Met in the POC, partly.*
|
|
221
|
+
- The page registers the worker and starts the runtime once a controller exists, and
|
|
222
|
+
`claimClients` hands it the page. A hard reload asks the active worker to claim it.
|
|
223
|
+
- The first paint isn't composed. Fragments boot client-side, and every later navigation is
|
|
224
|
+
pierced by the worker.
|
|
225
|
+
- Still open: build-time realm stubs, and a timeout on the wait.
|
|
226
|
+
2. **Fragment origins must send CORS headers.** *Documented, not exercised.* The worker reads what
|
|
227
|
+
it pierces, so a cross-origin endpoint needs `Access-Control-Allow-Origin` naming the shell's
|
|
228
|
+
origin. The POC's endpoint is same-origin. The endpoint also has to be public: on a server it can
|
|
229
|
+
sit behind the gateway, but here the browser fetches it.
|
|
230
|
+
3. **Authenticated fragments must be same-origin with the shell.** *Documented.* The worker can't set
|
|
231
|
+
`Cookie` by name, and its fragment requests send credentials same-origin only, as the page's own
|
|
232
|
+
do. So a same-origin endpoint gets the browser's cookies and a cross-origin one gets none.
|
|
233
|
+
`forwardCredentials` and per-fragment cookie scoping can't take effect: the browser's cookie rules
|
|
234
|
+
replace them.
|
|
235
|
+
4. **There must be a fallback where no worker can run.** *Open.* Without a worker, the namespace is
|
|
236
|
+
a 404 on a static host. The POC page waits for a controller indefinitely.
|
|
237
|
+
5. **What the server gateway enforces is advisory here.**
|
|
238
|
+
- `access` and `redirectOrigins` run on the user's device, where the user can change them.
|
|
239
|
+
Anything that must hold against the user stays on the fragment's own origin.
|
|
240
|
+
- Everything in the options ships in the public `sw.js`, `additionalHeaders` included, so they
|
|
241
|
+
must hold no secrets.
|
|
242
|
+
- A fragment's redirect comes back to the worker opaque, so `redirect: 'navigate'` can't be
|
|
243
|
+
honoured. It fails closed: a pierced slot falls back to its placeholder.
|
|
244
|
+
- On `SameSite`, by reasoning rather than a browser test: a worker pierce sends a same-origin
|
|
245
|
+
fragment the cookies the page's own client-side compose would send a moment later. That
|
|
246
|
+
includes `Strict` ones after a cross-site link, which a server pierce wouldn't see. The shell
|
|
247
|
+
should refuse cross-site framing, and the example sends `X-Frame-Options: SAMEORIGIN`.
|
|
248
|
+
6. **The registry must be pinned.** *Open.* The POC bakes its registry into the worker, so a
|
|
249
|
+
registry change is a worker update. That's version discipline of a kind, but it isn't
|
|
250
|
+
`snapshotUrl` loading and verification.
|
|
251
|
+
7. **The gateway core stays runtime-neutral.** *Holds.* The worker bundles the gateway apart from two
|
|
252
|
+
fixes that apply on any runtime: the coding fix above, and passing an opaque shell through. A
|
|
253
|
+
worker that imports only the main entry carries none of it.
|
|
161
254
|
|
|
162
255
|
### Open questions
|
|
163
256
|
|
|
164
|
-
- **Worker startup on every navigation.**
|
|
165
|
-
|
|
166
|
-
the
|
|
257
|
+
- **Worker startup on every navigation.** With preload off, a navigation's shell request waits for
|
|
258
|
+
the worker to boot, which can be tens of milliseconds on mobile. Requests it doesn't answer no
|
|
259
|
+
longer pay that where the browser has static routing. `setupBraidGateway` declares its namespace
|
|
260
|
+
and navigations at install (`staticRoutes`, on by default), and everything else goes straight to
|
|
261
|
+
the network.
|
|
167
262
|
- **Which `x-braid-composed` value** a worker-composed online page should carry, so it can be told
|
|
168
263
|
apart from a server-composed one in telemetry.
|
|
264
|
+
- **`assets` together with `basePath`.** The asset handler only recognises the namespace at the
|
|
265
|
+
root, so `setupBraidGateway` refuses the combination for now.
|
|
169
266
|
|
|
170
267
|
## The worker is itself a skew vector
|
|
171
268
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braidlabs/sw",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.1",
|
|
4
4
|
"description": "Braid service worker — skew-aware asset serving for composed pages: each fragment gets its own build-keyed cache partition, so a page already open keeps getting the chunks it was built against after a deploy lands. Ships as a handler to compose into an existing worker, and as a complete worker.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"micro-frontends",
|
|
@@ -22,11 +22,15 @@
|
|
|
22
22
|
"types": "./src/index.d.ts",
|
|
23
23
|
"default": "./src/index.js"
|
|
24
24
|
},
|
|
25
|
+
"./gateway": {
|
|
26
|
+
"types": "./src/gateway.d.ts",
|
|
27
|
+
"default": "./src/gateway.js"
|
|
28
|
+
},
|
|
25
29
|
"./package.json": "./package.json"
|
|
26
30
|
},
|
|
27
31
|
"peerDependencies": {
|
|
28
|
-
"@braidlabs/data": "^0.
|
|
29
|
-
"@braidlabs/gateway": "^0.
|
|
32
|
+
"@braidlabs/data": "^0.8.1",
|
|
33
|
+
"@braidlabs/gateway": "^0.8.1"
|
|
30
34
|
},
|
|
31
35
|
"publishConfig": {
|
|
32
36
|
"access": "public"
|
package/src/gateway.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@braidlabs/sw/gateway` — the Braid gateway, run inside the service worker.
|
|
3
|
+
*
|
|
4
|
+
* Its own entry rather than part of `@braidlabs/sw`, because it is a different decision: the main
|
|
5
|
+
* entry keeps a server gateway's assets alive across deploys, and stays small enough to compose
|
|
6
|
+
* into any worker. This one *is* the gateway — the whole router, rewriter, and piercing — for an
|
|
7
|
+
* origin with no server to run it on. A worker that imports only the main entry never bundles it.
|
|
8
|
+
*/
|
|
9
|
+
export { braidGatewayHandler, setupBraidGateway, BRAID_CLAIM_MESSAGE, } from './lib/gateway.js';
|
|
10
|
+
export type { BraidGatewayHandler, BraidGatewayHandlerOptions, BraidGatewayWorkerOptions, } from './lib/gateway.js';
|
package/src/gateway.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@braidlabs/sw/gateway` — the Braid gateway, run inside the service worker.
|
|
3
|
+
*
|
|
4
|
+
* Its own entry rather than part of `@braidlabs/sw`, because it is a different decision: the main
|
|
5
|
+
* entry keeps a server gateway's assets alive across deploys, and stays small enough to compose
|
|
6
|
+
* into any worker. This one *is* the gateway — the whole router, rewriter, and piercing — for an
|
|
7
|
+
* origin with no server to run it on. A worker that imports only the main entry never bundles it.
|
|
8
|
+
*/
|
|
9
|
+
export { braidGatewayHandler, setupBraidGateway, BRAID_CLAIM_MESSAGE, } from './lib/gateway.js';
|
|
10
|
+
//# sourceMappingURL=gateway.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"gateway.js","sourceRoot":"","sources":["../../../../libs/braid-sw/src/gateway.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EACL,mBAAmB,EACnB,iBAAiB,EACjB,mBAAmB,GACpB,MAAM,kBAAkB,CAAC"}
|
package/src/index.d.ts
CHANGED
|
@@ -14,7 +14,7 @@ export { braidFetchHandler, pruneFragmentCaches } from './lib/asset-handler.js';
|
|
|
14
14
|
export type { BraidFetchHandlerOptions, AssetOutcome, AssetReport } from './lib/asset-handler.js';
|
|
15
15
|
export { setupBraidWorker } from './lib/worker.js';
|
|
16
16
|
export type { BraidWorkerOptions } from './lib/worker.js';
|
|
17
|
-
export { braidNavigationHandler } from './lib/compose.js';
|
|
17
|
+
export { braidNavigationHandler, clearBraidCaches } from './lib/compose.js';
|
|
18
18
|
export type { OfflineCompositionOptions } from './lib/compose.js';
|
|
19
19
|
export { precacheRealmStubs } from './lib/precache.js';
|
|
20
20
|
export type { PrecacheOptions, PrecacheResult } from './lib/precache.js';
|
package/src/index.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export { braidFetchHandler, pruneFragmentCaches } from './lib/asset-handler.js';
|
|
14
14
|
export { setupBraidWorker } from './lib/worker.js';
|
|
15
|
-
export { braidNavigationHandler } from './lib/compose.js';
|
|
15
|
+
export { braidNavigationHandler, clearBraidCaches } from './lib/compose.js';
|
|
16
16
|
export { precacheRealmStubs } from './lib/precache.js';
|
|
17
17
|
export { createReportQueue, BRAID_REPORT_SYNC_TAG } from './lib/reports.js';
|
|
18
18
|
export { diagnoseSkew, BRAID_BUILD_HEADER, BRAID_SNAPSHOT_HEADER } from './lib/skew-report.js';
|
package/src/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/braid-sw/src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAGhF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,sBAAsB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../libs/braid-sw/src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAGhF,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,sBAAsB,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAG5E,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAGvD,OAAO,EAAE,iBAAiB,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAG5E,OAAO,EAAE,YAAY,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FragmentCacheOptions } from './fragment-caches.js';
|
|
1
2
|
/**
|
|
2
3
|
* Skew-aware asset serving: the one layer that can keep an open page working after a deploy.
|
|
3
4
|
*
|
|
@@ -28,7 +29,7 @@ export interface AssetReport {
|
|
|
28
29
|
/** The cache partition that answered, or would have. */
|
|
29
30
|
partition: string;
|
|
30
31
|
}
|
|
31
|
-
export interface BraidFetchHandlerOptions {
|
|
32
|
+
export interface BraidFetchHandlerOptions extends FragmentCacheOptions {
|
|
32
33
|
/**
|
|
33
34
|
* The build this worker was shipped as.
|
|
34
35
|
*
|
|
@@ -66,7 +67,4 @@ export declare function braidFetchHandler(options?: BraidFetchHandlerOptions): (
|
|
|
66
67
|
* old chunks this handler exists to keep. A fragment that has left the registry, on the other hand,
|
|
67
68
|
* is never coming back for them.
|
|
68
69
|
*/
|
|
69
|
-
export declare function pruneFragmentCaches(keep: readonly string[], options?:
|
|
70
|
-
cachePrefix?: string;
|
|
71
|
-
caches?: CacheStorage;
|
|
72
|
-
}): Promise<string[]>;
|
|
70
|
+
export declare function pruneFragmentCaches(keep: readonly string[], options?: FragmentCacheOptions): Promise<string[]>;
|
package/src/lib/asset-handler.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { parseBraidPathname } from '@braidlabs/gateway';
|
|
2
|
-
|
|
2
|
+
import { DEFAULT_CACHE_PREFIX, deleteFragmentBuckets, keptWithBuckets, openFragmentCache, usesBuckets } from './fragment-caches.js';
|
|
3
3
|
/**
|
|
4
4
|
* Builds a fetch handler for the Braid namespace.
|
|
5
5
|
*
|
|
@@ -13,7 +13,6 @@ const DEFAULT_CACHE_PREFIX = 'braid-frag';
|
|
|
13
13
|
*/
|
|
14
14
|
export function braidFetchHandler(options = {}) {
|
|
15
15
|
const cachePrefix = options.cachePrefix ?? DEFAULT_CACHE_PREFIX;
|
|
16
|
-
const cacheStorage = options.caches ?? globalThis.caches;
|
|
17
16
|
const networkFetch = options.fetch ?? globalThis.fetch;
|
|
18
17
|
return (request) => {
|
|
19
18
|
// GET only. A cache cannot answer a POST, and pretending otherwise is how a worker turns a
|
|
@@ -32,7 +31,7 @@ export function braidFetchHandler(options = {}) {
|
|
|
32
31
|
async function serve(request, url, fragmentId) {
|
|
33
32
|
const partition = `${cachePrefix}:${fragmentId}`;
|
|
34
33
|
const report = (outcome) => options.onReport?.({ fragmentId, url: url.href, outcome, partition });
|
|
35
|
-
const cache = await
|
|
34
|
+
const cache = await openFragmentCache(fragmentId, options);
|
|
36
35
|
try {
|
|
37
36
|
const response = await networkFetch(request);
|
|
38
37
|
// 404 is the deploy case, and the only status worth reaching into the cache for. A 500 is the
|
|
@@ -77,16 +76,19 @@ export function braidFetchHandler(options = {}) {
|
|
|
77
76
|
export async function pruneFragmentCaches(keep, options = {}) {
|
|
78
77
|
const cachePrefix = options.cachePrefix ?? DEFAULT_CACHE_PREFIX;
|
|
79
78
|
const cacheStorage = options.caches ?? globalThis.caches;
|
|
80
|
-
if (!cacheStorage)
|
|
81
|
-
return [];
|
|
82
79
|
const kept = new Set(keep.map((id) => `${cachePrefix}:${id}`));
|
|
83
80
|
const removed = [];
|
|
84
|
-
|
|
85
|
-
|
|
81
|
+
// The shared cache storage first: with buckets on, this is also where a worker that just
|
|
82
|
+
// switched left its old per-fragment caches. A kept fragment's shared cache is superseded only
|
|
83
|
+
// once its bucket exists; one whose bucket was refused falls back to it, and keeps it.
|
|
84
|
+
const superseded = new Set(usesBuckets(options) ? await keptWithBuckets(keep, options) : []);
|
|
85
|
+
for (const name of (await cacheStorage?.keys()) ?? []) {
|
|
86
|
+
if (!name.startsWith(`${cachePrefix}:`) || (kept.has(name) && !superseded.has(name.slice(cachePrefix.length + 1))))
|
|
86
87
|
continue;
|
|
87
88
|
await cacheStorage.delete(name);
|
|
88
89
|
removed.push(name);
|
|
89
90
|
}
|
|
91
|
+
removed.push(...(await deleteFragmentBuckets(keep, options)));
|
|
90
92
|
return removed;
|
|
91
93
|
}
|
|
92
94
|
//# sourceMappingURL=asset-handler.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"asset-handler.js","sourceRoot":"","sources":["../../../../../libs/braid-sw/src/lib/asset-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"asset-handler.js","sourceRoot":"","sources":["../../../../../libs/braid-sw/src/lib/asset-handler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AACxD,OAAO,EAAE,oBAAoB,EAAE,qBAAqB,EAAE,eAAe,EAAE,iBAAiB,EAAE,WAAW,EAA6B,MAAM,sBAAsB,CAAC;AA0D/J;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAC/B,UAAoC,EAAE;IAEtC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAChE,MAAM,YAAY,GAAG,OAAO,CAAC,KAAK,IAAI,UAAU,CAAC,KAAK,CAAC;IAEvD,OAAO,CAAC,OAAgB,EAAE,EAAE;QAC1B,2FAA2F;QAC3F,qCAAqC;QACrC,IAAI,OAAO,CAAC,MAAM,KAAK,KAAK;YAAE,OAAO,IAAI,CAAC;QAE1C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACjC,MAAM,KAAK,GAAG,kBAAkB,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAE/C,+FAA+F;QAC/F,6FAA6F;QAC7F,wFAAwF;QACxF,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,KAAK,UAAU,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC;QAEjF,OAAO,KAAK,CAAC,OAAO,EAAE,GAAG,EAAE,KAAK,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC,CAAC;IAEF,KAAK,UAAU,KAAK,CAAC,OAAgB,EAAE,GAAQ,EAAE,UAAkB;QACjE,MAAM,SAAS,GAAG,GAAG,WAAW,IAAI,UAAU,EAAE,CAAC;QACjD,MAAM,MAAM,GAAG,CAAC,OAAqB,EAAE,EAAE,CACvC,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,UAAU,EAAE,GAAG,EAAE,GAAG,CAAC,IAAI,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,CAAC;QAExE,MAAM,KAAK,GAAG,MAAM,iBAAiB,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAE3D,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,YAAY,CAAC,OAAO,CAAC,CAAC;YAE7C,8FAA8F;YAC9F,6FAA6F;YAC7F,2DAA2D;YAC3D,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,EAAE,CAAC;gBAC5B,MAAM,MAAM,GAAG,MAAM,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;gBAC3C,IAAI,MAAM,EAAE,CAAC;oBACX,MAAM,CAAC,iBAAiB,CAAC,CAAC;oBAC1B,OAAO,MAAM,CAAC;gBAChB,CAAC;YACH,CAAC;YAED,yFAAyF;YACzF,oCAAoC;YACpC,IAAI,QAAQ,CAAC,EAAE,IAAI,QAAQ,CAAC,MAAM,KAAK,GAAG,IAAI,KAAK,EAAE,CAAC;gBACpD,oFAAoF;gBACpF,0EAA0E;gBAC1E,KAAK,KAAK,CAAC,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;YACnE,CAAC;YAED,MAAM,CAAC,SAAS,CAAC,CAAC;YAClB,OAAO,QAAQ,CAAC;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,qEAAqE;YACrE,MAAM,MAAM,GAAG,MAAM,KAAK,EAAE,KAAK,CAAC,OAAO,CAAC,CAAC;YAC3C,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,CAAC,SAAS,CAAC,CAAC;gBAClB,OAAO,MAAM,CAAC;YAChB,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,IAAuB,EACvB,UAAgC,EAAE;IAElC,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,oBAAoB,CAAC;IAChE,MAAM,YAAY,GAAG,OAAO,CAAC,MAAM,IAAK,UAAwC,CAAC,MAAM,CAAC;IACxF,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC;IAC/D,MAAM,OAAO,GAAa,EAAE,CAAC;IAE7B,yFAAyF;IACzF,+FAA+F;IAC/F,uFAAuF;IACvF,MAAM,UAAU,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC7F,KAAK,MAAM,IAAI,IAAI,CAAC,MAAM,YAAY,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC;QACtD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,WAAW,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC;YAAE,SAAS;QAC7H,MAAM,YAAa,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrB,CAAC;IACD,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,qBAAqB,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAE9D,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
package/src/lib/compose.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type FragmentCacheOptions } from './fragment-caches.js';
|
|
1
2
|
/**
|
|
2
3
|
* Offline composition: the gateway's own piercing, run inside the worker.
|
|
3
4
|
*
|
|
@@ -29,6 +30,8 @@ export interface OfflineCompositionOptions {
|
|
|
29
30
|
snapshotUrl: string;
|
|
30
31
|
cachePrefix?: string;
|
|
31
32
|
caches?: CacheStorage;
|
|
33
|
+
/** Keep fragment documents in per-fragment Storage Buckets, as the asset handler does with `buckets`. */
|
|
34
|
+
buckets?: boolean;
|
|
32
35
|
fetch?: typeof fetch;
|
|
33
36
|
/** Called when a page is composed offline, so a shell can say so rather than look merely slow. */
|
|
34
37
|
onCompose?: (event: {
|
|
@@ -52,3 +55,9 @@ export interface OfflineCompositionOptions {
|
|
|
52
55
|
* while the worker is still booting, rather than after.
|
|
53
56
|
*/
|
|
54
57
|
export declare function braidNavigationHandler(options: OfflineCompositionOptions): (request: Request, preloadResponse?: Promise<Response | undefined>) => Promise<Response> | null;
|
|
58
|
+
/**
|
|
59
|
+
* Deletes every page and fragment document kept for offline composition, and the fragments' cached
|
|
60
|
+
* assets with them. Call it at sign-out: what was composed for one person must not be composed for
|
|
61
|
+
* the next. A page can ask its worker to do it with `postMessage({ type: 'braid-sw:clear' })`.
|
|
62
|
+
*/
|
|
63
|
+
export declare function clearBraidCaches(options?: FragmentCacheOptions): Promise<void>;
|
package/src/lib/compose.js
CHANGED
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { BRAID_DOCUMENT_PREFIX, Registry, pierceShellHtml, } from '@braidlabs/gateway';
|
|
2
|
-
|
|
2
|
+
import { DEFAULT_CACHE_PREFIX, deleteFragmentBuckets, openFragmentCache } from './fragment-caches.js';
|
|
3
|
+
/**
|
|
4
|
+
* Bumped by every clear. A refresh started before a clear must not write after it: it was fetched
|
|
5
|
+
* with the previous person's cookies, and putting it back would undo the sign-out it raced.
|
|
6
|
+
*/
|
|
7
|
+
let generation = 0;
|
|
3
8
|
const SHELL_CACHE = 'braid-shell';
|
|
4
9
|
const SNAPSHOT_CACHE = 'braid-snapshot';
|
|
5
10
|
/**
|
|
@@ -54,25 +59,44 @@ export function braidNavigationHandler(options) {
|
|
|
54
59
|
async function refresh(request) {
|
|
55
60
|
if (!cacheStorage)
|
|
56
61
|
return;
|
|
62
|
+
const startedIn = generation;
|
|
63
|
+
const current = () => generation === startedIn;
|
|
57
64
|
const url = new URL(request.url);
|
|
58
65
|
const manifests = await loadSnapshot(url.origin);
|
|
59
66
|
if (!manifests)
|
|
60
67
|
return;
|
|
61
68
|
const registry = new Registry([...manifests]);
|
|
62
69
|
const matches = await registry.matchPierceRoutes(url.pathname);
|
|
70
|
+
// A route no fragment pierces has nothing to compose, and its page is not worth keeping.
|
|
71
|
+
if (matches.length === 0)
|
|
72
|
+
return;
|
|
63
73
|
const shell = await networkFetch(new Request(request.url, { headers: { 'sec-fetch-dest': 'empty' } }));
|
|
64
|
-
if (
|
|
65
|
-
|
|
74
|
+
if (!current())
|
|
75
|
+
return;
|
|
76
|
+
const shellCache = await cacheStorage.open(SHELL_CACHE);
|
|
77
|
+
if (shell.ok && mayKeep(shell))
|
|
66
78
|
await shellCache.put(shellKey(url), shell.clone());
|
|
67
|
-
|
|
79
|
+
// A page the origin now says must not be stored loses the copy kept before it said so.
|
|
80
|
+
else if (shell.ok)
|
|
81
|
+
await shellCache.delete(shellKey(url));
|
|
68
82
|
for (const manifest of matches) {
|
|
69
83
|
const documentUrl = `${url.origin}${BRAID_DOCUMENT_PREFIX}${encodeURIComponent(manifest.id)}${documentPath(manifest, url)}`;
|
|
70
84
|
const response = await networkFetch(documentUrl);
|
|
85
|
+
if (!current())
|
|
86
|
+
return;
|
|
71
87
|
if (!response.ok)
|
|
72
88
|
continue;
|
|
73
89
|
// In the fragment's own partition, so pruning a retired fragment takes its documents too.
|
|
74
|
-
const cache = await
|
|
75
|
-
|
|
90
|
+
const cache = await openFragmentCache(manifest.id, options);
|
|
91
|
+
// a sign-out may have cleared while the bucket opened: nothing of the old session is written back
|
|
92
|
+
if (!current())
|
|
93
|
+
return;
|
|
94
|
+
if (!cache)
|
|
95
|
+
continue;
|
|
96
|
+
if (mayKeep(response))
|
|
97
|
+
await cache.put(documentUrl, response.clone());
|
|
98
|
+
else
|
|
99
|
+
await cache.delete(documentUrl);
|
|
76
100
|
}
|
|
77
101
|
}
|
|
78
102
|
async function composeFromCache(request) {
|
|
@@ -88,11 +112,14 @@ export function braidNavigationHandler(options) {
|
|
|
88
112
|
return null;
|
|
89
113
|
const registry = new Registry([...manifests]);
|
|
90
114
|
const matches = await registry.matchPierceRoutes(url.pathname);
|
|
115
|
+
// Nothing to compose: a shell kept for this route by an older build is not served alone.
|
|
116
|
+
if (matches.length === 0)
|
|
117
|
+
return null;
|
|
91
118
|
const fragments = [];
|
|
92
119
|
for (const manifest of matches) {
|
|
93
|
-
const cache = await
|
|
120
|
+
const cache = await openFragmentCache(manifest.id, options);
|
|
94
121
|
const documentUrl = `${url.origin}${BRAID_DOCUMENT_PREFIX}${encodeURIComponent(manifest.id)}${documentPath(manifest, url)}`;
|
|
95
|
-
const cached = await cache
|
|
122
|
+
const cached = await cache?.match(documentUrl);
|
|
96
123
|
// A fragment with nothing cached pierces nothing and is marked, exactly as a failed fetch is
|
|
97
124
|
// server-side: the slot degrades to a placeholder rather than the page failing.
|
|
98
125
|
fragments.push({
|
|
@@ -140,6 +167,34 @@ export function braidNavigationHandler(options) {
|
|
|
140
167
|
}
|
|
141
168
|
}
|
|
142
169
|
}
|
|
170
|
+
/**
|
|
171
|
+
* Whether a response may be kept for an offline compose: anything but `no-store`.
|
|
172
|
+
*
|
|
173
|
+
* `private` is no reason to refuse. It keeps a response out of *shared* caches, and this cache is the
|
|
174
|
+
* browser's own — and the gateway adds `private` to every pierced page by default, so refusing it
|
|
175
|
+
* would turn offline composition off. What a private page needs is to leave with its person, which
|
|
176
|
+
* is `clearBraidCaches` at sign-out.
|
|
177
|
+
*/
|
|
178
|
+
function mayKeep(response) {
|
|
179
|
+
const directives = (response.headers.get('cache-control') ?? '').toLowerCase().split(',').map((part) => part.trim());
|
|
180
|
+
return !directives.includes('no-store');
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Deletes every page and fragment document kept for offline composition, and the fragments' cached
|
|
184
|
+
* assets with them. Call it at sign-out: what was composed for one person must not be composed for
|
|
185
|
+
* the next. A page can ask its worker to do it with `postMessage({ type: 'braid-sw:clear' })`.
|
|
186
|
+
*/
|
|
187
|
+
export async function clearBraidCaches(options = {}) {
|
|
188
|
+
// Before anything else, so a refresh already running does not write after this clear.
|
|
189
|
+
generation += 1;
|
|
190
|
+
const cacheStorage = options.caches ?? globalThis.caches;
|
|
191
|
+
if (!cacheStorage)
|
|
192
|
+
return;
|
|
193
|
+
const prefix = `${options.cachePrefix ?? DEFAULT_CACHE_PREFIX}:`;
|
|
194
|
+
const names = await cacheStorage.keys();
|
|
195
|
+
await Promise.all(names.filter((name) => name === SHELL_CACHE || name.startsWith(prefix)).map((name) => cacheStorage.delete(name)));
|
|
196
|
+
await deleteFragmentBuckets(null, options, { always: true });
|
|
197
|
+
}
|
|
143
198
|
/** Unbound fragments are fetched at their own path, bound ones at the page's — as on the server. */
|
|
144
199
|
function documentPath(manifest, url) {
|
|
145
200
|
return manifest.bound === false && manifest.src ? manifest.src : `${url.pathname}${url.search}`;
|
package/src/lib/compose.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../../../libs/braid-sw/src/lib/compose.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,qBAAqB,EACrB,QAAQ,EACR,eAAe,GAGhB,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../../../libs/braid-sw/src/lib/compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where a fragment's cached files live: one Cache Storage cache per fragment (`braid-frag:<id>`),
|
|
3
|
+
* or, with `buckets: true` where the browser has Storage Buckets (Chromium), that same cache inside
|
|
4
|
+
* a bucket of the fragment's own.
|
|
5
|
+
*
|
|
6
|
+
* A bucket is the browser's unit of eviction, quota and deletion. In one shared Cache Storage, a
|
|
7
|
+
* device short on space can evict every fragment's files at once, and the deploy-skew save — the
|
|
8
|
+
* chunk an old page still asks for after a deploy — goes with them. Per-fragment buckets are evicted
|
|
9
|
+
* one fragment at a time, and pruning a fragment that left the registry deletes its bucket outright.
|
|
10
|
+
*
|
|
11
|
+
* Opt-in: turning it on moves where files are kept, so a worker that switches starts with empty
|
|
12
|
+
* fragment caches once (pruning removes the old ones).
|
|
13
|
+
*/
|
|
14
|
+
/** The per-fragment cache prefix, unless a worker names its own. */
|
|
15
|
+
export declare const DEFAULT_CACHE_PREFIX = "braid-frag";
|
|
16
|
+
export interface FragmentCacheOptions {
|
|
17
|
+
/** Prefix for the per-fragment caches. Defaults to `braid-frag`. */
|
|
18
|
+
cachePrefix?: string;
|
|
19
|
+
/** Cache storage to use (tests, or a worker that wraps it). Defaults to the global. */
|
|
20
|
+
caches?: CacheStorage;
|
|
21
|
+
/** Keep each fragment's cache in a Storage Bucket of its own where the browser has them. */
|
|
22
|
+
buckets?: boolean;
|
|
23
|
+
/** The bucket manager to use (tests). Defaults to `navigator.storageBuckets`. */
|
|
24
|
+
storageBuckets?: StorageBucketsLike;
|
|
25
|
+
}
|
|
26
|
+
export interface StorageBucketsLike {
|
|
27
|
+
open(name: string): Promise<{
|
|
28
|
+
caches: CacheStorage;
|
|
29
|
+
}>;
|
|
30
|
+
keys(): Promise<string[]>;
|
|
31
|
+
delete(name: string): Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
/** The cache a fragment's files are kept in, or undefined where there is no cache storage. */
|
|
34
|
+
export declare function openFragmentCache(fragmentId: string, options?: FragmentCacheOptions): Promise<Cache | undefined>;
|
|
35
|
+
/**
|
|
36
|
+
* Deletes the fragment buckets this worker's prefix made, except those for `keep` (all of them when
|
|
37
|
+
* null). Only names in this worker's exact format: buckets are the whole origin's, and a looser match
|
|
38
|
+
* would take the realm's database buckets (`braid-db-…`), another worker's, or the app's own.
|
|
39
|
+
*
|
|
40
|
+
* `always` deletes even when `buckets` is off, where the API exists: sign-out must not leave a
|
|
41
|
+
* previous configuration's buckets, which may hold a previous user's offline documents.
|
|
42
|
+
*/
|
|
43
|
+
export declare function deleteFragmentBuckets(keep: readonly string[] | null, options?: FragmentCacheOptions, { always }?: {
|
|
44
|
+
always?: boolean;
|
|
45
|
+
}): Promise<string[]>;
|
|
46
|
+
/** Whether this worker keeps fragment caches in buckets: asked for, and available. */
|
|
47
|
+
export declare function usesBuckets(options: FragmentCacheOptions): boolean;
|
|
48
|
+
/** The fragments among `keep` whose bucket exists, so their shared caches are superseded. */
|
|
49
|
+
export declare function keptWithBuckets(keep: readonly string[], options: FragmentCacheOptions): Promise<string[]>;
|
|
50
|
+
/**
|
|
51
|
+
* A fragment's bucket name: lowercase letters, digits, `-` and `_`, at most 64 characters, as the
|
|
52
|
+
* API requires. The readable part may collide once folded; the hash of the exact id keeps two
|
|
53
|
+
* fragments apart.
|
|
54
|
+
*/
|
|
55
|
+
export declare function bucketFor(fragmentId: string, options?: FragmentCacheOptions): string;
|