zip-peek 0.2.0 → 0.2.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 +63 -55
- package/dist/index.d.ts +3 -21
- package/dist/index.js +18 -15
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/zip-utils.d.ts +9 -5
- package/dist/zip-utils.js +33 -29
- package/dist/zipServiceWorker.js +1 -1
- package/dist/zipServiceWorker.js.map +1 -1
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -17,14 +17,14 @@ Use this package when your app receives a base path and later appends folders an
|
|
|
17
17
|
For example, your app may normally treat the base path as a directory:
|
|
18
18
|
|
|
19
19
|
```ts
|
|
20
|
-
const basePath =
|
|
20
|
+
const basePath = "https://cdn.example.com/packages/session";
|
|
21
21
|
const imageUrl = `${basePath}/slides/slide-1/image.png`;
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
If the same assets are sometimes delivered as a ZIP file instead:
|
|
25
25
|
|
|
26
26
|
```ts
|
|
27
|
-
const basePath =
|
|
27
|
+
const basePath = "https://cdn.example.com/packages/session.zip";
|
|
28
28
|
const imageUrl = `${basePath}/slides/slide-1/image.png`;
|
|
29
29
|
```
|
|
30
30
|
|
|
@@ -63,22 +63,22 @@ npm install zip-peek
|
|
|
63
63
|
## Basic Usage
|
|
64
64
|
|
|
65
65
|
```ts
|
|
66
|
-
import { initZipPeek } from
|
|
66
|
+
import { initZipPeek } from "zip-peek";
|
|
67
67
|
|
|
68
68
|
const zipPeekInit = await initZipPeek({
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
workerUrl: new URL("/zipServiceWorker.js", window.location.origin).href,
|
|
70
|
+
scopeUrl: new URL("/", window.location.origin).href,
|
|
71
71
|
});
|
|
72
72
|
|
|
73
73
|
if (zipPeekInit.reloaded) {
|
|
74
|
-
|
|
74
|
+
return;
|
|
75
75
|
}
|
|
76
76
|
```
|
|
77
77
|
|
|
78
78
|
After initialization, your app can continue building asset URLs under the ZIP URL:
|
|
79
79
|
|
|
80
80
|
```ts
|
|
81
|
-
const zipUrl =
|
|
81
|
+
const zipUrl = "https://cdn.example.com/packages/zipfile.zip";
|
|
82
82
|
const imgUrl = `${zipUrl}/slides/slide-1/image.png`;
|
|
83
83
|
|
|
84
84
|
// This request will work normally and zip-peek will do the magic in the background:
|
|
@@ -93,25 +93,25 @@ The browser requests that URL normally. The service worker intercepts it and ser
|
|
|
93
93
|
|
|
94
94
|
```ts
|
|
95
95
|
type InitZipPeekOptions = {
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
workerUrl: string;
|
|
97
|
+
scopeUrl: string;
|
|
98
|
+
reloadOnFirstInstall?: boolean;
|
|
99
|
+
cacheClearingStrategy?: ZipCacheClearingStrategy;
|
|
100
|
+
allowedZipUrls?: string[];
|
|
101
|
+
zipAssetCacheName?: string;
|
|
102
|
+
assetCacheTtlMs?: number;
|
|
103
|
+
logPrefix?: string;
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
-
type ZipCacheClearingStrategy =
|
|
106
|
+
type ZipCacheClearingStrategy = "keep-all" | "keep-allowed-urls" | "clear-all";
|
|
107
107
|
```
|
|
108
108
|
|
|
109
109
|
Returns:
|
|
110
110
|
|
|
111
111
|
```ts
|
|
112
112
|
type InitZipPeekResult = {
|
|
113
|
-
|
|
114
|
-
|
|
113
|
+
reloaded: boolean;
|
|
114
|
+
initialized: boolean;
|
|
115
115
|
};
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -124,10 +124,10 @@ The URL where the browser can download the service worker JavaScript file. The s
|
|
|
124
124
|
Example:
|
|
125
125
|
|
|
126
126
|
```ts
|
|
127
|
-
workerUrl: new URL(
|
|
127
|
+
workerUrl: new URL("./zipServiceWorker.js", window.location.href).href;
|
|
128
128
|
```
|
|
129
129
|
|
|
130
|
-
In production, you will usually prefer hashed file to give it a long
|
|
130
|
+
In production, you will usually prefer hashed file to give it a long `Cache-Control` duration:
|
|
131
131
|
|
|
132
132
|
```text
|
|
133
133
|
zipServiceWorker.a1b2c3d4.js
|
|
@@ -136,12 +136,13 @@ zipServiceWorker.a1b2c3d4.js
|
|
|
136
136
|
`scopeUrl`
|
|
137
137
|
|
|
138
138
|
The service worker scope. The worker can only intercept requests inside this scope.
|
|
139
|
+
See [Service Worker API](https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerContainer/register#scope) for more details.
|
|
139
140
|
|
|
140
141
|
Examples:
|
|
141
142
|
|
|
142
143
|
```ts
|
|
143
|
-
scopeUrl: new URL(
|
|
144
|
-
scopeUrl: new URL(
|
|
144
|
+
scopeUrl: new URL("/", window.location.origin).href;
|
|
145
|
+
scopeUrl: new URL("/app/", window.location.origin).href;
|
|
145
146
|
```
|
|
146
147
|
|
|
147
148
|
Choose the smallest scope that includes the pages and asset requests you want the service worker to intercept. For local development, the scope often differs from production because the app may be served from a different path.
|
|
@@ -160,17 +161,17 @@ Defaults to `'keep-all'`. Controls which zip-peek cache entries are cleared duri
|
|
|
160
161
|
|
|
161
162
|
`allowedZipUrls`
|
|
162
163
|
|
|
163
|
-
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, and zip-peek
|
|
164
|
+
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, and zip-peek transparently passes matching requests for ZIP URLs outside the list.
|
|
164
165
|
|
|
165
166
|
The allow-list also acts as a warmup list: zip-peek loads and caches each allowed ZIP manifest during initialization.
|
|
166
167
|
|
|
167
168
|
Examples:
|
|
168
169
|
|
|
169
170
|
```ts
|
|
170
|
-
allowedZipUrls: [
|
|
171
|
+
allowedZipUrls: ["https://cdn.example.com/packages/zipfile.zip"];
|
|
171
172
|
allowedZipUrls: [
|
|
172
|
-
|
|
173
|
-
]
|
|
173
|
+
"https://d3jfe.cloudfront.net/somedir/zipfile.zip?Expires=1798761599&Signature=K2CJW4Z7B3DCM4&Key-Pair-Id=K2CJW4Z7B3DCM4",
|
|
174
|
+
];
|
|
174
175
|
```
|
|
175
176
|
|
|
176
177
|
`zipAssetCacheName`
|
|
@@ -190,7 +191,7 @@ Controls how long extracted assets remain valid in the Cache API.
|
|
|
190
191
|
Default:
|
|
191
192
|
|
|
192
193
|
```ts
|
|
193
|
-
2 * 60 * 60 * 1000
|
|
194
|
+
2 * 60 * 60 * 1000; // 2 hours
|
|
194
195
|
```
|
|
195
196
|
|
|
196
197
|
`logPrefix`
|
|
@@ -268,8 +269,8 @@ The registered scope must be allowed by both:
|
|
|
268
269
|
Example:
|
|
269
270
|
|
|
270
271
|
```ts
|
|
271
|
-
navigator.serviceWorker.register(
|
|
272
|
-
|
|
272
|
+
navigator.serviceWorker.register("/app/zipServiceWorker.a1b2c3d4.js", {
|
|
273
|
+
scope: "/app/",
|
|
273
274
|
});
|
|
274
275
|
```
|
|
275
276
|
|
|
@@ -300,19 +301,19 @@ Your application must copy this file into its public build output. The browser r
|
|
|
300
301
|
When your app emits hashed assets, copy the worker with a content hash and pass the final worker URL to `initZipPeek()`.
|
|
301
302
|
|
|
302
303
|
```js
|
|
303
|
-
const CopyPlugin = require(
|
|
304
|
+
const CopyPlugin = require("copy-webpack-plugin");
|
|
304
305
|
|
|
305
306
|
module.exports = {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
307
|
+
plugins: [
|
|
308
|
+
new CopyPlugin({
|
|
309
|
+
patterns: [
|
|
310
|
+
{
|
|
311
|
+
from: require.resolve("zip-peek/zipServiceWorker.js"),
|
|
312
|
+
to: "zipServiceWorker.[contenthash:8].js",
|
|
313
|
+
},
|
|
314
|
+
],
|
|
315
|
+
}),
|
|
316
|
+
],
|
|
316
317
|
};
|
|
317
318
|
```
|
|
318
319
|
|
|
@@ -326,8 +327,11 @@ For example, after resolving the emitted filename:
|
|
|
326
327
|
|
|
327
328
|
```ts
|
|
328
329
|
await initZipPeek({
|
|
329
|
-
|
|
330
|
-
|
|
330
|
+
workerUrl: new URL(
|
|
331
|
+
`/assets/${zipServiceWorkerFilename}`,
|
|
332
|
+
window.location.origin,
|
|
333
|
+
).href,
|
|
334
|
+
scopeUrl: new URL("/", window.location.origin).href,
|
|
331
335
|
});
|
|
332
336
|
```
|
|
333
337
|
|
|
@@ -342,19 +346,19 @@ zipServiceWorker.a1b2c3d4.js
|
|
|
342
346
|
In development, it is usually simpler to copy the worker without a hash:
|
|
343
347
|
|
|
344
348
|
```js
|
|
345
|
-
const CopyPlugin = require(
|
|
349
|
+
const CopyPlugin = require("copy-webpack-plugin");
|
|
346
350
|
|
|
347
351
|
module.exports = {
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
352
|
+
plugins: [
|
|
353
|
+
new CopyPlugin({
|
|
354
|
+
patterns: [
|
|
355
|
+
{
|
|
356
|
+
from: require.resolve("zip-peek/zipServiceWorker.js"),
|
|
357
|
+
to: "zipServiceWorker.js",
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
}),
|
|
361
|
+
],
|
|
358
362
|
};
|
|
359
363
|
```
|
|
360
364
|
|
|
@@ -362,8 +366,8 @@ Then use:
|
|
|
362
366
|
|
|
363
367
|
```ts
|
|
364
368
|
await initZipPeek({
|
|
365
|
-
|
|
366
|
-
|
|
369
|
+
workerUrl: new URL("/zipServiceWorker.js", window.location.origin).href,
|
|
370
|
+
scopeUrl: new URL("/", window.location.origin).href,
|
|
367
371
|
});
|
|
368
372
|
```
|
|
369
373
|
|
|
@@ -386,6 +390,10 @@ Extracted assets expire after two hours by default. Set `assetCacheTtlMs` to cus
|
|
|
386
390
|
|
|
387
391
|
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.
|
|
388
392
|
|
|
393
|
+
## Internal Flow
|
|
394
|
+
|
|
395
|
+
For a detailed explanation of how the service worker intercepts requests, parses ZIP files, and handles caching, see the [Zip Service Worker Flow documentation](./docs/ZIP_SERVICE_WORKER_FLOW.md).
|
|
396
|
+
|
|
389
397
|
## Limitations
|
|
390
398
|
|
|
391
399
|
- The ZIP URL must end with `.zip`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,24 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
assetCacheTtlMs?: number;
|
|
5
|
-
logPrefix?: string;
|
|
6
|
-
};
|
|
7
|
-
export type ZipCacheClearingStrategy = 'keep-all' | 'keep-allowed-urls' | 'clear-all';
|
|
8
|
-
export type InitZipPeekOptions = {
|
|
9
|
-
workerUrl: string;
|
|
10
|
-
scopeUrl: string;
|
|
11
|
-
reloadOnFirstInstall?: boolean;
|
|
12
|
-
cacheClearingStrategy?: ZipCacheClearingStrategy;
|
|
13
|
-
allowedZipUrls?: string[];
|
|
14
|
-
zipAssetCacheName?: string;
|
|
15
|
-
assetCacheTtlMs?: number;
|
|
16
|
-
logPrefix?: string;
|
|
17
|
-
};
|
|
18
|
-
export type InitZipPeekResult = {
|
|
19
|
-
reloaded: boolean;
|
|
20
|
-
initialized: boolean;
|
|
21
|
-
};
|
|
1
|
+
import type { InitZipPeekOptions, InitZipPeekResult } from "./types";
|
|
2
|
+
import { isZipPackage } from "./zip-utils";
|
|
3
|
+
export type { InitZipPeekOptions, InitZipPeekResult, ZipCacheClearingStrategy, ZipWorkerConfig, } from "./types";
|
|
22
4
|
/**
|
|
23
5
|
* Registers the zip-peek service worker and configures lazy ZIP asset loading.
|
|
24
6
|
*
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@ function normalizeAllowedZipUrls(allowedZipUrls) {
|
|
|
10
10
|
return undefined;
|
|
11
11
|
}
|
|
12
12
|
if (allowedZipUrls.length === 0) {
|
|
13
|
-
throw new Error(
|
|
13
|
+
throw new Error("zip-peek: allowedZipUrls must contain at least one ZIP URL when provided.");
|
|
14
14
|
}
|
|
15
15
|
const normalized = new Set();
|
|
16
16
|
for (const zipUrl of allowedZipUrls) {
|
|
@@ -18,7 +18,7 @@ function normalizeAllowedZipUrls(allowedZipUrls) {
|
|
|
18
18
|
if (!(0, zip_utils_1.isZipPackage)(resolvedZipUrl)) {
|
|
19
19
|
throw new Error(`zip-peek: allowedZipUrls contains a non-ZIP URL: ${zipUrl}`);
|
|
20
20
|
}
|
|
21
|
-
normalized.add((0, zip_utils_1.
|
|
21
|
+
normalized.add((0, zip_utils_1.normalizeZipUrl)(resolvedZipUrl));
|
|
22
22
|
}
|
|
23
23
|
return Array.from(normalized);
|
|
24
24
|
}
|
|
@@ -29,19 +29,19 @@ function postWorkerMessage(message) {
|
|
|
29
29
|
}
|
|
30
30
|
return new Promise((resolve, reject) => {
|
|
31
31
|
const channel = new MessageChannel();
|
|
32
|
-
channel.port1.onmessage = event => {
|
|
32
|
+
channel.port1.onmessage = (event) => {
|
|
33
33
|
const response = event.data;
|
|
34
34
|
channel.port1.close();
|
|
35
35
|
if (!response || response.ok) {
|
|
36
36
|
resolve();
|
|
37
37
|
}
|
|
38
38
|
else {
|
|
39
|
-
reject(new Error(response.error ??
|
|
39
|
+
reject(new Error(response.error ?? "zip-peek: service worker message failed."));
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
42
|
channel.port1.onmessageerror = () => {
|
|
43
43
|
channel.port1.close();
|
|
44
|
-
reject(new Error(
|
|
44
|
+
reject(new Error("zip-peek: failed to receive service worker response."));
|
|
45
45
|
};
|
|
46
46
|
controller.postMessage(message, [channel.port2]);
|
|
47
47
|
});
|
|
@@ -56,12 +56,14 @@ function postWorkerMessage(message) {
|
|
|
56
56
|
* `cacheClearingStrategy` to keep existing cache entries, keep only allowed
|
|
57
57
|
* ZIP URLs, or clear all zip-peek cache entries before use.
|
|
58
58
|
*/
|
|
59
|
-
async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, cacheClearingStrategy =
|
|
59
|
+
async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, cacheClearingStrategy = "keep-all", allowedZipUrls, zipAssetCacheName, assetCacheTtlMs, logPrefix, }) {
|
|
60
60
|
const normalizedAllowedZipUrls = normalizeAllowedZipUrls(allowedZipUrls);
|
|
61
|
-
if (cacheClearingStrategy ===
|
|
61
|
+
if (cacheClearingStrategy === "keep-allowed-urls" &&
|
|
62
|
+
!normalizedAllowedZipUrls) {
|
|
62
63
|
throw new Error('zip-peek: cacheClearingStrategy "keep-allowed-urls" requires a non-empty allowedZipUrls allow-list.');
|
|
63
64
|
}
|
|
64
|
-
if (!(
|
|
65
|
+
if (!("serviceWorker" in navigator)) {
|
|
66
|
+
console.warn("zip-peek: Service Worker API not supported.");
|
|
65
67
|
return { reloaded: false, initialized: false };
|
|
66
68
|
}
|
|
67
69
|
const reloaded = await (0, registerZipServiceWorker_1.ensureZipServiceWorkerRegistered)({
|
|
@@ -73,7 +75,7 @@ async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, c
|
|
|
73
75
|
return { reloaded: true, initialized: false };
|
|
74
76
|
}
|
|
75
77
|
await postWorkerMessage({
|
|
76
|
-
type:
|
|
78
|
+
type: "ZIP_SW_CONFIG",
|
|
77
79
|
config: {
|
|
78
80
|
zipAssetCacheName,
|
|
79
81
|
assetCacheTtlMs,
|
|
@@ -81,12 +83,12 @@ async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, c
|
|
|
81
83
|
allowedZipUrls: normalizedAllowedZipUrls ?? null,
|
|
82
84
|
},
|
|
83
85
|
});
|
|
84
|
-
if (cacheClearingStrategy ===
|
|
86
|
+
if (cacheClearingStrategy === "clear-all") {
|
|
85
87
|
await postWorkerMessage({
|
|
86
|
-
type:
|
|
88
|
+
type: "CLEAR_ALL_ZIP_ASSETS",
|
|
87
89
|
});
|
|
88
90
|
await postWorkerMessage({
|
|
89
|
-
type:
|
|
91
|
+
type: "ZIP_SW_CONFIG",
|
|
90
92
|
config: {
|
|
91
93
|
zipAssetCacheName,
|
|
92
94
|
assetCacheTtlMs,
|
|
@@ -95,15 +97,16 @@ async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, c
|
|
|
95
97
|
},
|
|
96
98
|
});
|
|
97
99
|
}
|
|
98
|
-
else if (cacheClearingStrategy ===
|
|
100
|
+
else if (cacheClearingStrategy === "keep-allowed-urls" &&
|
|
101
|
+
normalizedAllowedZipUrls) {
|
|
99
102
|
await postWorkerMessage({
|
|
100
|
-
type:
|
|
103
|
+
type: "CLEAR_ZIP_ASSETS_EXCEPT_ALLOWED",
|
|
101
104
|
allowedZipUrls: normalizedAllowedZipUrls,
|
|
102
105
|
});
|
|
103
106
|
}
|
|
104
107
|
if (normalizedAllowedZipUrls) {
|
|
105
108
|
await postWorkerMessage({
|
|
106
|
-
type:
|
|
109
|
+
type: "PRELOAD_ZIP_MANIFESTS",
|
|
107
110
|
allowedZipUrls: normalizedAllowedZipUrls,
|
|
108
111
|
});
|
|
109
112
|
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export type ZipWorkerConfig = {
|
|
2
|
+
zipAssetCacheName?: string;
|
|
3
|
+
assetCacheTtlMs?: number;
|
|
4
|
+
logPrefix?: string;
|
|
5
|
+
};
|
|
6
|
+
export type ZipCacheClearingStrategy = 'keep-all' | 'keep-allowed-urls' | 'clear-all';
|
|
7
|
+
export type InitZipPeekOptions = {
|
|
8
|
+
workerUrl: string;
|
|
9
|
+
scopeUrl: string;
|
|
10
|
+
reloadOnFirstInstall?: boolean;
|
|
11
|
+
cacheClearingStrategy?: ZipCacheClearingStrategy;
|
|
12
|
+
allowedZipUrls?: string[];
|
|
13
|
+
zipAssetCacheName?: string;
|
|
14
|
+
assetCacheTtlMs?: number;
|
|
15
|
+
logPrefix?: string;
|
|
16
|
+
};
|
|
17
|
+
export type InitZipPeekResult = {
|
|
18
|
+
reloaded: boolean;
|
|
19
|
+
initialized: boolean;
|
|
20
|
+
};
|
|
21
|
+
export type WorkerMessageResponse = {
|
|
22
|
+
ok: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
};
|
package/dist/types.js
ADDED
package/dist/zip-utils.d.ts
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
/** True when the URL points at a `.zip` package (path ends with `.zip`). */
|
|
2
2
|
export declare function isZipPackage(url: string): boolean;
|
|
3
|
+
export type NormalizeZipUrlOptions = {
|
|
4
|
+
/** Remove cache-buster `t` query param (default: true). */
|
|
5
|
+
stripCacheBuster?: boolean;
|
|
6
|
+
/** Drop fragment `#...` (default: true). */
|
|
7
|
+
stripHash?: boolean;
|
|
8
|
+
};
|
|
3
9
|
/**
|
|
4
|
-
*
|
|
10
|
+
* Normalize a ZIP package URL for cache keys, allow-lists, and manifests.
|
|
5
11
|
* Avoids `URL` / `URLSearchParams` so SigV4 values (e.g. `%2F` in credentials)
|
|
6
12
|
* are not re-encoded.
|
|
7
13
|
*/
|
|
8
|
-
export declare function
|
|
9
|
-
/** Same key the service worker uses for manifests / purge (presigned query kept). */
|
|
10
|
-
export declare const normalizeZipUrlForSw: typeof stripTParam;
|
|
14
|
+
export declare function normalizeZipUrl(urlString: string, options?: NormalizeZipUrlOptions): string;
|
|
11
15
|
/** Align zip entry names with URL paths under `*.zip/…`. */
|
|
12
16
|
export declare function normalizeZipEntryPath(name: string): string;
|
|
13
17
|
export type ParsedZipAssetRequest = {
|
|
@@ -24,5 +28,5 @@ export type ParsedZipAssetRequest = {
|
|
|
24
28
|
* Returns `null` for bare `.zip` GETs (e.g. manifest bootstrap).
|
|
25
29
|
*/
|
|
26
30
|
export declare function parseZipAssetRequest(urlString: string): ParsedZipAssetRequest | null;
|
|
27
|
-
/** Cache key for a zip asset (decoded manifest key + zip URL
|
|
31
|
+
/** Cache key for a zip asset (decoded manifest key + normalized zip URL). */
|
|
28
32
|
export declare function canonicalZipAssetRequestUrl(zipUrl: string, manifestKey: string): string;
|
package/dist/zip-utils.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.normalizeZipUrlForSw = void 0;
|
|
4
3
|
exports.isZipPackage = isZipPackage;
|
|
5
|
-
exports.
|
|
4
|
+
exports.normalizeZipUrl = normalizeZipUrl;
|
|
6
5
|
exports.normalizeZipEntryPath = normalizeZipEntryPath;
|
|
7
6
|
exports.parseZipAssetRequest = parseZipAssetRequest;
|
|
8
7
|
exports.canonicalZipAssetRequestUrl = canonicalZipAssetRequestUrl;
|
|
@@ -10,41 +9,46 @@ exports.canonicalZipAssetRequestUrl = canonicalZipAssetRequestUrl;
|
|
|
10
9
|
function isZipPackage(url) {
|
|
11
10
|
try {
|
|
12
11
|
const u = new URL(url);
|
|
13
|
-
return u.pathname.endsWith(
|
|
12
|
+
return u.pathname.endsWith(".zip");
|
|
14
13
|
}
|
|
15
14
|
catch {
|
|
16
|
-
return url.split(
|
|
15
|
+
return url.split("?")[0].endsWith(".zip");
|
|
17
16
|
}
|
|
18
17
|
}
|
|
19
18
|
/**
|
|
20
|
-
*
|
|
19
|
+
* Normalize a ZIP package URL for cache keys, allow-lists, and manifests.
|
|
21
20
|
* Avoids `URL` / `URLSearchParams` so SigV4 values (e.g. `%2F` in credentials)
|
|
22
21
|
* are not re-encoded.
|
|
23
22
|
*/
|
|
24
|
-
function
|
|
25
|
-
const
|
|
23
|
+
function normalizeZipUrl(urlString, options = {}) {
|
|
24
|
+
const { stripCacheBuster = true, stripHash = true } = options;
|
|
25
|
+
const withoutHash = (url) => (stripHash ? url.split("#")[0] : url);
|
|
26
|
+
if (!stripCacheBuster) {
|
|
27
|
+
return withoutHash(urlString);
|
|
28
|
+
}
|
|
29
|
+
const qIdx = urlString.indexOf("?");
|
|
26
30
|
if (qIdx === -1)
|
|
27
|
-
return urlString
|
|
28
|
-
const hashIdx = urlString.indexOf(
|
|
31
|
+
return withoutHash(urlString);
|
|
32
|
+
const hashIdx = urlString.indexOf("#", qIdx);
|
|
29
33
|
const prefix = urlString.substring(0, qIdx);
|
|
30
|
-
const querySection = hashIdx === -1
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
const querySection = hashIdx === -1
|
|
35
|
+
? urlString.substring(qIdx + 1)
|
|
36
|
+
: urlString.substring(qIdx + 1, hashIdx);
|
|
37
|
+
const hashSection = stripHash || hashIdx === -1 ? "" : urlString.substring(hashIdx);
|
|
38
|
+
const filtered = querySection.split("&").filter((p) => {
|
|
39
|
+
const eqIdx = p.indexOf("=");
|
|
34
40
|
const k = eqIdx === -1 ? p : p.substring(0, eqIdx);
|
|
35
|
-
return k !==
|
|
41
|
+
return k !== "t";
|
|
36
42
|
});
|
|
37
43
|
if (filtered.length === 0)
|
|
38
44
|
return prefix + hashSection;
|
|
39
|
-
return prefix +
|
|
45
|
+
return prefix + "?" + filtered.join("&") + hashSection;
|
|
40
46
|
}
|
|
41
|
-
/** Same key the service worker uses for manifests / purge (presigned query kept). */
|
|
42
|
-
exports.normalizeZipUrlForSw = stripTParam;
|
|
43
47
|
/** Align zip entry names with URL paths under `*.zip/…`. */
|
|
44
48
|
function normalizeZipEntryPath(name) {
|
|
45
|
-
let s = name.replace(/\\/g,
|
|
46
|
-
while (s.startsWith(
|
|
47
|
-
s = s.slice(1);
|
|
49
|
+
let s = name.replace(/\\/g, "/"); // Replace backslashes with forward slashes.
|
|
50
|
+
while (s.startsWith("/"))
|
|
51
|
+
s = s.slice(1); // Remove leading slashes.
|
|
48
52
|
return s;
|
|
49
53
|
}
|
|
50
54
|
function safeDecodeSegment(s) {
|
|
@@ -68,33 +72,33 @@ function parseZipAssetRequest(urlString) {
|
|
|
68
72
|
const match = /\.zip([/?])/.exec(urlString);
|
|
69
73
|
if (!match)
|
|
70
74
|
return null;
|
|
71
|
-
const sepIdx = match.index +
|
|
75
|
+
const sepIdx = match.index + ".zip".length;
|
|
72
76
|
const separator = match[1];
|
|
73
77
|
const zipUrlBase = urlString.substring(0, sepIdx);
|
|
74
|
-
if (separator ===
|
|
78
|
+
if (separator === "/") {
|
|
75
79
|
const rest = urlString.substring(sepIdx + 1);
|
|
76
|
-
const qIdx = rest.indexOf(
|
|
80
|
+
const qIdx = rest.indexOf("?");
|
|
77
81
|
const rawInternal = qIdx === -1 ? rest : rest.substring(0, qIdx);
|
|
78
|
-
if (rawInternal ===
|
|
82
|
+
if (rawInternal === "")
|
|
79
83
|
return null;
|
|
80
84
|
return { zipUrl: zipUrlBase, internalPath: safeDecodeSegment(rawInternal) };
|
|
81
85
|
}
|
|
82
86
|
const afterZip = urlString.substring(sepIdx);
|
|
83
|
-
const slashIdx = afterZip.indexOf(
|
|
87
|
+
const slashIdx = afterZip.indexOf("/");
|
|
84
88
|
if (slashIdx === -1)
|
|
85
89
|
return null;
|
|
86
90
|
const zipQueryPart = afterZip.substring(0, slashIdx);
|
|
87
91
|
const afterSlash = afterZip.substring(slashIdx + 1);
|
|
88
|
-
const qIdx = afterSlash.indexOf(
|
|
92
|
+
const qIdx = afterSlash.indexOf("?");
|
|
89
93
|
const rawInternal = qIdx === -1 ? afterSlash : afterSlash.substring(0, qIdx);
|
|
90
|
-
if (rawInternal ===
|
|
94
|
+
if (rawInternal === "")
|
|
91
95
|
return null;
|
|
92
96
|
return {
|
|
93
97
|
zipUrl: zipUrlBase + zipQueryPart,
|
|
94
98
|
internalPath: safeDecodeSegment(rawInternal),
|
|
95
99
|
};
|
|
96
100
|
}
|
|
97
|
-
/** Cache key for a zip asset (decoded manifest key + zip URL
|
|
101
|
+
/** Cache key for a zip asset (decoded manifest key + normalized zip URL). */
|
|
98
102
|
function canonicalZipAssetRequestUrl(zipUrl, manifestKey) {
|
|
99
|
-
return zipUrl +
|
|
103
|
+
return zipUrl + "/" + encodeURIComponent(manifestKey);
|
|
100
104
|
}
|