@banou/ripple 0.0.30 → 0.0.32
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/assets/{worker-Dx1oQ7Q_.js → worker-kaROsro7.js} +2 -2
- package/fkn.json +1 -1
- package/index.js +2992 -2892
- package/package.json +1 -1
- package/sw.js +59 -24
package/package.json
CHANGED
package/sw.js
CHANGED
|
@@ -8,10 +8,12 @@ const live = new Map()
|
|
|
8
8
|
/* Stamped at build time. These are the dev defaults and must never reach production: a worker whose
|
|
9
9
|
BUILD never changes is never reinstalled, so the update this feature announces would never fire. */
|
|
10
10
|
const MODE = "cache" // @stamp:mode
|
|
11
|
-
const BUILD = "
|
|
12
|
-
const MANIFEST = [["/assets/empty-BvRk9kiK.js","e3b0c44298fc1c14"],["/assets/libtorrent-DleyqKUt.js","27516279f85831f1"],["/assets/worker-
|
|
11
|
+
const BUILD = "dc9663766143c273" // @stamp:build
|
|
12
|
+
const MANIFEST = [["/assets/empty-BvRk9kiK.js","e3b0c44298fc1c14"],["/assets/libtorrent-DleyqKUt.js","27516279f85831f1"],["/assets/worker-kaROsro7.js","3f29b36a9bea36cc"],["/build-B7te3F-u.js","47b056c564f078c5"],["/remote-DvdN47iT.js","b0649250a9e3a079"]] // @stamp:manifest
|
|
13
13
|
|
|
14
14
|
const CACHE = 'ripple-assets-' + BUILD
|
|
15
|
+
/** Holds the name of the generation that activated last, so the next activation can keep it. */
|
|
16
|
+
const ACTIVE = 'ripple-active'
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
19
|
* Which requests this worker may answer from its cache, and the whole of it.
|
|
@@ -30,25 +32,49 @@ const CACHE = 'ripple-assets-' + BUILD
|
|
|
30
32
|
* SPA fallback: 200, text/html, which the browser then runs as a module worker. Measured against
|
|
31
33
|
* production, an earlier build's chunk is byte-identical to one that never existed.
|
|
32
34
|
*/
|
|
33
|
-
const HASHED = /^\/(?:assets\/)?[A-Za-z0-9_.$-]+-[A-Za-z0-9_-]{8
|
|
35
|
+
const HASHED = /^\/(?:assets\/)?[A-Za-z0-9_.$-]+-[A-Za-z0-9_-]{8}\.js$/
|
|
36
|
+
|
|
37
|
+
// An HTML answer for a chunk path is Cloudflare's SPA fallback for a chunk that is gone, not the chunk.
|
|
38
|
+
const isScript = (response) => response.ok && (response.headers.get('content-type') || '').includes('javascript')
|
|
39
|
+
|
|
40
|
+
const PRECACHE_ATTEMPTS = 3
|
|
41
|
+
|
|
42
|
+
/** Resolves once every manifest chunk is stored, asking again only for the ones that failed. */
|
|
43
|
+
const precache = async () => {
|
|
44
|
+
const cache = await caches.open(CACHE)
|
|
45
|
+
let missing = MANIFEST.map(([path]) => path)
|
|
46
|
+
for (let attempt = 0; attempt < PRECACHE_ATTEMPTS && missing.length > 0; attempt++) {
|
|
47
|
+
const failed = await Promise.all(missing.map(async (path) => {
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(path)
|
|
50
|
+
if (!isScript(response)) return path
|
|
51
|
+
await cache.put(path, response)
|
|
52
|
+
return null
|
|
53
|
+
} catch {
|
|
54
|
+
return path
|
|
55
|
+
}
|
|
56
|
+
}))
|
|
57
|
+
missing = failed.filter((path) => path !== null)
|
|
58
|
+
}
|
|
59
|
+
if (missing.length > 0) throw new Error('precache incomplete: ' + missing.join(', '))
|
|
60
|
+
}
|
|
34
61
|
|
|
35
62
|
self.addEventListener('install', (event) => {
|
|
36
63
|
// PURGE is the remote kill switch: ship src/sw.mode as `purge` and the next deploy's worker takes
|
|
37
64
|
// over at once, deletes every cache, and answers nothing. It needs no cooperation from the page.
|
|
38
65
|
if (MODE === 'purge') { self.skipWaiting(); return }
|
|
39
66
|
/*
|
|
40
|
-
* No skipWaiting
|
|
41
|
-
*
|
|
42
|
-
*
|
|
67
|
+
* No skipWaiting: a new worker installs and WAITS, and the page decides when it takes over.
|
|
68
|
+
*
|
|
69
|
+
* An installed worker is what the page reads as "the update is downloaded", so with a worker
|
|
70
|
+
* already active an incomplete precache fails the install: the browser drops this worker and
|
|
71
|
+
* installs it again at its next update check, while the active one keeps serving. The first
|
|
72
|
+
* install never fails on it, because an origin with no worker at all is worse than one whose
|
|
73
|
+
* cache fills on demand.
|
|
43
74
|
*/
|
|
44
|
-
event.waitUntil(
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// A failed precache must not fail the install. The worker still routes and still fills the
|
|
48
|
-
// cache on demand; a rejected install would leave the origin with no worker at all, which is
|
|
49
|
-
// strictly worse than one with a cold cache.
|
|
50
|
-
.catch(() => undefined),
|
|
51
|
-
)
|
|
75
|
+
event.waitUntil(precache().catch((error) => {
|
|
76
|
+
if (self.registration.active) throw error
|
|
77
|
+
}))
|
|
52
78
|
})
|
|
53
79
|
|
|
54
80
|
self.addEventListener('activate', (event) => {
|
|
@@ -58,11 +84,21 @@ self.addEventListener('activate', (event) => {
|
|
|
58
84
|
await self.clients.claim()
|
|
59
85
|
return
|
|
60
86
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
87
|
+
/*
|
|
88
|
+
* Every generation but this one and the one active before it. A page the update gate leaves on
|
|
89
|
+
* the previous build (held by a save or a video, or framed by another page) still loads its lazy
|
|
90
|
+
* chunks, the torrent engine's included, and this worker controls it from the claim below. Older
|
|
91
|
+
* generations are only wasted room: a hashed chunk is immutable, so none of them is ever wrong.
|
|
92
|
+
* With no record yet (the first activation that keeps one) nothing is deleted.
|
|
93
|
+
*/
|
|
94
|
+
const record = await caches.open(ACTIVE)
|
|
95
|
+
const previous = await (await record.match('/'))?.text()
|
|
96
|
+
if (previous !== undefined) {
|
|
97
|
+
for (const key of await caches.keys()) {
|
|
98
|
+
if (key.startsWith('ripple-assets-') && key !== CACHE && key !== previous) await caches.delete(key)
|
|
99
|
+
}
|
|
65
100
|
}
|
|
101
|
+
await record.put('/', new Response(CACHE))
|
|
66
102
|
await self.clients.claim()
|
|
67
103
|
})())
|
|
68
104
|
})
|
|
@@ -82,8 +118,8 @@ self.addEventListener('message', (event) => {
|
|
|
82
118
|
event.source.postMessage({ type: 'pong', id: data.id })
|
|
83
119
|
return
|
|
84
120
|
}
|
|
85
|
-
//
|
|
86
|
-
// in every open page, which
|
|
121
|
+
// From a page's update gate. Only a WAITING worker ever receives this, and taking over fires
|
|
122
|
+
// controllerchange in every open page, each of which then reloads at its own safe moment.
|
|
87
123
|
if (data.type === 'take-over') self.skipWaiting()
|
|
88
124
|
if (data.type === 'which-build' && event.source) {
|
|
89
125
|
event.source.postMessage({ type: 'build', build: BUILD, mode: MODE })
|
|
@@ -110,15 +146,14 @@ self.addEventListener('fetch', (event) => {
|
|
|
110
146
|
const path = new URL(url).pathname
|
|
111
147
|
if (new URL(url).origin === self.location.origin && HASHED.test(path)) {
|
|
112
148
|
event.respondWith((async () => {
|
|
113
|
-
|
|
114
|
-
const hit = await
|
|
149
|
+
// any generation: a page still on the previous build asks for that build's chunks
|
|
150
|
+
const hit = await caches.match(path)
|
|
115
151
|
if (hit) return hit
|
|
116
152
|
const fresh = await fetch(event.request)
|
|
117
153
|
// An HTML answer here IS the bug this worker exists for: the chunk is gone and Cloudflare
|
|
118
154
|
// served the SPA fallback with a 200. Never store that, or the cache would preserve the
|
|
119
155
|
// failure across the very reload meant to cure it.
|
|
120
|
-
|
|
121
|
-
if (fresh.ok && type.includes('javascript')) await cache.put(path, fresh.clone())
|
|
156
|
+
if (isScript(fresh)) await (await caches.open(CACHE)).put(path, fresh.clone())
|
|
122
157
|
return fresh
|
|
123
158
|
})())
|
|
124
159
|
return
|