zip-peek 0.1.0 → 0.2.1
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 +90 -77
- package/dist/index.d.ts +14 -18
- package/dist/index.js +94 -11
- package/dist/types.d.ts +24 -0
- package/dist/types.js +2 -0
- package/dist/zip-utils.js +24 -22
- package/dist/zipServiceWorker.js +1 -1
- package/dist/zipServiceWorker.js.map +1 -1
- package/package.json +2 -1
- package/dist/ZipStreamManager.d.ts +0 -19
- package/dist/ZipStreamManager.js +0 -181
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
|
|
|
@@ -39,16 +39,14 @@ The package provides two pieces:
|
|
|
39
39
|
|
|
40
40
|
At runtime, the package:
|
|
41
41
|
|
|
42
|
-
1.
|
|
43
|
-
2.
|
|
44
|
-
3.
|
|
45
|
-
4.
|
|
46
|
-
5.
|
|
47
|
-
6.
|
|
48
|
-
7.
|
|
49
|
-
8.
|
|
50
|
-
9. Returns the extracted file bytes to the browser request as a normal `Response`.
|
|
51
|
-
10. Caches extracted assets in the browser Cache API.
|
|
42
|
+
1. Registers the ZIP service worker with the configured `workerUrl` and `scopeUrl`.
|
|
43
|
+
2. Reloads once after first service worker install when needed, so the page becomes controlled.
|
|
44
|
+
3. Intercepts matching `.zip/...` asset requests within the service worker scope.
|
|
45
|
+
4. Loads and caches the ZIP central directory on first use, or during init when `allowedZipUrls` is provided.
|
|
46
|
+
5. Fetches only the byte range needed for the requested file.
|
|
47
|
+
6. Inflates deflated files using `fflate`.
|
|
48
|
+
7. Returns the extracted file bytes to the browser request as a normal `Response`.
|
|
49
|
+
8. Caches extracted assets in the browser Cache API.
|
|
52
50
|
|
|
53
51
|
## Installation
|
|
54
52
|
|
|
@@ -65,23 +63,22 @@ npm install zip-peek
|
|
|
65
63
|
## Basic Usage
|
|
66
64
|
|
|
67
65
|
```ts
|
|
68
|
-
import { initZipPeek } from
|
|
66
|
+
import { initZipPeek } from "zip-peek";
|
|
69
67
|
|
|
70
68
|
const zipPeekInit = await initZipPeek({
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
scopeUrl: new URL('/', window.location.origin).href,
|
|
69
|
+
workerUrl: new URL("/zipServiceWorker.js", window.location.origin).href,
|
|
70
|
+
scopeUrl: new URL("/", window.location.origin).href,
|
|
74
71
|
});
|
|
75
72
|
|
|
76
73
|
if (zipPeekInit.reloaded) {
|
|
77
|
-
|
|
74
|
+
return;
|
|
78
75
|
}
|
|
79
76
|
```
|
|
80
77
|
|
|
81
78
|
After initialization, your app can continue building asset URLs under the ZIP URL:
|
|
82
79
|
|
|
83
80
|
```ts
|
|
84
|
-
const zipUrl =
|
|
81
|
+
const zipUrl = "https://cdn.example.com/packages/zipfile.zip";
|
|
85
82
|
const imgUrl = `${zipUrl}/slides/slide-1/image.png`;
|
|
86
83
|
|
|
87
84
|
// This request will work normally and zip-peek will do the magic in the background:
|
|
@@ -96,39 +93,30 @@ The browser requests that URL normally. The service worker intercepts it and ser
|
|
|
96
93
|
|
|
97
94
|
```ts
|
|
98
95
|
type InitZipPeekOptions = {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
96
|
+
workerUrl: string;
|
|
97
|
+
scopeUrl: string;
|
|
98
|
+
reloadOnFirstInstall?: boolean;
|
|
99
|
+
cacheClearingStrategy?: ZipCacheClearingStrategy;
|
|
100
|
+
allowedZipUrls?: string[];
|
|
101
|
+
zipAssetCacheName?: string;
|
|
102
|
+
assetCacheTtlMs?: number;
|
|
103
|
+
logPrefix?: string;
|
|
107
104
|
};
|
|
105
|
+
|
|
106
|
+
type ZipCacheClearingStrategy = "keep-all" | "keep-allowed-urls" | "clear-all";
|
|
108
107
|
```
|
|
109
108
|
|
|
110
109
|
Returns:
|
|
111
110
|
|
|
112
111
|
```ts
|
|
113
112
|
type InitZipPeekResult = {
|
|
114
|
-
|
|
115
|
-
|
|
113
|
+
reloaded: boolean;
|
|
114
|
+
initialized: boolean;
|
|
116
115
|
};
|
|
117
116
|
```
|
|
118
117
|
|
|
119
118
|
### Options
|
|
120
119
|
|
|
121
|
-
`zipUrl`
|
|
122
|
-
|
|
123
|
-
The URL of the ZIP package. This can be absolute or relative, but it must point to a path ending in `.zip`. It can also be a presigned url.
|
|
124
|
-
|
|
125
|
-
Example:
|
|
126
|
-
|
|
127
|
-
```ts
|
|
128
|
-
zipUrl: 'https://cdn.example.com/packages/zipfile.zip'
|
|
129
|
-
zipUrl: 'https://d3jfe.cloudfront.net/somedir/zipfile.zip?Expires=1798761599&Signature=K2CJW4Z7B3DCM4&Key-Pair-Id=K2CJW4Z7B3DCM4'
|
|
130
|
-
```
|
|
131
|
-
|
|
132
120
|
`workerUrl`
|
|
133
121
|
|
|
134
122
|
The URL where the browser can download the service worker JavaScript file. The service worker file must be served from the same origin as the page.
|
|
@@ -136,10 +124,10 @@ The URL where the browser can download the service worker JavaScript file. The s
|
|
|
136
124
|
Example:
|
|
137
125
|
|
|
138
126
|
```ts
|
|
139
|
-
workerUrl: new URL(
|
|
127
|
+
workerUrl: new URL("./zipServiceWorker.js", window.location.href).href;
|
|
140
128
|
```
|
|
141
129
|
|
|
142
|
-
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:
|
|
143
131
|
|
|
144
132
|
```text
|
|
145
133
|
zipServiceWorker.a1b2c3d4.js
|
|
@@ -148,12 +136,13 @@ zipServiceWorker.a1b2c3d4.js
|
|
|
148
136
|
`scopeUrl`
|
|
149
137
|
|
|
150
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.
|
|
151
140
|
|
|
152
141
|
Examples:
|
|
153
142
|
|
|
154
143
|
```ts
|
|
155
|
-
scopeUrl: new URL(
|
|
156
|
-
scopeUrl: new URL(
|
|
144
|
+
scopeUrl: new URL("/", window.location.origin).href;
|
|
145
|
+
scopeUrl: new URL("/app/", window.location.origin).href;
|
|
157
146
|
```
|
|
158
147
|
|
|
159
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.
|
|
@@ -162,9 +151,28 @@ Choose the smallest scope that includes the pages and asset requests you want th
|
|
|
162
151
|
|
|
163
152
|
Defaults to `true`. When a service worker is installed for the first time, the current page may not yet be controlled by it. With this option enabled, the package reloads once and returns `{ reloaded: true, initialized: false }`.
|
|
164
153
|
|
|
165
|
-
`
|
|
154
|
+
`cacheClearingStrategy`
|
|
155
|
+
|
|
156
|
+
Defaults to `'keep-all'`. Controls which zip-peek cache entries are cleared during initialization.
|
|
157
|
+
|
|
158
|
+
- `'keep-all'`: do not clear cached ZIP manifests or extracted assets.
|
|
159
|
+
- `'keep-allowed-urls'`: requires non-empty `allowedZipUrls`; clears cached ZIP data for URLs outside the allow-list.
|
|
160
|
+
- `'clear-all'`: clears all zip-peek cached manifests and extracted assets for a fresh start.
|
|
166
161
|
|
|
167
|
-
|
|
162
|
+
`allowedZipUrls`
|
|
163
|
+
|
|
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.
|
|
165
|
+
|
|
166
|
+
The allow-list also acts as a warmup list: zip-peek loads and caches each allowed ZIP manifest during initialization.
|
|
167
|
+
|
|
168
|
+
Examples:
|
|
169
|
+
|
|
170
|
+
```ts
|
|
171
|
+
allowedZipUrls: ["https://cdn.example.com/packages/zipfile.zip"];
|
|
172
|
+
allowedZipUrls: [
|
|
173
|
+
"https://d3jfe.cloudfront.net/somedir/zipfile.zip?Expires=1798761599&Signature=K2CJW4Z7B3DCM4&Key-Pair-Id=K2CJW4Z7B3DCM4",
|
|
174
|
+
];
|
|
175
|
+
```
|
|
168
176
|
|
|
169
177
|
`zipAssetCacheName`
|
|
170
178
|
|
|
@@ -183,7 +191,7 @@ Controls how long extracted assets remain valid in the Cache API.
|
|
|
183
191
|
Default:
|
|
184
192
|
|
|
185
193
|
```ts
|
|
186
|
-
2 * 60 * 60 * 1000
|
|
194
|
+
2 * 60 * 60 * 1000; // 2 hours
|
|
187
195
|
```
|
|
188
196
|
|
|
189
197
|
`logPrefix`
|
|
@@ -261,8 +269,8 @@ The registered scope must be allowed by both:
|
|
|
261
269
|
Example:
|
|
262
270
|
|
|
263
271
|
```ts
|
|
264
|
-
navigator.serviceWorker.register(
|
|
265
|
-
|
|
272
|
+
navigator.serviceWorker.register("/app/zipServiceWorker.a1b2c3d4.js", {
|
|
273
|
+
scope: "/app/",
|
|
266
274
|
});
|
|
267
275
|
```
|
|
268
276
|
|
|
@@ -293,19 +301,19 @@ Your application must copy this file into its public build output. The browser r
|
|
|
293
301
|
When your app emits hashed assets, copy the worker with a content hash and pass the final worker URL to `initZipPeek()`.
|
|
294
302
|
|
|
295
303
|
```js
|
|
296
|
-
const CopyPlugin = require(
|
|
304
|
+
const CopyPlugin = require("copy-webpack-plugin");
|
|
297
305
|
|
|
298
306
|
module.exports = {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
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
|
+
],
|
|
309
317
|
};
|
|
310
318
|
```
|
|
311
319
|
|
|
@@ -319,9 +327,11 @@ For example, after resolving the emitted filename:
|
|
|
319
327
|
|
|
320
328
|
```ts
|
|
321
329
|
await initZipPeek({
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
330
|
+
workerUrl: new URL(
|
|
331
|
+
`/assets/${zipServiceWorkerFilename}`,
|
|
332
|
+
window.location.origin,
|
|
333
|
+
).href,
|
|
334
|
+
scopeUrl: new URL("/", window.location.origin).href,
|
|
325
335
|
});
|
|
326
336
|
```
|
|
327
337
|
|
|
@@ -336,19 +346,19 @@ zipServiceWorker.a1b2c3d4.js
|
|
|
336
346
|
In development, it is usually simpler to copy the worker without a hash:
|
|
337
347
|
|
|
338
348
|
```js
|
|
339
|
-
const CopyPlugin = require(
|
|
349
|
+
const CopyPlugin = require("copy-webpack-plugin");
|
|
340
350
|
|
|
341
351
|
module.exports = {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
+
plugins: [
|
|
353
|
+
new CopyPlugin({
|
|
354
|
+
patterns: [
|
|
355
|
+
{
|
|
356
|
+
from: require.resolve("zip-peek/zipServiceWorker.js"),
|
|
357
|
+
to: "zipServiceWorker.js",
|
|
358
|
+
},
|
|
359
|
+
],
|
|
360
|
+
}),
|
|
361
|
+
],
|
|
352
362
|
};
|
|
353
363
|
```
|
|
354
364
|
|
|
@@ -356,9 +366,8 @@ Then use:
|
|
|
356
366
|
|
|
357
367
|
```ts
|
|
358
368
|
await initZipPeek({
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
scopeUrl: new URL('/', window.location.origin).href,
|
|
369
|
+
workerUrl: new URL("/zipServiceWorker.js", window.location.origin).href,
|
|
370
|
+
scopeUrl: new URL("/", window.location.origin).href,
|
|
362
371
|
});
|
|
363
372
|
```
|
|
364
373
|
|
|
@@ -379,7 +388,11 @@ zip-cache-v1
|
|
|
379
388
|
|
|
380
389
|
Extracted assets expire after two hours by default. Set `assetCacheTtlMs` to customize this.
|
|
381
390
|
|
|
382
|
-
|
|
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.
|
|
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).
|
|
383
396
|
|
|
384
397
|
## Limitations
|
|
385
398
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { isZipPackage } from
|
|
3
|
-
export type InitZipPeekOptions
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
initialized: boolean;
|
|
16
|
-
};
|
|
17
|
-
export declare function initZipPeek({ zipUrl, workerUrl, scopeUrl, reloadOnFirstInstall, purgeOtherZipAssets, zipAssetCacheName, assetCacheTtlMs, logPrefix, }: InitZipPeekOptions): Promise<InitZipPeekResult>;
|
|
1
|
+
import type { InitZipPeekOptions, InitZipPeekResult } from "./types";
|
|
2
|
+
import { isZipPackage } from "./zip-utils";
|
|
3
|
+
export type { InitZipPeekOptions, InitZipPeekResult, ZipCacheClearingStrategy, ZipWorkerConfig, } from "./types";
|
|
4
|
+
/**
|
|
5
|
+
* Registers the zip-peek service worker and configures lazy ZIP asset loading.
|
|
6
|
+
*
|
|
7
|
+
* By default, zip-peek serves any matching `.zip/...` request within the
|
|
8
|
+
* service worker scope by loading the ZIP manifest on first use. Pass
|
|
9
|
+
* `allowedZipUrls` to restrict serving to a known, non-empty set of ZIP
|
|
10
|
+
* package URLs and warm their manifests during initialization. Use
|
|
11
|
+
* `cacheClearingStrategy` to keep existing cache entries, keep only allowed
|
|
12
|
+
* ZIP URLs, or clear all zip-peek cache entries before use.
|
|
13
|
+
*/
|
|
14
|
+
export declare function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall, cacheClearingStrategy, allowedZipUrls, zipAssetCacheName, assetCacheTtlMs, logPrefix, }: InitZipPeekOptions): Promise<InitZipPeekResult>;
|
|
18
15
|
export { isZipPackage };
|
|
19
|
-
export type { ZipWorkerConfig };
|
package/dist/index.js
CHANGED
|
@@ -3,11 +3,67 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.isZipPackage = void 0;
|
|
4
4
|
exports.initZipPeek = initZipPeek;
|
|
5
5
|
const registerZipServiceWorker_1 = require("./registerZipServiceWorker");
|
|
6
|
-
const ZipStreamManager_1 = require("./ZipStreamManager");
|
|
7
6
|
const zip_utils_1 = require("./zip-utils");
|
|
8
7
|
Object.defineProperty(exports, "isZipPackage", { enumerable: true, get: function () { return zip_utils_1.isZipPackage; } });
|
|
9
|
-
|
|
10
|
-
if (
|
|
8
|
+
function normalizeAllowedZipUrls(allowedZipUrls) {
|
|
9
|
+
if (allowedZipUrls === undefined) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
if (allowedZipUrls.length === 0) {
|
|
13
|
+
throw new Error("zip-peek: allowedZipUrls must contain at least one ZIP URL when provided.");
|
|
14
|
+
}
|
|
15
|
+
const normalized = new Set();
|
|
16
|
+
for (const zipUrl of allowedZipUrls) {
|
|
17
|
+
const resolvedZipUrl = new URL(zipUrl, window.location.href).href;
|
|
18
|
+
if (!(0, zip_utils_1.isZipPackage)(resolvedZipUrl)) {
|
|
19
|
+
throw new Error(`zip-peek: allowedZipUrls contains a non-ZIP URL: ${zipUrl}`);
|
|
20
|
+
}
|
|
21
|
+
normalized.add((0, zip_utils_1.normalizeZipUrlForSw)(resolvedZipUrl));
|
|
22
|
+
}
|
|
23
|
+
return Array.from(normalized);
|
|
24
|
+
}
|
|
25
|
+
function postWorkerMessage(message) {
|
|
26
|
+
const controller = navigator.serviceWorker.controller;
|
|
27
|
+
if (!controller) {
|
|
28
|
+
return Promise.resolve();
|
|
29
|
+
}
|
|
30
|
+
return new Promise((resolve, reject) => {
|
|
31
|
+
const channel = new MessageChannel();
|
|
32
|
+
channel.port1.onmessage = (event) => {
|
|
33
|
+
const response = event.data;
|
|
34
|
+
channel.port1.close();
|
|
35
|
+
if (!response || response.ok) {
|
|
36
|
+
resolve();
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
reject(new Error(response.error ?? "zip-peek: service worker message failed."));
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
channel.port1.onmessageerror = () => {
|
|
43
|
+
channel.port1.close();
|
|
44
|
+
reject(new Error("zip-peek: failed to receive service worker response."));
|
|
45
|
+
};
|
|
46
|
+
controller.postMessage(message, [channel.port2]);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Registers the zip-peek service worker and configures lazy ZIP asset loading.
|
|
51
|
+
*
|
|
52
|
+
* By default, zip-peek serves any matching `.zip/...` request within the
|
|
53
|
+
* service worker scope by loading the ZIP manifest on first use. Pass
|
|
54
|
+
* `allowedZipUrls` to restrict serving to a known, non-empty set of ZIP
|
|
55
|
+
* package URLs and warm their manifests during initialization. Use
|
|
56
|
+
* `cacheClearingStrategy` to keep existing cache entries, keep only allowed
|
|
57
|
+
* ZIP URLs, or clear all zip-peek cache entries before use.
|
|
58
|
+
*/
|
|
59
|
+
async function initZipPeek({ workerUrl, scopeUrl, reloadOnFirstInstall = true, cacheClearingStrategy = "keep-all", allowedZipUrls, zipAssetCacheName, assetCacheTtlMs, logPrefix, }) {
|
|
60
|
+
const normalizedAllowedZipUrls = normalizeAllowedZipUrls(allowedZipUrls);
|
|
61
|
+
if (cacheClearingStrategy === "keep-allowed-urls" &&
|
|
62
|
+
!normalizedAllowedZipUrls) {
|
|
63
|
+
throw new Error('zip-peek: cacheClearingStrategy "keep-allowed-urls" requires a non-empty allowedZipUrls allow-list.');
|
|
64
|
+
}
|
|
65
|
+
if (!("serviceWorker" in navigator)) {
|
|
66
|
+
console.warn("zip-peek: Service Worker API not supported.");
|
|
11
67
|
return { reloaded: false, initialized: false };
|
|
12
68
|
}
|
|
13
69
|
const reloaded = await (0, registerZipServiceWorker_1.ensureZipServiceWorkerRegistered)({
|
|
@@ -18,14 +74,41 @@ async function initZipPeek({ zipUrl, workerUrl, scopeUrl, reloadOnFirstInstall =
|
|
|
18
74
|
if (reloaded) {
|
|
19
75
|
return { reloaded: true, initialized: false };
|
|
20
76
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
77
|
+
await postWorkerMessage({
|
|
78
|
+
type: "ZIP_SW_CONFIG",
|
|
79
|
+
config: {
|
|
80
|
+
zipAssetCacheName,
|
|
81
|
+
assetCacheTtlMs,
|
|
82
|
+
logPrefix,
|
|
83
|
+
allowedZipUrls: normalizedAllowedZipUrls ?? null,
|
|
84
|
+
},
|
|
29
85
|
});
|
|
86
|
+
if (cacheClearingStrategy === "clear-all") {
|
|
87
|
+
await postWorkerMessage({
|
|
88
|
+
type: "CLEAR_ALL_ZIP_ASSETS",
|
|
89
|
+
});
|
|
90
|
+
await postWorkerMessage({
|
|
91
|
+
type: "ZIP_SW_CONFIG",
|
|
92
|
+
config: {
|
|
93
|
+
zipAssetCacheName,
|
|
94
|
+
assetCacheTtlMs,
|
|
95
|
+
logPrefix,
|
|
96
|
+
allowedZipUrls: normalizedAllowedZipUrls ?? null,
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
else if (cacheClearingStrategy === "keep-allowed-urls" &&
|
|
101
|
+
normalizedAllowedZipUrls) {
|
|
102
|
+
await postWorkerMessage({
|
|
103
|
+
type: "CLEAR_ZIP_ASSETS_EXCEPT_ALLOWED",
|
|
104
|
+
allowedZipUrls: normalizedAllowedZipUrls,
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
if (normalizedAllowedZipUrls) {
|
|
108
|
+
await postWorkerMessage({
|
|
109
|
+
type: "PRELOAD_ZIP_MANIFESTS",
|
|
110
|
+
allowedZipUrls: normalizedAllowedZipUrls,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
30
113
|
return { reloaded: false, initialized: true };
|
|
31
114
|
}
|
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.js
CHANGED
|
@@ -10,10 +10,10 @@ exports.canonicalZipAssetRequestUrl = canonicalZipAssetRequestUrl;
|
|
|
10
10
|
function isZipPackage(url) {
|
|
11
11
|
try {
|
|
12
12
|
const u = new URL(url);
|
|
13
|
-
return u.pathname.endsWith(
|
|
13
|
+
return u.pathname.endsWith(".zip");
|
|
14
14
|
}
|
|
15
15
|
catch {
|
|
16
|
-
return url.split(
|
|
16
|
+
return url.split("?")[0].endsWith(".zip");
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
/**
|
|
@@ -22,29 +22,31 @@ function isZipPackage(url) {
|
|
|
22
22
|
* are not re-encoded.
|
|
23
23
|
*/
|
|
24
24
|
function stripTParam(urlString) {
|
|
25
|
-
const qIdx = urlString.indexOf(
|
|
25
|
+
const qIdx = urlString.indexOf("?");
|
|
26
26
|
if (qIdx === -1)
|
|
27
|
-
return urlString.split(
|
|
28
|
-
const hashIdx = urlString.indexOf(
|
|
27
|
+
return urlString.split("#")[0];
|
|
28
|
+
const hashIdx = urlString.indexOf("#", qIdx);
|
|
29
29
|
const prefix = urlString.substring(0, qIdx);
|
|
30
|
-
const querySection = hashIdx === -1
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
const querySection = hashIdx === -1
|
|
31
|
+
? urlString.substring(qIdx + 1)
|
|
32
|
+
: urlString.substring(qIdx + 1, hashIdx);
|
|
33
|
+
const hashSection = hashIdx === -1 ? "" : urlString.substring(hashIdx);
|
|
34
|
+
const filtered = querySection.split("&").filter((p) => {
|
|
35
|
+
const eqIdx = p.indexOf("=");
|
|
34
36
|
const k = eqIdx === -1 ? p : p.substring(0, eqIdx);
|
|
35
|
-
return k !==
|
|
37
|
+
return k !== "t";
|
|
36
38
|
});
|
|
37
39
|
if (filtered.length === 0)
|
|
38
40
|
return prefix + hashSection;
|
|
39
|
-
return prefix +
|
|
41
|
+
return prefix + "?" + filtered.join("&") + hashSection;
|
|
40
42
|
}
|
|
41
43
|
/** Same key the service worker uses for manifests / purge (presigned query kept). */
|
|
42
44
|
exports.normalizeZipUrlForSw = stripTParam;
|
|
43
45
|
/** Align zip entry names with URL paths under `*.zip/…`. */
|
|
44
46
|
function normalizeZipEntryPath(name) {
|
|
45
|
-
let s = name.replace(/\\/g,
|
|
46
|
-
while (s.startsWith(
|
|
47
|
-
s = s.slice(1);
|
|
47
|
+
let s = name.replace(/\\/g, "/"); // Replace backslashes with forward slashes.
|
|
48
|
+
while (s.startsWith("/"))
|
|
49
|
+
s = s.slice(1); // Remove leading slashes.
|
|
48
50
|
return s;
|
|
49
51
|
}
|
|
50
52
|
function safeDecodeSegment(s) {
|
|
@@ -68,26 +70,26 @@ function parseZipAssetRequest(urlString) {
|
|
|
68
70
|
const match = /\.zip([/?])/.exec(urlString);
|
|
69
71
|
if (!match)
|
|
70
72
|
return null;
|
|
71
|
-
const sepIdx = match.index +
|
|
73
|
+
const sepIdx = match.index + ".zip".length;
|
|
72
74
|
const separator = match[1];
|
|
73
75
|
const zipUrlBase = urlString.substring(0, sepIdx);
|
|
74
|
-
if (separator ===
|
|
76
|
+
if (separator === "/") {
|
|
75
77
|
const rest = urlString.substring(sepIdx + 1);
|
|
76
|
-
const qIdx = rest.indexOf(
|
|
78
|
+
const qIdx = rest.indexOf("?");
|
|
77
79
|
const rawInternal = qIdx === -1 ? rest : rest.substring(0, qIdx);
|
|
78
|
-
if (rawInternal ===
|
|
80
|
+
if (rawInternal === "")
|
|
79
81
|
return null;
|
|
80
82
|
return { zipUrl: zipUrlBase, internalPath: safeDecodeSegment(rawInternal) };
|
|
81
83
|
}
|
|
82
84
|
const afterZip = urlString.substring(sepIdx);
|
|
83
|
-
const slashIdx = afterZip.indexOf(
|
|
85
|
+
const slashIdx = afterZip.indexOf("/");
|
|
84
86
|
if (slashIdx === -1)
|
|
85
87
|
return null;
|
|
86
88
|
const zipQueryPart = afterZip.substring(0, slashIdx);
|
|
87
89
|
const afterSlash = afterZip.substring(slashIdx + 1);
|
|
88
|
-
const qIdx = afterSlash.indexOf(
|
|
90
|
+
const qIdx = afterSlash.indexOf("?");
|
|
89
91
|
const rawInternal = qIdx === -1 ? afterSlash : afterSlash.substring(0, qIdx);
|
|
90
|
-
if (rawInternal ===
|
|
92
|
+
if (rawInternal === "")
|
|
91
93
|
return null;
|
|
92
94
|
return {
|
|
93
95
|
zipUrl: zipUrlBase + zipQueryPart,
|
|
@@ -96,5 +98,5 @@ function parseZipAssetRequest(urlString) {
|
|
|
96
98
|
}
|
|
97
99
|
/** Cache key for a zip asset (decoded manifest key + zip URL without `t`). */
|
|
98
100
|
function canonicalZipAssetRequestUrl(zipUrl, manifestKey) {
|
|
99
|
-
return zipUrl +
|
|
101
|
+
return zipUrl + "/" + encodeURIComponent(manifestKey);
|
|
100
102
|
}
|