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 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. When omitted, zip-peek lazily serves any matching `.zip/...` request inside the service worker scope. When provided, the array must contain at least one ZIP URL; requests to ZIP URLs outside the list receive **403** from the service worker.
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?)` where `info` carries additional context when available.
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 initialization and fetch 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.
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 `allowedZipUrls` in a separate config cache (`zip-peek-config-v1`)
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 `allowedZipUrls`, and `clear-all` removes all zip-peek cached ZIP data before warming any allowed manifests.
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 allowed
13
- * ZIP URLs, or clear all zip-peek cache entries before use.
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 and re-checks on tab
17
- * wake (`visibilitychange`, `pageshow`, `focus`) after sleep or backgrounding.
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
- /** Guard concurrent onUrlExpiry invocations per zip URL. */
26
- const urlExpiryInFlight = new Set();
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
- async function invokeUrlExpiry(normalizedZipUrl) {
238
- var _a;
239
- if (!currentOnUrlExpiry || urlExpiryInFlight.has(normalizedZipUrl)) {
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
- urlExpiryInFlight.add(normalizedZipUrl);
243
- try {
244
- await currentOnUrlExpiry({
245
- expiredZipURL: (_a = trackedZipUrls.get(normalizedZipUrl)) !== null && _a !== void 0 ? _a : "",
246
- normalizedExpiredZipURL: normalizedZipUrl,
247
- });
277
+ return undefined;
278
+ }
279
+ function resolveCurrentZipUrl(requestedZipUrl) {
280
+ const tracked = resolveTrackedZipEntry(requestedZipUrl);
281
+ if (tracked) {
282
+ return tracked.original;
248
283
  }
249
- catch (error) {
250
- reportZipPeekError(currentOnError, error, "onUrlExpiry handler failed");
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
- finally {
253
- urlExpiryInFlight.delete(normalizedZipUrl);
254
- rearmExpiryIfStillTracked(normalizedZipUrl);
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 allowed
350
- * ZIP URLs, or clear all zip-peek cache entries before use.
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 and re-checks on tab
354
- * wake (`visibilitychange`, `pageshow`, `focus`) after sleep or backgrounding.
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
- export declare function createZipPeekError(message: string): Error;
52
- export declare function toZipPeekError(error: unknown): Error;
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
- function createZipPeekError(message) {
17
- return new Error(formatZipPeekError(message));
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
  }
@@ -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;