arcane-os 0.11.2 → 0.12.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/CHANGELOG.md +25 -0
- package/NOTICE +1 -0
- package/README.md +24 -13
- package/browser-runtime/ai/browser-wllama-runtime.mjs +7 -0
- package/browser-runtime/pwa.mjs +129 -0
- package/docs/architecture.md +28 -12
- package/docs/reference/ai/browser-wasm.md +5 -2
- package/docs/reference/cli.md +42 -16
- package/docs/reference/inventory/package-api.json +1 -1
- package/docs/reference/pwa.md +87 -22
- package/docs/reference/sdk-api.md +61 -27
- package/package.json +2 -1
- package/src/app-descriptor.mjs +43 -1
- package/src/cli/main.mjs +29 -13
- package/src/dev-server.mjs +387 -136
- package/src/pwa-worker.mjs +362 -136
- package/src/pwa.mjs +4 -1
- package/src/targets/index.mjs +5 -1
- package/src/toolchain.mjs +62 -12
package/src/pwa-worker.mjs
CHANGED
|
@@ -1,40 +1,60 @@
|
|
|
1
|
-
export function createPwaWorkerScript(manifest) {
|
|
2
|
-
return `(${installPwaWorker.toString()})(${JSON.stringify(manifest, null, 4)});\n`;
|
|
1
|
+
export function createPwaWorkerScript(manifest, clientUrl = 'arcane/sdk/pwa.mjs') {
|
|
2
|
+
return `(${installPwaWorker.toString()})(${JSON.stringify(manifest, null, 4)}, ${JSON.stringify(clientUrl)});\n`;
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
-
function installPwaWorker(manifest) {
|
|
5
|
+
function installPwaWorker(manifest, clientUrl) {
|
|
6
6
|
const scope = self.registration.scope;
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
);
|
|
7
|
+
const scopeOrigin = new URL(scope).origin;
|
|
8
|
+
const cachePrefix = `arcane-pwa|${JSON.stringify([manifest.appId, scope])}|`;
|
|
9
|
+
const cacheName = `${cachePrefix}resources`;
|
|
10
|
+
const manifestUrl = cacheUrl('arcane-offline.json');
|
|
11
|
+
const protocolUrls = new Set([cacheUrl('arcane-pwa.mjs'), cacheUrl(clientUrl)]);
|
|
12
|
+
const installationAssets = [...new Set(manifest.assets.map(cacheUrl))];
|
|
13
|
+
const resourceJobs = new Map();
|
|
14
|
+
const pendingChecks = new Set();
|
|
15
|
+
const refreshJobs = new Map();
|
|
16
|
+
const ownedUrls = new Set();
|
|
17
|
+
const navigationAliases = new Map();
|
|
18
|
+
let currentManifest = manifest;
|
|
19
|
+
let refreshTask = null;
|
|
20
|
+
let previousCaches = null;
|
|
21
|
+
let manifestRestored = false;
|
|
22
|
+
let lastChecked = null;
|
|
18
23
|
|
|
19
24
|
function cacheUrl(value) {
|
|
20
25
|
const url = new URL(value, scope);
|
|
21
|
-
//
|
|
26
|
+
// Fragments identify document positions; query variants remain distinct resources.
|
|
22
27
|
url.hash = '';
|
|
23
28
|
return url.href;
|
|
24
29
|
}
|
|
25
30
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
cacheUrl(alias),
|
|
36
|
-
new URL(asset, scope).href
|
|
31
|
+
function manifestResources(value) {
|
|
32
|
+
if (!Array.isArray(value.assets) || !value.navigationAliases || typeof value.navigationAliases !== 'object') {
|
|
33
|
+
throw new TypeError('The PWA offline inventory requires assets and navigationAliases.');
|
|
34
|
+
}
|
|
35
|
+
const assets = value.assets.map(cacheUrl);
|
|
36
|
+
const aliases = Object.entries(value.navigationAliases).map(
|
|
37
|
+
function navigationAlias([alias, asset]) {
|
|
38
|
+
return [cacheUrl(alias), new URL(asset, scope).href];
|
|
39
|
+
}
|
|
37
40
|
);
|
|
41
|
+
return {value, assets, aliases};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function useManifest({value, assets, aliases}) {
|
|
45
|
+
currentManifest = value;
|
|
46
|
+
ownedUrls.clear();
|
|
47
|
+
for (const asset of assets) {
|
|
48
|
+
ownedUrls.add(asset);
|
|
49
|
+
}
|
|
50
|
+
navigationAliases.clear();
|
|
51
|
+
for (const [alias, asset] of aliases) {
|
|
52
|
+
navigationAliases.set(alias, asset);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async function readManifest(response) {
|
|
57
|
+
return manifestResources(await response.json());
|
|
38
58
|
}
|
|
39
59
|
|
|
40
60
|
function errorDetails(error) {
|
|
@@ -55,9 +75,7 @@ function installPwaWorker(manifest) {
|
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
async function reportFailure(error) {
|
|
58
|
-
const clients = await self.clients.matchAll(
|
|
59
|
-
{type: 'window', includeUncontrolled: true}
|
|
60
|
-
);
|
|
78
|
+
const clients = await self.clients.matchAll({type: 'window', includeUncontrolled: true});
|
|
61
79
|
const message = {type: 'arcane.pwa.error', error: errorDetails(error)};
|
|
62
80
|
for (const client of clients) {
|
|
63
81
|
if (client.url.startsWith(scope)) {
|
|
@@ -66,126 +84,334 @@ function installPwaWorker(manifest) {
|
|
|
66
84
|
}
|
|
67
85
|
}
|
|
68
86
|
|
|
69
|
-
async function
|
|
87
|
+
async function reportFailureAndReject(error) {
|
|
88
|
+
try {
|
|
89
|
+
await reportFailure(error);
|
|
90
|
+
} catch (reportError) {
|
|
91
|
+
console.error('PWA failure could not be sent to application clients.', reportError, error);
|
|
92
|
+
}
|
|
93
|
+
throw error;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resourceError(url, cause) {
|
|
97
|
+
const error = new Error(`PWA resource could not be cached: ${url}`, {cause});
|
|
98
|
+
error.url = url;
|
|
99
|
+
return error;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function findCachedResource(url) {
|
|
70
103
|
const cache = await caches.open(cacheName);
|
|
71
|
-
const
|
|
72
|
-
|
|
104
|
+
const response = await cache.match(url);
|
|
105
|
+
if (response) {
|
|
106
|
+
return {cache, response, current: true};
|
|
107
|
+
}
|
|
108
|
+
// Carry existing app resources forward without deleting older generations.
|
|
109
|
+
previousCaches ??= caches.keys().then(
|
|
110
|
+
function openPriorCaches(names) {
|
|
111
|
+
return Promise.all(
|
|
112
|
+
names.reverse().filter(
|
|
113
|
+
function priorAppCache(name) {
|
|
114
|
+
return name.startsWith(cachePrefix) && name !== cacheName;
|
|
115
|
+
}
|
|
116
|
+
).map(
|
|
117
|
+
function openPriorCache(name) {
|
|
118
|
+
return caches.open(name);
|
|
119
|
+
}
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
for (const previous of await previousCaches) {
|
|
125
|
+
const retained = await previous.match(url);
|
|
126
|
+
if (retained) {
|
|
127
|
+
return {cache, response: retained, current: false};
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return {cache, response: null, current: true};
|
|
131
|
+
}
|
|
73
132
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
133
|
+
async function restoreManifest() {
|
|
134
|
+
const cached = await findCachedResource(manifestUrl);
|
|
135
|
+
if (cached.response) {
|
|
136
|
+
const inventory = await readManifest(cached.response);
|
|
137
|
+
if (['appVersion', 'sdkVersion', 'mode', 'revision'].every(
|
|
138
|
+
function sameWorkerDeclaration(field) {
|
|
139
|
+
return inventory.value[field] === manifest[field];
|
|
140
|
+
}
|
|
141
|
+
)) {
|
|
142
|
+
useManifest(inventory);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
useManifest(manifestResources(manifest));
|
|
148
|
+
const restored = restoreManifest().catch(
|
|
149
|
+
async function reportManifestRestoreFailure(error) {
|
|
150
|
+
try {
|
|
151
|
+
await reportFailure(error);
|
|
152
|
+
} catch (reportError) {
|
|
153
|
+
console.error('PWA manifest restore failure could not be reported.', reportError, error);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
).finally(
|
|
157
|
+
function manifestRestoreComplete() {
|
|
158
|
+
manifestRestored = true;
|
|
159
|
+
}
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
async function fetchResource(request, url, validate) {
|
|
163
|
+
const cached = await findCachedResource(url);
|
|
164
|
+
// Earlier clients cannot initiate this protocol; upgrade only its owners on migration.
|
|
165
|
+
const protocolMigration = cached.response && !cached.current && protocolUrls.has(url);
|
|
166
|
+
if (cached.response && !validate && !protocolMigration) {
|
|
167
|
+
return {
|
|
168
|
+
response: cached.response,
|
|
169
|
+
saved: cached.current ? Promise.resolve() : cached.cache.put(url, cached.response.clone()),
|
|
170
|
+
checked: false,
|
|
171
|
+
error: null
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
try {
|
|
175
|
+
const headers = new Headers(request.headers);
|
|
176
|
+
const modified = protocolMigration ? null : cached.response?.headers.get('last-modified');
|
|
177
|
+
headers.delete('if-none-match');
|
|
178
|
+
if (modified) {
|
|
179
|
+
headers.set('if-modified-since', modified);
|
|
180
|
+
} else {
|
|
181
|
+
headers.delete('if-modified-since');
|
|
182
|
+
}
|
|
183
|
+
// This conditional request owns validation; CacheStorage owns the durable body.
|
|
184
|
+
const response = await fetch(new Request(request, {headers, cache: 'no-store'}));
|
|
185
|
+
if (response.status === 304 && cached.response) {
|
|
186
|
+
return {
|
|
187
|
+
response: cached.response,
|
|
188
|
+
saved: cached.current ? Promise.resolve() : cached.cache.put(url, cached.response.clone()),
|
|
189
|
+
checked: true,
|
|
190
|
+
error: null
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
if (!response.ok) {
|
|
194
|
+
throw new Error(`PWA resource returned HTTP ${response.status} ${response.statusText}: ${url}`);
|
|
195
|
+
}
|
|
196
|
+
if (url === manifestUrl) {
|
|
197
|
+
// Parse the control document before replacing the last usable inventory.
|
|
198
|
+
await readManifest(response.clone());
|
|
199
|
+
}
|
|
200
|
+
return {
|
|
201
|
+
response,
|
|
202
|
+
saved: cached.cache.put(url, response.clone()),
|
|
203
|
+
checked: true,
|
|
204
|
+
error: null
|
|
205
|
+
};
|
|
206
|
+
} catch (cause) {
|
|
207
|
+
const error = resourceError(url, cause);
|
|
208
|
+
if (!cached.response) {
|
|
209
|
+
throw error;
|
|
210
|
+
}
|
|
211
|
+
return {response: cached.response, saved: Promise.resolve(), checked: false, error};
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function resourceJob(request, url, validate = false) {
|
|
216
|
+
if (resourceJobs.has(url)) {
|
|
217
|
+
return resourceJobs.get(url);
|
|
218
|
+
}
|
|
219
|
+
const job = {result: fetchResource(request, url, validate), done: null, checked: false};
|
|
220
|
+
resourceJobs.set(url, job);
|
|
221
|
+
if (pendingChecks.has(url)) {
|
|
222
|
+
refreshJobs.set(url, job);
|
|
223
|
+
}
|
|
224
|
+
job.done = job.result.then(
|
|
225
|
+
async function finishResource(result) {
|
|
226
|
+
job.checked = result.checked;
|
|
77
227
|
try {
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
url,
|
|
81
|
-
{cache: manifest.mode === 'development' ? 'no-cache' : 'reload'}
|
|
82
|
-
)
|
|
83
|
-
);
|
|
84
|
-
if (!response.ok) {
|
|
85
|
-
throw new Error(`PWA resource returned HTTP ${response.status} ${response.statusText}: ${url}`);
|
|
86
|
-
}
|
|
87
|
-
await cache.put(url, response);
|
|
228
|
+
await result.saved;
|
|
229
|
+
return result.error;
|
|
88
230
|
} catch (cause) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
231
|
+
return resourceError(url, cause);
|
|
232
|
+
}
|
|
233
|
+
},
|
|
234
|
+
function missingResource(error) {
|
|
235
|
+
return error;
|
|
236
|
+
}
|
|
237
|
+
).then(
|
|
238
|
+
function releaseResource(error) {
|
|
239
|
+
resourceJobs.delete(url);
|
|
240
|
+
pendingChecks.delete(url);
|
|
241
|
+
if (refreshJobs.get(url) === job) {
|
|
242
|
+
// Retain cycle outcomes without retaining every completed response stream.
|
|
243
|
+
refreshJobs.set(url, {
|
|
244
|
+
result: Promise.resolve({checked: job.checked}),
|
|
245
|
+
done: Promise.resolve(error)
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
return error;
|
|
249
|
+
}
|
|
250
|
+
);
|
|
251
|
+
return job;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
function resourceResponse(result) {
|
|
255
|
+
return result.response.clone();
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
async function populateResources(urls, validate = false) {
|
|
259
|
+
let nextAsset = 0;
|
|
260
|
+
const failures = [];
|
|
261
|
+
let allChecked = true;
|
|
262
|
+
async function fetchAssets() {
|
|
263
|
+
while (nextAsset < urls.length) {
|
|
264
|
+
const url = urls[nextAsset++];
|
|
265
|
+
let job = (validate ? refreshJobs.get(url) : null) ?? resourceJob(new Request(url), url, validate);
|
|
266
|
+
let error = await job.done;
|
|
267
|
+
if (validate && !error && !(await job.result).checked) {
|
|
268
|
+
// A concurrent cache carry-forward is complete, but has not checked this file.
|
|
269
|
+
job = resourceJob(new Request(url), url, true);
|
|
270
|
+
error = await job.done;
|
|
271
|
+
}
|
|
272
|
+
if (error) {
|
|
94
273
|
failures.push(error);
|
|
95
274
|
}
|
|
275
|
+
if (error || !(await job.result).checked) {
|
|
276
|
+
allChecked = false;
|
|
277
|
+
}
|
|
96
278
|
}
|
|
97
279
|
}
|
|
98
|
-
|
|
99
280
|
const workers = [];
|
|
100
|
-
for (let index = 0; index < Math.min(4,
|
|
101
|
-
workers.push(
|
|
102
|
-
fetchAssets()
|
|
103
|
-
);
|
|
281
|
+
for (let index = 0; index < Math.min(4, urls.length); index += 1) {
|
|
282
|
+
workers.push(fetchAssets());
|
|
104
283
|
}
|
|
105
284
|
await Promise.all(workers);
|
|
285
|
+
return {failures, allChecked};
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
async function populateCache() {
|
|
289
|
+
await restored;
|
|
290
|
+
const {failures, allChecked} = await populateResources(installationAssets);
|
|
106
291
|
if (failures.length > 0) {
|
|
107
292
|
throw new AggregateError(failures, 'The PWA resource generation could not be installed.');
|
|
108
293
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
async function reportFailureAndReject(error) {
|
|
112
|
-
try {
|
|
113
|
-
await reportFailure(error);
|
|
114
|
-
} catch (reportError) {
|
|
115
|
-
console.error('PWA failure could not be sent to application clients.', reportError, error);
|
|
294
|
+
if (allChecked) {
|
|
295
|
+
lastChecked = Date.now();
|
|
116
296
|
}
|
|
117
|
-
throw error;
|
|
118
297
|
}
|
|
119
298
|
|
|
120
299
|
function onInstall(event) {
|
|
121
|
-
event.waitUntil(
|
|
122
|
-
populateCache().catch(reportFailureAndReject)
|
|
123
|
-
);
|
|
300
|
+
event.waitUntil(populateCache().catch(reportFailureAndReject));
|
|
124
301
|
}
|
|
125
302
|
|
|
126
|
-
|
|
127
|
-
const
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
303
|
+
function checkDue() {
|
|
304
|
+
const interval = currentManifest.mode === 'development' ? 120000 : 900000;
|
|
305
|
+
return lastChecked === null || Date.now() - lastChecked > interval;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
async function refreshResources() {
|
|
309
|
+
await restored;
|
|
310
|
+
if (!checkDue()) {
|
|
311
|
+
return {lastChecked, error: null};
|
|
312
|
+
}
|
|
313
|
+
const failures = [];
|
|
314
|
+
let inventory = resourceJob(new Request(manifestUrl), manifestUrl, true);
|
|
315
|
+
let error = await inventory.done;
|
|
316
|
+
if (!error && !(await inventory.result).checked) {
|
|
317
|
+
inventory = resourceJob(new Request(manifestUrl), manifestUrl, true);
|
|
318
|
+
error = await inventory.done;
|
|
319
|
+
}
|
|
320
|
+
if (error) {
|
|
321
|
+
failures.push(error);
|
|
322
|
+
} else {
|
|
323
|
+
try {
|
|
324
|
+
const result = await inventory.result;
|
|
325
|
+
useManifest(await readManifest(result.response.clone()));
|
|
326
|
+
} catch (cause) {
|
|
327
|
+
failures.push(resourceError(manifestUrl, cause));
|
|
134
328
|
}
|
|
135
329
|
}
|
|
136
|
-
|
|
330
|
+
const urls = [...ownedUrls].filter(
|
|
331
|
+
function selectedResource(url) {
|
|
332
|
+
return url !== manifestUrl;
|
|
333
|
+
}
|
|
334
|
+
);
|
|
335
|
+
for (const url of urls) {
|
|
336
|
+
pendingChecks.add(url);
|
|
337
|
+
}
|
|
338
|
+
const checked = await populateResources(urls, true);
|
|
339
|
+
failures.push(...checked.failures);
|
|
340
|
+
if (failures.length === 0) {
|
|
341
|
+
lastChecked = Date.now();
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
lastChecked,
|
|
345
|
+
error: failures.length > 0
|
|
346
|
+
? errorDetails(new AggregateError(failures, 'PWA resources could not all be updated.'))
|
|
347
|
+
: null
|
|
348
|
+
};
|
|
137
349
|
}
|
|
138
350
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
351
|
+
function refresh(checked) {
|
|
352
|
+
if (Number.isFinite(checked)) {
|
|
353
|
+
lastChecked = Math.max(lastChecked ?? 0, checked);
|
|
354
|
+
}
|
|
355
|
+
if (!refreshTask) {
|
|
356
|
+
refreshTask = refreshResources().finally(
|
|
357
|
+
function releaseRefresh() {
|
|
358
|
+
refreshTask = null;
|
|
359
|
+
refreshJobs.clear();
|
|
360
|
+
}
|
|
361
|
+
);
|
|
362
|
+
}
|
|
363
|
+
return refreshTask;
|
|
143
364
|
}
|
|
144
365
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
366
|
+
function onMessage(event) {
|
|
367
|
+
if (event.data?.type === 'arcane.pwa.capabilities') {
|
|
368
|
+
try {
|
|
369
|
+
event.source?.postMessage({type: 'arcane.pwa.capabilities', refresh: true, cacheName});
|
|
370
|
+
} catch (error) {
|
|
371
|
+
event.waitUntil(reportFailureAndReject(error));
|
|
372
|
+
}
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (event.data?.type !== 'arcane.pwa.refresh') {
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
const port = event.ports?.[0];
|
|
379
|
+
event.waitUntil(
|
|
380
|
+
refresh(event.data.lastChecked).then(
|
|
381
|
+
async function completeRefresh(result) {
|
|
382
|
+
port?.postMessage({type: 'arcane.pwa.refreshed', ...result});
|
|
383
|
+
port?.close();
|
|
384
|
+
if (result.error) {
|
|
385
|
+
await reportFailure(result.error);
|
|
386
|
+
}
|
|
387
|
+
},
|
|
388
|
+
async function failRefresh(error) {
|
|
389
|
+
port?.postMessage({type: 'arcane.pwa.refreshed', lastChecked, error: errorDetails(error)});
|
|
390
|
+
port?.close();
|
|
391
|
+
await reportFailure(error);
|
|
392
|
+
}
|
|
393
|
+
).catch(reportFailureAndReject)
|
|
156
394
|
);
|
|
157
395
|
}
|
|
158
396
|
|
|
159
|
-
async function
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
397
|
+
async function requestedResource(request, url) {
|
|
398
|
+
await restored;
|
|
399
|
+
const redirect = request.mode === 'navigate' ? navigationAliases.get(url) : null;
|
|
400
|
+
if (redirect && cacheUrl(redirect) !== url && ownedUrls.has(cacheUrl(redirect))) {
|
|
401
|
+
return {response: Response.redirect(redirect, 302), done: Promise.resolve(null)};
|
|
402
|
+
}
|
|
403
|
+
if (!ownedUrls.has(url)) {
|
|
404
|
+
return {response: await fetch(request), done: Promise.resolve(null)};
|
|
405
|
+
}
|
|
406
|
+
if (!pendingChecks.has(url) && !resourceJobs.has(url)) {
|
|
169
407
|
const cache = await caches.open(cacheName);
|
|
170
408
|
const cached = await cache.match(url);
|
|
171
409
|
if (cached) {
|
|
172
|
-
return {response: cached,
|
|
410
|
+
return {response: cached, done: Promise.resolve(null)};
|
|
173
411
|
}
|
|
174
|
-
throw error;
|
|
175
|
-
}
|
|
176
|
-
return {response, fromNetwork: true, url};
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
function resourceResponse(resource) {
|
|
180
|
-
return resource.response;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function saveDevelopmentResource(resource) {
|
|
184
|
-
if (resource.fromNetwork && resource.response.ok) {
|
|
185
|
-
const response = resource.response.clone();
|
|
186
|
-
const cache = await caches.open(cacheName);
|
|
187
|
-
await cache.put(resource.url, response);
|
|
188
412
|
}
|
|
413
|
+
const job = resourceJob(request, url, pendingChecks.has(url));
|
|
414
|
+
return {response: resourceResponse(await job.result), done: job.done};
|
|
189
415
|
}
|
|
190
416
|
|
|
191
417
|
function onFetch(event) {
|
|
@@ -193,35 +419,35 @@ function installPwaWorker(manifest) {
|
|
|
193
419
|
if (request.method !== 'GET' || request.headers.has('range')) {
|
|
194
420
|
return;
|
|
195
421
|
}
|
|
196
|
-
const
|
|
197
|
-
|
|
198
|
-
if (redirect && cacheUrl(redirect) !== requestUrl && ownedUrls.has(cacheUrl(redirect))) {
|
|
199
|
-
// Navigation retains its requested document URL unless it follows a redirect.
|
|
200
|
-
event.respondWith(
|
|
201
|
-
Response.redirect(redirect, 302)
|
|
202
|
-
);
|
|
203
|
-
return;
|
|
204
|
-
}
|
|
205
|
-
const url = requestUrl;
|
|
206
|
-
if (!ownedUrls.has(url)) {
|
|
422
|
+
const url = cacheUrl(request.url);
|
|
423
|
+
if (new URL(url).origin !== scopeOrigin && !ownedUrls.has(url)) {
|
|
207
424
|
return;
|
|
208
425
|
}
|
|
209
|
-
if (
|
|
210
|
-
|
|
211
|
-
event.waitUntil(
|
|
212
|
-
resource.then(saveDevelopmentResource).catch(reportFailureAndReject)
|
|
213
|
-
);
|
|
214
|
-
event.respondWith(
|
|
215
|
-
resource.then(resourceResponse)
|
|
216
|
-
);
|
|
426
|
+
if (manifestRestored && !ownedUrls.has(url)
|
|
427
|
+
&& !(request.mode === 'navigate' && navigationAliases.has(url))) {
|
|
217
428
|
return;
|
|
218
429
|
}
|
|
430
|
+
const resource = requestedResource(request, url);
|
|
219
431
|
event.respondWith(
|
|
220
|
-
|
|
432
|
+
resource.then(
|
|
433
|
+
function requestedResponse(result) {
|
|
434
|
+
return result.response;
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
);
|
|
438
|
+
event.waitUntil(
|
|
439
|
+
resource.then(
|
|
440
|
+
async function completeRequestedResource(result) {
|
|
441
|
+
const error = await result.done;
|
|
442
|
+
if (error) {
|
|
443
|
+
throw error;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
).catch(reportFailureAndReject)
|
|
221
447
|
);
|
|
222
448
|
}
|
|
223
449
|
|
|
224
450
|
self.addEventListener('install', onInstall);
|
|
225
|
-
self.addEventListener('activate', onActivate);
|
|
226
451
|
self.addEventListener('fetch', onFetch);
|
|
452
|
+
self.addEventListener('message', onMessage);
|
|
227
453
|
}
|
package/src/pwa.mjs
CHANGED
|
@@ -301,7 +301,10 @@ controller.ready.catch(
|
|
|
301
301
|
files: [
|
|
302
302
|
{path: PWA_MANIFEST_NAME, content: json(manifest)},
|
|
303
303
|
{path: PWA_OFFLINE_MANIFEST_NAME, content: json(offlineManifest)},
|
|
304
|
-
{
|
|
304
|
+
{
|
|
305
|
+
path: PWA_WORKER_NAME,
|
|
306
|
+
content: createPwaWorkerScript(offlineManifest, `${runtimeBase}pwa.mjs`)
|
|
307
|
+
},
|
|
305
308
|
{path: PWA_BOOTSTRAP_NAME, content: bootstrap}
|
|
306
309
|
],
|
|
307
310
|
entryAssets,
|
package/src/targets/index.mjs
CHANGED
|
@@ -148,7 +148,7 @@ const browserAdapter={
|
|
|
148
148
|
throwIfAborted(signal);
|
|
149
149
|
return {target:'browser',release:await verifyApp({workspaceRoot,appId,signal,onEvent})};
|
|
150
150
|
},
|
|
151
|
-
async run({workspaceRoot,appId,host='127.0.0.1',port=0,signal,onEvent}={}){
|
|
151
|
+
async run({workspaceRoot,appId,host='127.0.0.1',port=0,httpPort=0,tls,certPath,keyPath,signal,onEvent}={}){
|
|
152
152
|
throwIfAborted(signal);
|
|
153
153
|
const releaseRoot=path.join(workspaceRoot,'dist',appId);
|
|
154
154
|
const server=await startDevServer({
|
|
@@ -158,6 +158,10 @@ const browserAdapter={
|
|
|
158
158
|
releaseRoot,
|
|
159
159
|
host,
|
|
160
160
|
port,
|
|
161
|
+
httpPort,
|
|
162
|
+
tls,
|
|
163
|
+
certPath,
|
|
164
|
+
keyPath,
|
|
161
165
|
signal,
|
|
162
166
|
onEvent
|
|
163
167
|
});
|