zip-peek 0.6.7 → 0.7.0-beta.2
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 +13 -6
- package/dist/index.d.ts +12 -5
- package/dist/index.js +128 -32
- package/dist/types.d.ts +17 -4
- package/dist/types.js +28 -4
- package/dist/zip-utils.d.ts +4 -0
- package/dist/zip-utils.js +16 -0
- package/dist/zipServiceWorker.js +1 -1
- package/dist/zipServiceWorker.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -157,12 +157,14 @@ Defaults to `true`. When a service worker is installed for the first time, the c
|
|
|
157
157
|
Defaults to `'keep-all'`. Controls which zip-peek cache entries are cleared during initialization.
|
|
158
158
|
|
|
159
159
|
- `'keep-all'`: do not clear cached ZIP manifests or extracted assets.
|
|
160
|
-
- `'keep-allowed-urls'`: requires non-empty `allowedZipUrls`; clears cached ZIP data for URLs outside the allow-list.
|
|
160
|
+
- `'keep-allowed-urls'`: requires non-empty `allowedZipUrls`; clears cached ZIP data for URLs outside the **union of live client claims** (every open tab under the same service worker scope that has configured an allow-list). Closed tabs are dropped from the union so their packages can be cleared.
|
|
161
161
|
- `'clear-all'`: clears all zip-peek cached manifests and extracted assets for a fresh start.
|
|
162
162
|
|
|
163
163
|
`allowedZipUrls`
|
|
164
164
|
|
|
165
|
-
Restricts zip-peek to a known set of ZIP package URLs
|
|
165
|
+
Restricts zip-peek to a known set of ZIP package URLs **for this client (tab)**. When omitted, this tab does not add a claim; if no tab has an allow-list claim, zip-peek lazily serves any matching `.zip/...` request inside the service worker scope. When provided, the array must contain at least one ZIP URL.
|
|
166
|
+
|
|
167
|
+
Across tabs that share the same service worker, each tab's allow-list is a **claim**. The worker serves (and `keep-allowed-urls` retains) the **union** of claims from live clients. Requests to ZIP URLs outside that union receive **403**. After a service worker restart, open tabs are asked to re-claim before a destructive clear so sibling tabs are not wiped.
|
|
166
168
|
|
|
167
169
|
The allow-list also acts as a warmup list: zip-peek loads and caches each allowed ZIP manifest during initialization.
|
|
168
170
|
|
|
@@ -186,9 +188,14 @@ When fallback resolution succeeds, the asset is still served normally, but `onEr
|
|
|
186
188
|
|
|
187
189
|
`onError`
|
|
188
190
|
|
|
189
|
-
Optional error handler for zip-peek failures and informational reports from the service worker. Receives `(error, info?)
|
|
191
|
+
Optional error handler for zip-peek failures and informational reports from the service worker. Receives `(error, info?)`.
|
|
192
|
+
|
|
193
|
+
- `error.message` is a **stable title** for telemetry grouping, always prefixed with `[zip-peek-error]`. Unique presigned query strings are not included in the title.
|
|
194
|
+
- `info` holds request-specific details (`path=… zipPath=… reason=… status=… cause=…`).
|
|
195
|
+
|
|
196
|
+
Service-worker fetch failures post `ZIP_SW_ERROR` so `onError` runs (allow-list 403, missing manifest/file 404, renew 502, origin/handler 500). A thrown `fetch()` (offline, DNS, CORS) uses `[zip-peek-error] [NetworkError] ZIP asset fetch failed` or `[zip-peek-error] [NetworkError] ZIP manifest fetch failed`. An HTTP error response from origin is not tagged `[NetworkError]`.
|
|
190
197
|
|
|
191
|
-
Besides
|
|
198
|
+
Besides those failures, `onError` is also called when exact manifest lookup fails but zip-basename folder fallback succeeds (see `requireExactManifestPath`). Use this to log or telemetry mismatches between request paths and manifest layout without blocking the response.
|
|
192
199
|
|
|
193
200
|
`zipAssetCacheName`
|
|
194
201
|
|
|
@@ -458,7 +465,7 @@ The service worker stores:
|
|
|
458
465
|
|
|
459
466
|
- parsed ZIP manifests (in `zip-cache-v1` by default, keyed per ZIP URL)
|
|
460
467
|
- fully extracted and decompressed assets (populated in the background from a tee of the client stream, with `X-ZipSW-Cached-At` TTL metadata)
|
|
461
|
-
- persisted
|
|
468
|
+
- persisted allow-list union (and restricted flag) in a separate config cache (`zip-peek-config-v1`); live allow-list truth is the union of per-tab claims in the service worker
|
|
462
469
|
|
|
463
470
|
The default asset/manifest cache bucket is:
|
|
464
471
|
|
|
@@ -468,7 +475,7 @@ zip-cache-v1
|
|
|
468
475
|
|
|
469
476
|
Extracted assets expire after two hours by default. Set `assetCacheTtlMs` to customize this.
|
|
470
477
|
|
|
471
|
-
Use `cacheClearingStrategy` to control initialization-time cleanup. `keep-all` leaves existing entries untouched, `keep-allowed-urls` removes cached ZIP data outside
|
|
478
|
+
Use `cacheClearingStrategy` to control initialization-time cleanup. `keep-all` leaves existing entries untouched, `keep-allowed-urls` removes cached ZIP data outside the union of live client claims, and `clear-all` removes all zip-peek cached ZIP data before warming any allowed manifests.
|
|
472
479
|
|
|
473
480
|
## Internal Flow
|
|
474
481
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { InitZipPeekOptions, InitZipPeekResult, RenewPresignedUrlOptions } from "./types";
|
|
2
2
|
import { isZipPackage } from "./zip-utils";
|
|
3
3
|
export type { InitZipPeekOptions, InitZipPeekResult, RenewPresignedUrlOptions, ZipCacheClearingStrategy, ZipPeekErrorHandler, ZipPeekUrlExpiryHandler, ZipWorkerConfig, } from "./types";
|
|
4
|
-
export { getPresignedUrlExpiryMs } from "./zip-utils";
|
|
4
|
+
export { getPresignedUrlExpiryMs, isPresignedUrlExpired } from "./zip-utils";
|
|
5
5
|
/**
|
|
6
6
|
* Registers the zip-peek service worker and configures lazy ZIP asset loading.
|
|
7
7
|
*
|
|
@@ -9,12 +9,19 @@ export { getPresignedUrlExpiryMs } from "./zip-utils";
|
|
|
9
9
|
* service worker scope by loading the ZIP manifest on first use. Pass
|
|
10
10
|
* `allowedZipUrls` to restrict serving to a known, non-empty set of ZIP
|
|
11
11
|
* package URLs and warm their manifests during initialization. Use
|
|
12
|
-
* `cacheClearingStrategy` to keep existing cache entries, keep only
|
|
13
|
-
*
|
|
12
|
+
* `cacheClearingStrategy` to keep existing cache entries, keep only URLs
|
|
13
|
+
* claimed by live clients (union across tabs), or clear all zip-peek cache
|
|
14
|
+
* entries before use.
|
|
15
|
+
*
|
|
16
|
+
* Allow-list state is per client (tab): multiple tabs under the same service
|
|
17
|
+
* worker scope each claim their own URLs; serving and `keep-allowed-urls`
|
|
18
|
+
* clearing use the union of live claims. Closed tabs are reaped, and the
|
|
19
|
+
* worker may reclaim claims from open tabs after a service worker restart.
|
|
14
20
|
*
|
|
15
21
|
* When `onUrlExpiry` is provided and an allowed URL is a recognized presigned
|
|
16
|
-
* URL, zip-peek invokes the handler shortly before expiry
|
|
17
|
-
*
|
|
22
|
+
* URL, zip-peek invokes the handler shortly before expiry, on tab wake after
|
|
23
|
+
* sleep or backgrounding, and when a cache-miss fetch finds the URL already
|
|
24
|
+
* expired. Concurrent callers share one in-flight renewal.
|
|
18
25
|
*/
|
|
19
26
|
export declare function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall, cacheClearingStrategy, allowedZipUrls, requireExactManifestPath, zipAssetCacheName, assetCacheTtlMs, logPrefix, onError, onUrlExpiry, }: InitZipPeekOptions): Promise<InitZipPeekResult>;
|
|
20
27
|
/**
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.isZipPackage = exports.getPresignedUrlExpiryMs = void 0;
|
|
3
|
+
exports.isZipPackage = exports.isPresignedUrlExpired = exports.getPresignedUrlExpiryMs = void 0;
|
|
4
4
|
exports.initZipPeek = initZipPeek;
|
|
5
5
|
exports.renewPresignedUrl = renewPresignedUrl;
|
|
6
6
|
const types_1 = require("./types");
|
|
@@ -8,6 +8,7 @@ const zip_utils_1 = require("./zip-utils");
|
|
|
8
8
|
Object.defineProperty(exports, "isZipPackage", { enumerable: true, get: function () { return zip_utils_1.isZipPackage; } });
|
|
9
9
|
var zip_utils_2 = require("./zip-utils");
|
|
10
10
|
Object.defineProperty(exports, "getPresignedUrlExpiryMs", { enumerable: true, get: function () { return zip_utils_2.getPresignedUrlExpiryMs; } });
|
|
11
|
+
Object.defineProperty(exports, "isPresignedUrlExpired", { enumerable: true, get: function () { return zip_utils_2.isPresignedUrlExpired; } });
|
|
11
12
|
/** Default lead time before presigned URL expiry when scheduling renewal. */
|
|
12
13
|
const URL_EXPIRY_LEAD_MS = 10000;
|
|
13
14
|
/** Retry delay when onUrlExpiry runs but the URL is still tracked (renew failed / skipped). */
|
|
@@ -18,20 +19,17 @@ let currentOnError;
|
|
|
18
19
|
let currentOnUrlExpiry;
|
|
19
20
|
let clientMessageListenerInstalled = false;
|
|
20
21
|
let expiryWakeListenersInstalled = false;
|
|
22
|
+
/**
|
|
23
|
+
* Last allow-list sent to the service worker from this page.
|
|
24
|
+
* `undefined` = initZipPeek not completed; `null` = unrestricted; `string[]` = claim.
|
|
25
|
+
*/
|
|
26
|
+
let lastConfiguredAllowedZipUrls = undefined;
|
|
21
27
|
/** Timers keyed by normalized zip URL. */
|
|
22
28
|
const urlExpiryTimers = new Map();
|
|
23
29
|
/** Original resolved URLs for expiry parsing, keyed by normalized zip URL. */
|
|
24
30
|
const trackedZipUrls = new Map();
|
|
25
|
-
/**
|
|
26
|
-
const urlExpiryInFlight = new
|
|
27
|
-
function zipPackagePathname(zipUrl) {
|
|
28
|
-
try {
|
|
29
|
-
return new URL(zipUrl).pathname;
|
|
30
|
-
}
|
|
31
|
-
catch (_a) {
|
|
32
|
-
return zipUrl.split("?")[0].split("#")[0];
|
|
33
|
-
}
|
|
34
|
-
}
|
|
31
|
+
/** In-flight onUrlExpiry promises keyed by normalized zip URL. */
|
|
32
|
+
const urlExpiryInFlight = new Map();
|
|
35
33
|
const sessionManifests = new Map();
|
|
36
34
|
function normalizeScopePath(href) {
|
|
37
35
|
let p = new URL(href).pathname;
|
|
@@ -175,6 +173,10 @@ function scheduleUrlExpiry(normalizedZipUrl, originalUrlForExpiry) {
|
|
|
175
173
|
trackedZipUrls.delete(normalizedZipUrl);
|
|
176
174
|
return;
|
|
177
175
|
}
|
|
176
|
+
if (isZipUrlDueForRenewal(originalUrlForExpiry)) {
|
|
177
|
+
void invokeUrlExpiry(normalizedZipUrl);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
178
180
|
const delayMs = Math.max(URL_EXPIRY_MIN_DELAY_MS, expiryMs - Date.now() - URL_EXPIRY_LEAD_MS);
|
|
179
181
|
const timerId = setTimeout(() => {
|
|
180
182
|
urlExpiryTimers.delete(normalizedZipUrl);
|
|
@@ -234,24 +236,74 @@ function rearmExpiryIfStillTracked(normalizedZipUrl) {
|
|
|
234
236
|
}, URL_EXPIRY_RETRY_MS);
|
|
235
237
|
urlExpiryTimers.set(normalizedZipUrl, timerId);
|
|
236
238
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
if (
|
|
240
|
-
return;
|
|
239
|
+
function invokeUrlExpiry(normalizedZipUrl) {
|
|
240
|
+
const existing = urlExpiryInFlight.get(normalizedZipUrl);
|
|
241
|
+
if (existing)
|
|
242
|
+
return existing;
|
|
243
|
+
const handler = currentOnUrlExpiry;
|
|
244
|
+
if (!handler)
|
|
245
|
+
return Promise.resolve();
|
|
246
|
+
const run = (async () => {
|
|
247
|
+
var _a;
|
|
248
|
+
try {
|
|
249
|
+
await handler({
|
|
250
|
+
expiredZipURL: (_a = trackedZipUrls.get(normalizedZipUrl)) !== null && _a !== void 0 ? _a : "",
|
|
251
|
+
normalizedExpiredZipURL: normalizedZipUrl,
|
|
252
|
+
});
|
|
253
|
+
}
|
|
254
|
+
catch (error) {
|
|
255
|
+
reportZipPeekError(currentOnError, error, "onUrlExpiry handler failed");
|
|
256
|
+
}
|
|
257
|
+
finally {
|
|
258
|
+
urlExpiryInFlight.delete(normalizedZipUrl);
|
|
259
|
+
rearmExpiryIfStillTracked(normalizedZipUrl);
|
|
260
|
+
}
|
|
261
|
+
})();
|
|
262
|
+
urlExpiryInFlight.set(normalizedZipUrl, run);
|
|
263
|
+
return run;
|
|
264
|
+
}
|
|
265
|
+
function resolveTrackedZipEntry(requestedZipUrl) {
|
|
266
|
+
const requested = (0, zip_utils_1.normalizeZipUrl)(requestedZipUrl);
|
|
267
|
+
const original = trackedZipUrls.get(requested);
|
|
268
|
+
if (original) {
|
|
269
|
+
return { normalized: requested, original };
|
|
270
|
+
}
|
|
271
|
+
const path = (0, zip_utils_1.zipPackagePathname)(requestedZipUrl);
|
|
272
|
+
for (const [normalized, orig] of trackedZipUrls) {
|
|
273
|
+
if ((0, zip_utils_1.zipPackagePathname)(normalized) === path || (0, zip_utils_1.zipPackagePathname)(orig) === path) {
|
|
274
|
+
return { normalized, original: orig };
|
|
275
|
+
}
|
|
241
276
|
}
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
277
|
+
return undefined;
|
|
278
|
+
}
|
|
279
|
+
function resolveCurrentZipUrl(requestedZipUrl) {
|
|
280
|
+
const tracked = resolveTrackedZipEntry(requestedZipUrl);
|
|
281
|
+
if (tracked) {
|
|
282
|
+
return tracked.original;
|
|
248
283
|
}
|
|
249
|
-
|
|
250
|
-
|
|
284
|
+
const path = (0, zip_utils_1.zipPackagePathname)(requestedZipUrl);
|
|
285
|
+
if (Array.isArray(lastConfiguredAllowedZipUrls)) {
|
|
286
|
+
return lastConfiguredAllowedZipUrls.find((url) => (0, zip_utils_1.zipPackagePathname)(url) === path);
|
|
251
287
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
288
|
+
return undefined;
|
|
289
|
+
}
|
|
290
|
+
async function handleSwRenewRequest(zipUrl) {
|
|
291
|
+
var _a, _b;
|
|
292
|
+
try {
|
|
293
|
+
const tracked = resolveTrackedZipEntry(zipUrl);
|
|
294
|
+
const candidateOriginal = (_a = tracked === null || tracked === void 0 ? void 0 : tracked.original) !== null && _a !== void 0 ? _a : zipUrl;
|
|
295
|
+
const candidateNormalized = (_b = tracked === null || tracked === void 0 ? void 0 : tracked.normalized) !== null && _b !== void 0 ? _b : (0, zip_utils_1.normalizeZipUrl)(zipUrl);
|
|
296
|
+
if (isZipUrlDueForRenewal(candidateOriginal) || (0, zip_utils_1.isPresignedUrlExpired)(candidateOriginal)) {
|
|
297
|
+
await invokeUrlExpiry(candidateNormalized);
|
|
298
|
+
}
|
|
299
|
+
const nextUrl = resolveCurrentZipUrl(zipUrl);
|
|
300
|
+
if (!nextUrl || (0, zip_utils_1.isPresignedUrlExpired)(nextUrl)) {
|
|
301
|
+
return { ok: false };
|
|
302
|
+
}
|
|
303
|
+
return { ok: true, nextUrl };
|
|
304
|
+
}
|
|
305
|
+
catch (_c) {
|
|
306
|
+
return { ok: false };
|
|
255
307
|
}
|
|
256
308
|
}
|
|
257
309
|
function ensureClientMessageListener() {
|
|
@@ -265,6 +317,17 @@ function ensureClientMessageListener() {
|
|
|
265
317
|
if (!data) {
|
|
266
318
|
return;
|
|
267
319
|
}
|
|
320
|
+
if (data.type === "ZIP_SW_CLAIM_REQUEST") {
|
|
321
|
+
const port = event.ports[0];
|
|
322
|
+
if (!port)
|
|
323
|
+
return;
|
|
324
|
+
if (lastConfiguredAllowedZipUrls === undefined) {
|
|
325
|
+
port.postMessage({});
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
port.postMessage({ allowedZipUrls: lastConfiguredAllowedZipUrls });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
268
331
|
if (data.type === "ZIP_SW_MANIFEST_CREATED" &&
|
|
269
332
|
data.zipUrl &&
|
|
270
333
|
Array.isArray(data.files) &&
|
|
@@ -280,6 +343,20 @@ function ensureClientMessageListener() {
|
|
|
280
343
|
port.postMessage(files ? { ok: true, files } : { ok: false });
|
|
281
344
|
return;
|
|
282
345
|
}
|
|
346
|
+
if (data.type === "ZIP_SW_RENEW_REQUEST" && data.zipUrl) {
|
|
347
|
+
const port = event.ports[0];
|
|
348
|
+
if (!port)
|
|
349
|
+
return;
|
|
350
|
+
void handleSwRenewRequest(data.zipUrl).then((result) => {
|
|
351
|
+
try {
|
|
352
|
+
port.postMessage(result);
|
|
353
|
+
}
|
|
354
|
+
catch (_a) {
|
|
355
|
+
// Port may already be closed if the client went away.
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
283
360
|
if (data.type !== "ZIP_SW_ERROR" || !currentOnError) {
|
|
284
361
|
return;
|
|
285
362
|
}
|
|
@@ -346,12 +423,19 @@ async function postWorkerMessageOrThrow(message, info) {
|
|
|
346
423
|
* service worker scope by loading the ZIP manifest on first use. Pass
|
|
347
424
|
* `allowedZipUrls` to restrict serving to a known, non-empty set of ZIP
|
|
348
425
|
* package URLs and warm their manifests during initialization. Use
|
|
349
|
-
* `cacheClearingStrategy` to keep existing cache entries, keep only
|
|
350
|
-
*
|
|
426
|
+
* `cacheClearingStrategy` to keep existing cache entries, keep only URLs
|
|
427
|
+
* claimed by live clients (union across tabs), or clear all zip-peek cache
|
|
428
|
+
* entries before use.
|
|
429
|
+
*
|
|
430
|
+
* Allow-list state is per client (tab): multiple tabs under the same service
|
|
431
|
+
* worker scope each claim their own URLs; serving and `keep-allowed-urls`
|
|
432
|
+
* clearing use the union of live claims. Closed tabs are reaped, and the
|
|
433
|
+
* worker may reclaim claims from open tabs after a service worker restart.
|
|
351
434
|
*
|
|
352
435
|
* When `onUrlExpiry` is provided and an allowed URL is a recognized presigned
|
|
353
|
-
* URL, zip-peek invokes the handler shortly before expiry
|
|
354
|
-
*
|
|
436
|
+
* URL, zip-peek invokes the handler shortly before expiry, on tab wake after
|
|
437
|
+
* sleep or backgrounding, and when a cache-miss fetch finds the URL already
|
|
438
|
+
* expired. Concurrent callers share one in-flight renewal.
|
|
355
439
|
*/
|
|
356
440
|
async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, cacheClearingStrategy = "keep-all", allowedZipUrls, requireExactManifestPath = false, zipAssetCacheName, assetCacheTtlMs, logPrefix, onError, onUrlExpiry, }) {
|
|
357
441
|
if (onError) {
|
|
@@ -366,7 +450,9 @@ async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, c
|
|
|
366
450
|
throw (0, types_1.createZipPeekError)('cacheClearingStrategy "keep-allowed-urls" requires a non-empty allowedZipUrls allow-list.');
|
|
367
451
|
}
|
|
368
452
|
if (!("serviceWorker" in navigator)) {
|
|
453
|
+
// Usually happens in the built in Facebook/Messenger browser.
|
|
369
454
|
console.warn("zip-peek: Service Worker API not supported in this browser.");
|
|
455
|
+
alert("Browser not supported. Please try a different browser.");
|
|
370
456
|
return { reloaded: false, initialized: false };
|
|
371
457
|
}
|
|
372
458
|
ensureClientMessageListener();
|
|
@@ -398,6 +484,7 @@ async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, c
|
|
|
398
484
|
requireExactManifestPath,
|
|
399
485
|
},
|
|
400
486
|
}, "Failed to configure service worker");
|
|
487
|
+
lastConfiguredAllowedZipUrls = normalizedAllowedZipUrls !== null && normalizedAllowedZipUrls !== void 0 ? normalizedAllowedZipUrls : null;
|
|
401
488
|
if (cacheClearingStrategy === "clear-all") {
|
|
402
489
|
await postWorkerMessageOrThrow({
|
|
403
490
|
type: "CLEAR_ALL_ZIP_ASSETS",
|
|
@@ -412,6 +499,7 @@ async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, c
|
|
|
412
499
|
requireExactManifestPath,
|
|
413
500
|
},
|
|
414
501
|
}, "Failed to reconfigure service worker after cache clear");
|
|
502
|
+
lastConfiguredAllowedZipUrls = normalizedAllowedZipUrls !== null && normalizedAllowedZipUrls !== void 0 ? normalizedAllowedZipUrls : null;
|
|
415
503
|
}
|
|
416
504
|
else if (cacheClearingStrategy === "keep-allowed-urls" && normalizedAllowedZipUrls) {
|
|
417
505
|
await postWorkerMessageOrThrow({
|
|
@@ -449,6 +537,8 @@ async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, c
|
|
|
449
537
|
*/
|
|
450
538
|
async function renewPresignedUrl({ previousUrl, nextUrl }) {
|
|
451
539
|
if (!("serviceWorker" in navigator)) {
|
|
540
|
+
// Usually happens in the built in Facebook/Messenger browser.
|
|
541
|
+
alert("Browser not supported. Please try a different browser.");
|
|
452
542
|
throw (0, types_1.createZipPeekError)("Service Worker API not supported in this browser.");
|
|
453
543
|
}
|
|
454
544
|
// Prefer the host-provided absolute strings as-is so SigV4 query encoding is
|
|
@@ -469,6 +559,12 @@ async function renewPresignedUrl({ previousUrl, nextUrl }) {
|
|
|
469
559
|
previousUrl: previous,
|
|
470
560
|
nextUrl: next,
|
|
471
561
|
}, "Failed to renew ZIP URL in service worker");
|
|
562
|
+
if (Array.isArray(lastConfiguredAllowedZipUrls)) {
|
|
563
|
+
lastConfiguredAllowedZipUrls = lastConfiguredAllowedZipUrls.map((url) => (url === previous ? next : url));
|
|
564
|
+
if (!lastConfiguredAllowedZipUrls.includes(next)) {
|
|
565
|
+
lastConfiguredAllowedZipUrls = [...lastConfiguredAllowedZipUrls, next];
|
|
566
|
+
}
|
|
567
|
+
}
|
|
472
568
|
const files = sessionManifests.get(previous);
|
|
473
569
|
if (files) {
|
|
474
570
|
sessionManifests.set(next, files);
|
|
@@ -476,9 +572,9 @@ async function renewPresignedUrl({ previousUrl, nextUrl }) {
|
|
|
476
572
|
}
|
|
477
573
|
// Drop the previous URL and any orphaned tracked URLs for the same zip path
|
|
478
574
|
// (e.g. when a prior renew used a mismatched previousUrl and left a timer-less entry).
|
|
479
|
-
const nextPath = zipPackagePathname(next);
|
|
575
|
+
const nextPath = (0, zip_utils_1.zipPackagePathname)(next);
|
|
480
576
|
for (const [normalized] of [...trackedZipUrls]) {
|
|
481
|
-
if (normalized === previous || zipPackagePathname(normalized) === nextPath) {
|
|
577
|
+
if (normalized === previous || (0, zip_utils_1.zipPackagePathname)(normalized) === nextPath) {
|
|
482
578
|
trackedZipUrls.delete(normalized);
|
|
483
579
|
clearUrlExpiryTimer(normalized);
|
|
484
580
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -4,6 +4,11 @@ export type ZipWorkerConfig = {
|
|
|
4
4
|
logPrefix?: string;
|
|
5
5
|
};
|
|
6
6
|
export type ZipCacheClearingStrategy = "keep-all" | "keep-allowed-urls" | "clear-all";
|
|
7
|
+
/**
|
|
8
|
+
* `keep-allowed-urls` clears cached ZIP data outside the **union of live
|
|
9
|
+
* client claims** (all tabs under the same service worker scope that have
|
|
10
|
+
* configured `allowedZipUrls`), not only the calling tab's list.
|
|
11
|
+
*/
|
|
7
12
|
export type ZipPeekErrorHandler = (error: Error, info?: string) => void;
|
|
8
13
|
/**
|
|
9
14
|
* Invoked shortly before a tracked presigned ZIP URL expires.
|
|
@@ -30,10 +35,11 @@ export type InitZipPeekOptions = {
|
|
|
30
35
|
/**
|
|
31
36
|
* Called shortly before a recognized presigned URL expires (CloudFront
|
|
32
37
|
* `Expires` or S3 `X-Amz-Date` + `X-Amz-Expires`). Also invoked after tab
|
|
33
|
-
* wake when a timer was missed due to sleep or background throttling
|
|
38
|
+
* wake when a timer was missed due to sleep or background throttling, and
|
|
39
|
+
* when a service-worker cache miss finds the URL already expired.
|
|
34
40
|
* Not scheduled for non-presigned ZIP URLs. If the handler does not call
|
|
35
41
|
* `renewPresignedUrl`, zip-peek re-arms a retry timer so renewal is not
|
|
36
|
-
* one-shot.
|
|
42
|
+
* one-shot. Concurrent callers share one in-flight handler call per ZIP URL.
|
|
37
43
|
*/
|
|
38
44
|
onUrlExpiry?: ZipPeekUrlExpiryHandler;
|
|
39
45
|
};
|
|
@@ -47,7 +53,14 @@ export type WorkerMessageResponse = {
|
|
|
47
53
|
errorStack?: string;
|
|
48
54
|
};
|
|
49
55
|
export declare const ZIP_PEEK_ERROR_PREFIX = "[zip-peek-error]";
|
|
56
|
+
export declare const NETWORK_ERROR_PREFIX = "[NetworkError]";
|
|
50
57
|
export declare function formatZipPeekError(message: string): string;
|
|
51
|
-
|
|
52
|
-
export declare function
|
|
58
|
+
/** Compact `key=value` details for `onError` info (Sentry extra), not the error title. */
|
|
59
|
+
export declare function zipErrorInfo(fields: Record<string, string | number | boolean | null | undefined>): string;
|
|
60
|
+
export type ZipPeekError = Error & {
|
|
61
|
+
zipPeekInfo?: string;
|
|
62
|
+
};
|
|
63
|
+
export declare function createZipPeekError(message: string, info?: string): Error;
|
|
64
|
+
export declare function toZipPeekError(error: unknown): ZipPeekError;
|
|
65
|
+
export declare function zipPeekErrorInfo(error: unknown): string | undefined;
|
|
53
66
|
export declare function errorMessage(error: unknown): string;
|
package/dist/types.js
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ZIP_PEEK_ERROR_PREFIX = void 0;
|
|
3
|
+
exports.NETWORK_ERROR_PREFIX = exports.ZIP_PEEK_ERROR_PREFIX = void 0;
|
|
4
4
|
exports.formatZipPeekError = formatZipPeekError;
|
|
5
|
+
exports.zipErrorInfo = zipErrorInfo;
|
|
5
6
|
exports.createZipPeekError = createZipPeekError;
|
|
6
7
|
exports.toZipPeekError = toZipPeekError;
|
|
8
|
+
exports.zipPeekErrorInfo = zipPeekErrorInfo;
|
|
7
9
|
exports.errorMessage = errorMessage;
|
|
8
10
|
exports.ZIP_PEEK_ERROR_PREFIX = "[zip-peek-error]";
|
|
11
|
+
exports.NETWORK_ERROR_PREFIX = "[NetworkError]";
|
|
9
12
|
function formatZipPeekError(message) {
|
|
10
13
|
const trimmed = message.trim();
|
|
11
14
|
if (trimmed.startsWith(exports.ZIP_PEEK_ERROR_PREFIX)) {
|
|
@@ -13,16 +16,37 @@ function formatZipPeekError(message) {
|
|
|
13
16
|
}
|
|
14
17
|
return `${exports.ZIP_PEEK_ERROR_PREFIX} ${trimmed}`;
|
|
15
18
|
}
|
|
16
|
-
|
|
17
|
-
|
|
19
|
+
/** Compact `key=value` details for `onError` info (Sentry extra), not the error title. */
|
|
20
|
+
function zipErrorInfo(fields) {
|
|
21
|
+
return Object.entries(fields)
|
|
22
|
+
.filter((entry) => {
|
|
23
|
+
const value = entry[1];
|
|
24
|
+
return value != null && value !== "";
|
|
25
|
+
})
|
|
26
|
+
.map(([key, value]) => `${key}=${String(value)}`)
|
|
27
|
+
.join(" ");
|
|
28
|
+
}
|
|
29
|
+
function createZipPeekError(message, info) {
|
|
30
|
+
const err = new Error(formatZipPeekError(message));
|
|
31
|
+
if (info) {
|
|
32
|
+
err.zipPeekInfo = info;
|
|
33
|
+
}
|
|
34
|
+
return err;
|
|
18
35
|
}
|
|
19
36
|
function toZipPeekError(error) {
|
|
20
|
-
const err = error instanceof Error ? error : new Error(String(error));
|
|
37
|
+
const err = (error instanceof Error ? error : new Error(String(error)));
|
|
21
38
|
if (!err.message.startsWith(exports.ZIP_PEEK_ERROR_PREFIX)) {
|
|
22
39
|
err.message = formatZipPeekError(err.message);
|
|
23
40
|
}
|
|
24
41
|
return err;
|
|
25
42
|
}
|
|
43
|
+
function zipPeekErrorInfo(error) {
|
|
44
|
+
if (error && typeof error === "object" && "zipPeekInfo" in error) {
|
|
45
|
+
const info = error.zipPeekInfo;
|
|
46
|
+
return info ? info : undefined;
|
|
47
|
+
}
|
|
48
|
+
return undefined;
|
|
49
|
+
}
|
|
26
50
|
function errorMessage(error) {
|
|
27
51
|
return error instanceof Error ? error.message : String(error);
|
|
28
52
|
}
|
package/dist/zip-utils.d.ts
CHANGED
|
@@ -12,6 +12,10 @@ export type NormalizeZipUrlOptions = {
|
|
|
12
12
|
* are not re-encoded.
|
|
13
13
|
*/
|
|
14
14
|
export declare function normalizeZipUrl(urlString: string, options?: NormalizeZipUrlOptions): string;
|
|
15
|
+
/** Pathname of a ZIP package URL (`/pkg.zip`), ignoring query and hash. */
|
|
16
|
+
export declare function zipPackagePathname(zipUrl: string): string;
|
|
17
|
+
/** True when a recognized presigned ZIP URL has already passed its expiry. */
|
|
18
|
+
export declare function isPresignedUrlExpired(url: string): boolean;
|
|
15
19
|
/** Basename of the ZIP file from a package URL, without the `.zip` extension. */
|
|
16
20
|
export declare function zipBasenameWithoutExtension(zipUrl: string): string;
|
|
17
21
|
/** Align zip entry names with URL paths under `*.zip/…`. */
|
package/dist/zip-utils.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.isZipPackage = isZipPackage;
|
|
4
4
|
exports.normalizeZipUrl = normalizeZipUrl;
|
|
5
|
+
exports.zipPackagePathname = zipPackagePathname;
|
|
6
|
+
exports.isPresignedUrlExpired = isPresignedUrlExpired;
|
|
5
7
|
exports.zipBasenameWithoutExtension = zipBasenameWithoutExtension;
|
|
6
8
|
exports.normalizeZipEntryPath = normalizeZipEntryPath;
|
|
7
9
|
exports.parseZipAssetRequest = parseZipAssetRequest;
|
|
@@ -46,6 +48,20 @@ function normalizeZipUrl(urlString, options = {}) {
|
|
|
46
48
|
return prefix + hashSection;
|
|
47
49
|
return prefix + "?" + filtered.join("&") + hashSection;
|
|
48
50
|
}
|
|
51
|
+
/** Pathname of a ZIP package URL (`/pkg.zip`), ignoring query and hash. */
|
|
52
|
+
function zipPackagePathname(zipUrl) {
|
|
53
|
+
try {
|
|
54
|
+
return new URL(zipUrl).pathname;
|
|
55
|
+
}
|
|
56
|
+
catch (_a) {
|
|
57
|
+
return zipUrl.split("?")[0].split("#")[0];
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/** True when a recognized presigned ZIP URL has already passed its expiry. */
|
|
61
|
+
function isPresignedUrlExpired(url) {
|
|
62
|
+
const expiryMs = getPresignedUrlExpiryMs(url);
|
|
63
|
+
return expiryMs != null && expiryMs <= Date.now();
|
|
64
|
+
}
|
|
49
65
|
/** Basename of the ZIP file from a package URL, without the `.zip` extension. */
|
|
50
66
|
function zipBasenameWithoutExtension(zipUrl) {
|
|
51
67
|
var _a, _b;
|