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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zip-peek",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Service worker based ZIP asset loader for browser applications.",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"packageManager": "pnpm@10.23.0",
|
|
@@ -19,6 +19,7 @@
|
|
|
19
19
|
"scripts": {
|
|
20
20
|
"clean": "rm -rf dist",
|
|
21
21
|
"build": "pnpm run clean && tsc -p tsconfig.json && webpack --config webpack.worker.config.js",
|
|
22
|
+
"build:service-worker": "rm -rf dist && webpack --config webpack.worker.config.js",
|
|
22
23
|
"prepack": "pnpm run build"
|
|
23
24
|
},
|
|
24
25
|
"dependencies": {
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
export type ZipWorkerConfig = {
|
|
2
|
-
zipAssetCacheName?: string;
|
|
3
|
-
assetCacheTtlMs?: number;
|
|
4
|
-
logPrefix?: string;
|
|
5
|
-
};
|
|
6
|
-
export type ZipStreamManagerOptions = {
|
|
7
|
-
purgeOtherZipAssets?: boolean;
|
|
8
|
-
workerConfig?: ZipWorkerConfig;
|
|
9
|
-
};
|
|
10
|
-
export declare class ZipStreamManager {
|
|
11
|
-
/**
|
|
12
|
-
* Fetches the zip Central Directory and sends the manifest to the
|
|
13
|
-
* service worker, which then serves individual files via HTTP Range
|
|
14
|
-
* requests. The full zip is never downloaded.
|
|
15
|
-
*/
|
|
16
|
-
static init(zipUrl: string, options?: ZipStreamManagerOptions): Promise<void>;
|
|
17
|
-
private static postWorkerConfig;
|
|
18
|
-
private static fetchZipManifest;
|
|
19
|
-
}
|
package/dist/ZipStreamManager.js
DELETED
|
@@ -1,181 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ZipStreamManager = void 0;
|
|
4
|
-
const zip_utils_1 = require("./zip-utils");
|
|
5
|
-
class ZipStreamManager {
|
|
6
|
-
/**
|
|
7
|
-
* Fetches the zip Central Directory and sends the manifest to the
|
|
8
|
-
* service worker, which then serves individual files via HTTP Range
|
|
9
|
-
* requests. The full zip is never downloaded.
|
|
10
|
-
*/
|
|
11
|
-
static async init(zipUrl, options = {}) {
|
|
12
|
-
if (!(0, zip_utils_1.isZipPackage)(zipUrl)) {
|
|
13
|
-
return;
|
|
14
|
-
}
|
|
15
|
-
let resolvedZipUrl = '';
|
|
16
|
-
try {
|
|
17
|
-
resolvedZipUrl = new URL(zipUrl, window.location.origin).href;
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
resolvedZipUrl = new URL(zipUrl, window.location.href).href;
|
|
21
|
-
}
|
|
22
|
-
let resolveInit;
|
|
23
|
-
const initPromise = new Promise(res => {
|
|
24
|
-
resolveInit = res;
|
|
25
|
-
});
|
|
26
|
-
try {
|
|
27
|
-
void navigator.locks.request(`zip-init-${resolvedZipUrl}`, { ifAvailable: true }, async (lock) => {
|
|
28
|
-
if (!lock) {
|
|
29
|
-
console.log('[ZipStreamManager] Another frame is already processing this zip. Skipping.');
|
|
30
|
-
resolveInit?.();
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
this.postWorkerConfig(options.workerConfig);
|
|
34
|
-
if (options.purgeOtherZipAssets !== false && navigator.serviceWorker.controller) {
|
|
35
|
-
const swZipUrl = (0, zip_utils_1.normalizeZipUrlForSw)(resolvedZipUrl);
|
|
36
|
-
navigator.serviceWorker.controller.postMessage({
|
|
37
|
-
type: 'PURGE_OTHER_ZIP_ASSETS',
|
|
38
|
-
zipUrl: swZipUrl,
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
try {
|
|
42
|
-
console.log('[ZipStreamManager] Fetching zip manifest for range requests.');
|
|
43
|
-
await this.fetchZipManifest(resolvedZipUrl, zipUrl);
|
|
44
|
-
}
|
|
45
|
-
catch (err) {
|
|
46
|
-
console.error('[ZipStreamManager] Manifest fetch failed', err);
|
|
47
|
-
}
|
|
48
|
-
finally {
|
|
49
|
-
resolveInit?.();
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
catch (error) {
|
|
54
|
-
console.error('[ZipStreamManager] Failed to initialize zip stream manager', error);
|
|
55
|
-
resolveInit?.();
|
|
56
|
-
}
|
|
57
|
-
await initPromise;
|
|
58
|
-
}
|
|
59
|
-
static postWorkerConfig(workerConfig) {
|
|
60
|
-
if (!navigator.serviceWorker.controller || !workerConfig) {
|
|
61
|
-
return;
|
|
62
|
-
}
|
|
63
|
-
navigator.serviceWorker.controller.postMessage({
|
|
64
|
-
type: 'ZIP_SW_CONFIG',
|
|
65
|
-
config: workerConfig,
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
static async fetchZipManifest(resolvedZipUrl, zipUrl) {
|
|
69
|
-
const response = await fetch(zipUrl, {
|
|
70
|
-
headers: {
|
|
71
|
-
Range: 'bytes=-65536',
|
|
72
|
-
},
|
|
73
|
-
});
|
|
74
|
-
if (response.status !== 206) {
|
|
75
|
-
console.warn('[ZipStreamManager] Range requests not supported (status ' + response.status + ').');
|
|
76
|
-
return;
|
|
77
|
-
}
|
|
78
|
-
const buffer = await response.arrayBuffer();
|
|
79
|
-
const dataView = new DataView(buffer);
|
|
80
|
-
let eocdOffset = -1;
|
|
81
|
-
for (let i = buffer.byteLength - 22; i >= 0; i--) {
|
|
82
|
-
if (dataView.getUint32(i, true) === 0x06054b50) {
|
|
83
|
-
eocdOffset = i;
|
|
84
|
-
break;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
if (eocdOffset === -1) {
|
|
88
|
-
console.warn('[ZipStreamManager] EOCD not found in last 64KB');
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
const cdOffset = dataView.getUint32(eocdOffset + 16, true);
|
|
92
|
-
const cdSize = dataView.getUint32(eocdOffset + 12, true);
|
|
93
|
-
let cdBuffer = buffer;
|
|
94
|
-
let cdStartInBuf = -1;
|
|
95
|
-
const contentRange = response.headers.get('Content-Range');
|
|
96
|
-
let totalSize = 0;
|
|
97
|
-
if (contentRange) {
|
|
98
|
-
const match = contentRange.match(/\/(\d+)$/);
|
|
99
|
-
if (match)
|
|
100
|
-
totalSize = parseInt(match[1], 10);
|
|
101
|
-
}
|
|
102
|
-
if (totalSize > 0) {
|
|
103
|
-
const rangeStart = totalSize - buffer.byteLength;
|
|
104
|
-
if (cdOffset >= rangeStart) {
|
|
105
|
-
cdStartInBuf = cdOffset - rangeStart;
|
|
106
|
-
}
|
|
107
|
-
else {
|
|
108
|
-
const cdResponse = await fetch(zipUrl, {
|
|
109
|
-
headers: { Range: `bytes=${cdOffset}-${cdOffset + cdSize - 1}` },
|
|
110
|
-
});
|
|
111
|
-
if (cdResponse.status === 206) {
|
|
112
|
-
cdBuffer = await cdResponse.arrayBuffer();
|
|
113
|
-
cdStartInBuf = 0;
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
if (cdStartInBuf === -1)
|
|
124
|
-
return;
|
|
125
|
-
const files = [];
|
|
126
|
-
const cdDataView = new DataView(cdBuffer);
|
|
127
|
-
let offset = cdStartInBuf;
|
|
128
|
-
while (offset + 46 <= cdBuffer.byteLength) {
|
|
129
|
-
if (cdDataView.getUint32(offset, true) !== 0x02014b50) {
|
|
130
|
-
break;
|
|
131
|
-
}
|
|
132
|
-
const compressionMethod = cdDataView.getUint16(offset + 10, true);
|
|
133
|
-
const compressedSize = cdDataView.getUint32(offset + 20, true);
|
|
134
|
-
const uncompressedSize = cdDataView.getUint32(offset + 24, true);
|
|
135
|
-
const fileNameLength = cdDataView.getUint16(offset + 28, true);
|
|
136
|
-
const extraFieldLength = cdDataView.getUint16(offset + 30, true);
|
|
137
|
-
const fileCommentLength = cdDataView.getUint16(offset + 32, true);
|
|
138
|
-
const localHeaderOffset = cdDataView.getUint32(offset + 42, true);
|
|
139
|
-
if (offset + 46 + fileNameLength > cdBuffer.byteLength) {
|
|
140
|
-
break;
|
|
141
|
-
}
|
|
142
|
-
const fileNameBytes = new Uint8Array(cdBuffer, offset + 46, fileNameLength);
|
|
143
|
-
const rawName = new TextDecoder().decode(fileNameBytes);
|
|
144
|
-
const fileName = (0, zip_utils_1.normalizeZipEntryPath)(rawName);
|
|
145
|
-
if (fileName !== '') {
|
|
146
|
-
files.push({
|
|
147
|
-
filename: fileName,
|
|
148
|
-
offset: localHeaderOffset,
|
|
149
|
-
compressedSize,
|
|
150
|
-
uncompressedSize,
|
|
151
|
-
compression: compressionMethod,
|
|
152
|
-
});
|
|
153
|
-
}
|
|
154
|
-
offset += 46 + fileNameLength + extraFieldLength + fileCommentLength;
|
|
155
|
-
}
|
|
156
|
-
if (files.length === 0) {
|
|
157
|
-
console.warn('[ZipStreamManager] No files parsed from Central Directory');
|
|
158
|
-
return;
|
|
159
|
-
}
|
|
160
|
-
files.sort((a, b) => a.offset - b.offset);
|
|
161
|
-
for (let i = 0; i < files.length; i++) {
|
|
162
|
-
files[i].nextOffset = i < files.length - 1 ? files[i + 1].offset : cdOffset;
|
|
163
|
-
}
|
|
164
|
-
const swZipUrl = (0, zip_utils_1.normalizeZipUrlForSw)(resolvedZipUrl);
|
|
165
|
-
const allPaths = files.map(f => f.filename);
|
|
166
|
-
console.log('[ZipStreamManager] manifest all paths', allPaths);
|
|
167
|
-
console.log('[ZipStreamManager] manifest ready', {
|
|
168
|
-
zipUrl: swZipUrl,
|
|
169
|
-
entryCount: files.length,
|
|
170
|
-
sampleEntries: allPaths.slice(0, 30),
|
|
171
|
-
});
|
|
172
|
-
if (navigator.serviceWorker.controller) {
|
|
173
|
-
navigator.serviceWorker.controller.postMessage({
|
|
174
|
-
type: 'ZIP_MANIFEST',
|
|
175
|
-
zipUrl: swZipUrl,
|
|
176
|
-
files,
|
|
177
|
-
});
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
exports.ZipStreamManager = ZipStreamManager;
|