libfw-client 0.4.0 → 0.4.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/index.d.ts CHANGED
@@ -36,6 +36,17 @@ export interface UploadEntry {
36
36
  mtime: number;
37
37
  }
38
38
 
39
+ /**
40
+ * A `File` together with a caller-provided virtual path override. Passed
41
+ * verbatim to `options.resolveUploadPath`; when no resolver is configured,
42
+ * `relPath` itself is used as the upload path.
43
+ */
44
+ export interface UploadFileEntry {
45
+ file: File;
46
+ /** Virtual path (POSIX separators) to upload the file as. */
47
+ relPath?: string;
48
+ }
49
+
39
50
  /** Adaptive-tuning parameters the engine is currently tuned to. */
40
51
  export interface TuningParams {
41
52
  /** Cross-file transfer concurrency. */
@@ -171,6 +182,30 @@ export interface LibfwClientOptions {
171
182
  tuneTtlMs?: number;
172
183
  /** Optional progress/state listener. Tuning updates arrive as `{ type: 'tuning', phase, params, stats }`. */
173
184
  onEvent?: (event: LibfwEvent | LibfwTuningEvent) => void;
185
+ /**
186
+ * Pre-selected directory handle (or a resolver returning one) used instead
187
+ * of `showDirectoryPicker()` for every fs-mode download/upload.
188
+ */
189
+ directoryHandle?:
190
+ | FileSystemDirectoryHandle
191
+ | (() => FileSystemDirectoryHandle | Promise<FileSystemDirectoryHandle>);
192
+ /**
193
+ * Map a virtual server path to the display name used for fs-mode on-disk
194
+ * paths (directories/files created in the picked download folder),
195
+ * browser downloads, `.zip` entry names and the `.zip` archive name. May
196
+ * be async; returned values are still validated against zip-slip.
197
+ */
198
+ resolveDisplayName?: (path: string) => string | Promise<string>;
199
+ /**
200
+ * Map an upload entry to its virtual path on the server. `entry` is the
201
+ * original item passed to `upload()` (a bare `File` or an
202
+ * `UploadFileEntry` wrapper). `defaultPath` is
203
+ * `entry.relPath || file.webkitRelativePath || file.name`. May be async.
204
+ */
205
+ resolveUploadPath?: (
206
+ entry: File | UploadFileEntry,
207
+ defaultPath: string
208
+ ) => string | Promise<string>;
174
209
  }
175
210
 
176
211
  /**
@@ -204,9 +239,12 @@ export declare class LibfwClient {
204
239
  *
205
240
  * @param token bearer token
206
241
  * @param filePath virtual server path of the file to download
242
+ * @param opts optional overrides; `fileName` is the local name the file is
243
+ * saved as (leaf name only — parent directories of `filePath` still
244
+ * apply in fs mode).
207
245
  * @returns total bytes written
208
246
  */
209
- downloadFile(token: string, filePath: string): Promise<number>;
247
+ downloadFile(token: string, filePath: string, opts?: { fileName?: string }): Promise<number>;
210
248
 
211
249
  /**
212
250
  * Upload files to the server.
@@ -220,7 +258,7 @@ export declare class LibfwClient {
220
258
  */
221
259
  upload(
222
260
  token: string,
223
- files?: FileList | File[] | UploadEntry[],
261
+ files?: FileList | File[] | UploadFileEntry[] | UploadEntry[],
224
262
  ): Promise<number>;
225
263
 
226
264
  /** Pause the active transfer. */
package/index.js CHANGED
@@ -245,6 +245,26 @@ export class LibfwClient {
245
245
  * @param {(event: {type: string, done: number, total: number, path?: string, error?: string}) => void} [options.onEvent]
246
246
  * optional progress/state listener. Tuning updates arrive as
247
247
  * `{ type: 'tuning', phase, params, stats }` events.
248
+ * @param {FileSystemDirectoryHandle|(() => FileSystemDirectoryHandle|Promise<FileSystemDirectoryHandle>)} [options.directoryHandle]
249
+ * pre-selected directory handle (or a resolver returning one) used
250
+ * instead of popping `showDirectoryPicker()` for every fs-mode
251
+ * download/upload. Callers that already hold a handle (e.g. from a
252
+ * picker of their own, or a persisted handle) can inject it here.
253
+ * @param {(path: string) => string|Promise<string>} [options.resolveDisplayName]
254
+ * optional mapping from a virtual server path to the display name
255
+ * used for fs-mode on-disk paths (`_ensureFileHandle` — the
256
+ * directories/files actually created in the picked folder), browser
257
+ * downloads (`_downloadName`), `.zip` entry names (fallback folder
258
+ * downloads) and the `.zip` archive name. The resolver may be sync
259
+ * or async (return a `Promise`); returned values are still validated
260
+ * against zip-slip/path traversal.
261
+ * @param {(entry: File|{file: File, relPath?: string}, defaultPath: string) => string|Promise<string>} [options.resolveUploadPath]
262
+ * optional mapping from an upload entry to its virtual path on the
263
+ * server. `entry` is the ORIGINAL item passed to `upload()` — either
264
+ * a bare `File` or a caller wrapper such as `{ file, relPath }` — so
265
+ * custom metadata survives into the resolver. `defaultPath` is
266
+ * `entry.relPath || file.webkitRelativePath || file.name`. The
267
+ * resolver may be sync or async (return a `Promise`).
248
268
  */
249
269
  constructor(options = {}) {
250
270
  this._options = {
@@ -265,6 +285,9 @@ export class LibfwClient {
265
285
  autoTune: false,
266
286
  tuneTtlMs: 3600000,
267
287
  onEvent: null,
288
+ directoryHandle: null,
289
+ resolveDisplayName: null,
290
+ resolveUploadPath: null,
268
291
  ...options,
269
292
  };
270
293
  /** @type {WasmEngine|null} */
@@ -283,9 +306,11 @@ export class LibfwClient {
283
306
  this._uploadPlan = [];
284
307
  /**
285
308
  * Active browser-download fallback state, or `null`.
286
- * @type {{isFolder:boolean, buffers:Map<string,Uint8Array[]>, order:string[], sizes:Map<string,number>}|null}
309
+ * @type {{isFolder:boolean, buffers:Map<string,Uint8Array[]>, order:string[], sizes:Map<string,number>, total:number, buffered:number}|null}
287
310
  */
288
311
  this._fallback = null;
312
+ /** Leaf-name override for the current single-file download, or `null`. */
313
+ this._singleFileName = null;
289
314
  }
290
315
 
291
316
  // ------------------------------------------------------------------ setup
@@ -434,6 +459,22 @@ export class LibfwClient {
434
459
  );
435
460
  }
436
461
 
462
+ /**
463
+ * Obtain the destination directory handle for fs-mode transfers: the
464
+ * injected `directoryHandle` option (a handle or a resolver function)
465
+ * wins; otherwise `showDirectoryPicker()` is popped.
466
+ * @returns {Promise<FileSystemDirectoryHandle>}
467
+ * @private
468
+ */
469
+ async _pickDirectory() {
470
+ const provided = this._options.directoryHandle;
471
+ if (provided) {
472
+ const handle = typeof provided === 'function' ? await provided() : provided;
473
+ if (handle) return handle;
474
+ }
475
+ return window.showDirectoryPicker();
476
+ }
477
+
437
478
  /**
438
479
  * Resolve the effective download mode from the `downloadMode` option:
439
480
  * an explicit `'fs'`/`'browser'` wins; `'auto'` falls back to the browser
@@ -469,7 +510,7 @@ export class LibfwClient {
469
510
  if (this._effectiveMode() === 'browser') {
470
511
  return this._downloadViaBrowser(engine, token, dirPath, true);
471
512
  }
472
- this._dirHandle = await window.showDirectoryPicker();
513
+ this._dirHandle = await this._pickDirectory();
473
514
  this._fileHandles.clear();
474
515
  try {
475
516
  return await engine.download_folder(this._options.baseUrl, token, dirPath);
@@ -491,22 +532,35 @@ export class LibfwClient {
491
532
  *
492
533
  * @param {string} token bearer token
493
534
  * @param {string} filePath virtual server path of the file to download
535
+ * @param {{fileName?: string}} [opts] optional overrides; `fileName` is the
536
+ * local name the file is saved as (leaf name only — parent
537
+ * directories of `filePath` still apply in fs mode).
494
538
  * @returns {Promise<number>} total bytes transferred
495
539
  * @throws {LibfwError}
496
540
  */
497
- async downloadFile(token, filePath) {
541
+ async downloadFile(token, filePath, opts = {}) {
498
542
  const engine = await this._ready();
499
543
  if (!filePath) throw new LibfwError('downloadFile requires a file path', 'path');
500
- if (this._effectiveMode() === 'browser') {
501
- return this._downloadViaBrowser(engine, token, filePath, false);
544
+ if (opts.fileName !== undefined) {
545
+ const name = String(opts.fileName);
546
+ // Reject anything that could escape the destination directory (the
547
+ // FS Access API would throw a raw TypeError anyway; fail uniformly).
548
+ if (!name || name === '.' || name === '..' || /[/\\]/.test(name)) {
549
+ throw new LibfwError(`invalid fileName: ${name}`, 'path');
550
+ }
502
551
  }
503
- this._dirHandle = await window.showDirectoryPicker();
504
- this._fileHandles.clear();
552
+ this._singleFileName = opts.fileName ? String(opts.fileName) : null;
505
553
  try {
554
+ if (this._effectiveMode() === 'browser') {
555
+ return await this._downloadViaBrowser(engine, token, filePath, false);
556
+ }
557
+ this._dirHandle = await this._pickDirectory();
558
+ this._fileHandles.clear();
506
559
  return await engine.download_file(this._options.baseUrl, token, filePath);
507
560
  } catch (err) {
508
561
  throw toLibfwError(err);
509
562
  } finally {
563
+ this._singleFileName = null;
510
564
  await this._flushWritables();
511
565
  await this._syncResumeOffsets();
512
566
  }
@@ -531,7 +585,7 @@ export class LibfwClient {
531
585
  * @private
532
586
  */
533
587
  async _downloadViaBrowser(engine, token, path, isFolder) {
534
- this._fallback = { isFolder, buffers: new Map(), order: [], sizes: new Map(), total: 0 };
588
+ this._fallback = { isFolder, buffers: new Map(), order: [], sizes: new Map(), total: 0, buffered: 0 };
535
589
  try {
536
590
  const total = isFolder
537
591
  ? await engine.download_folder(this._options.baseUrl, token, path)
@@ -539,22 +593,36 @@ export class LibfwClient {
539
593
  const { buffers, order, sizes } = this._fallback;
540
594
  if (isFolder) {
541
595
  const entries = [];
596
+ // Distinct virtual paths may map to the same display name (a custom
597
+ // `resolveDisplayName` is not required to be injective); duplicate
598
+ // names would silently shadow each other on extraction.
599
+ const names = new Set();
600
+ const pushEntry = (name, data) => {
601
+ if (names.has(name)) {
602
+ throw new LibfwError(`duplicate download entry name: ${name}`, 'path');
603
+ }
604
+ names.add(name);
605
+ entries.push({ name, data });
606
+ };
542
607
  for (const p of order) {
543
- entries.push({
544
- name: this._safeEntryName(p),
545
- data: this._concatBuffers(buffers.get(p)?.chunks || []),
546
- });
608
+ pushEntry(
609
+ await this._safeEntryName(p),
610
+ this._concatBuffers(buffers.get(p)?.chunks || [])
611
+ );
547
612
  }
548
613
  // Include files that were announced but produced no bytes (empty).
549
614
  for (const p of sizes.keys()) {
550
615
  if (!buffers.has(p)) {
551
- entries.push({ name: this._safeEntryName(p), data: new Uint8Array(0) });
616
+ pushEntry(await this._safeEntryName(p), new Uint8Array(0));
552
617
  }
553
618
  }
554
- this._triggerBrowserDownload(createZip(entries), this._archiveName(path));
619
+ this._triggerBrowserDownload(createZip(entries), await this._archiveName(path));
555
620
  } else {
556
621
  const data = this._concatBuffers(buffers.get(path)?.chunks || []);
557
- this._triggerBrowserDownload(new Blob([data], { type: 'application/octet-stream' }), this._downloadName(path));
622
+ this._triggerBrowserDownload(
623
+ new Blob([data], { type: 'application/octet-stream' }),
624
+ this._singleFileName || (await this._downloadName(path))
625
+ );
558
626
  }
559
627
  return total;
560
628
  } catch (err) {
@@ -584,25 +652,36 @@ export class LibfwClient {
584
652
  }
585
653
 
586
654
  /**
587
- * Strip a leading `/` so an entry path is archive/OS friendly.
655
+ * Strip a leading `/` so an entry path is archive/OS/disk friendly,
656
+ * applying the `resolveDisplayName` option when configured. The resolver
657
+ * may be sync or async (return a `Promise`); both fs-mode on-disk paths
658
+ * (`_ensureFileHandle`) and browser-fallback archive/file names go
659
+ * through here, so a display-name mapping applies uniformly to every
660
+ * download destination.
588
661
  * @param {string} path
589
- * @returns {string}
662
+ * @returns {Promise<string>}
590
663
  * @private
591
664
  */
592
- _cleanPath(path) {
593
- return String(path).replace(/^\/+/, '');
665
+ async _cleanPath(path) {
666
+ const resolve = this._options.resolveDisplayName;
667
+ let resolved = null;
668
+ if (typeof resolve === 'function') {
669
+ resolved = await resolve(String(path));
670
+ }
671
+ return String(resolved != null ? resolved : path).replace(/^\/+/, '');
594
672
  }
595
673
 
596
674
  /**
597
- * Validate a virtual path for use as a ZIP entry name, rejecting any
598
- * traversal (`..`), absolute/drive-letter prefixes or Windows-style
599
- * separators that could escape the archive on extraction (zip-slip).
675
+ * Validate a virtual path for use as a ZIP entry name or an on-disk
676
+ * fs-mode path, rejecting any traversal (`..`), absolute/drive-letter
677
+ * prefixes or Windows-style separators that could escape the archive on
678
+ * extraction (zip-slip) or the picked download directory.
600
679
  * @param {string} path
601
- * @returns {string}
680
+ * @returns {Promise<string>}
602
681
  * @private
603
682
  */
604
- _safeEntryName(path) {
605
- const cleaned = this._cleanPath(path);
683
+ async _safeEntryName(path) {
684
+ const cleaned = await this._cleanPath(path);
606
685
  const segs = String(cleaned).split('/');
607
686
  if (segs.some((seg) => seg === '..' || seg.includes('\\') || /^[a-zA-Z]:/.test(seg))) {
608
687
  throw new LibfwError(`unsafe path in download: ${path}`, 'path');
@@ -623,22 +702,22 @@ export class LibfwClient {
623
702
  /**
624
703
  * Derive a safe file name from a virtual path.
625
704
  * @param {string} path
626
- * @returns {string}
705
+ * @returns {Promise<string>}
627
706
  * @private
628
707
  */
629
- _downloadName(path) {
630
- const name = this._cleanPath(path).split('/').pop();
708
+ async _downloadName(path) {
709
+ const name = (await this._cleanPath(path)).split('/').pop();
631
710
  return name || 'download';
632
711
  }
633
712
 
634
713
  /**
635
714
  * Derive the `.zip` archive name for a folder download.
636
715
  * @param {string} path
637
- * @returns {string}
716
+ * @returns {Promise<string>}
638
717
  * @private
639
718
  */
640
- _archiveName(path) {
641
- const base = this._cleanPath(path).split('/').pop() || 'download';
719
+ async _archiveName(path) {
720
+ const base = (await this._cleanPath(path)).split('/').pop() || 'download';
642
721
  return `${base.replace(/[^\w.\- ]+/g, '_') || 'download'}.zip`;
643
722
  }
644
723
 
@@ -703,6 +782,18 @@ export class LibfwClient {
703
782
  }
704
783
  buf.chunks.push(data);
705
784
  buf.len += data.length;
785
+ // Runtime backstop for the in-memory cap: `onFileStart` pre-checks use
786
+ // the engine's declared size; an under-reported size (or a skipped
787
+ // fileStart on an unusual path) must still fail instead of growing the
788
+ // buffer unbounded.
789
+ this._fallback.buffered += data.length;
790
+ const max = this._maxFallbackBytes();
791
+ if (max > 0 && this._fallback.buffered > max) {
792
+ throw new LibfwError(
793
+ `browser download buffered more than the ${max}-byte in-memory limit`,
794
+ 'too-large'
795
+ );
796
+ }
706
797
  return;
707
798
  }
708
799
  let entry = this._writables.get(path);
@@ -797,7 +888,9 @@ export class LibfwClient {
797
888
  * @private
798
889
  */
799
890
  async _ensureFileHandle(path) {
800
- const segments = splitPath(path);
891
+ // Map through the display-name resolver (sync or async) so fs-mode
892
+ // streaming lands under the same names the browser fallback would use.
893
+ const segments = splitPath(await this._safeEntryName(path));
801
894
  if (segments.length === 0) {
802
895
  throw new LibfwError(`invalid download path: ${path}`, 'path');
803
896
  }
@@ -805,7 +898,7 @@ export class LibfwClient {
805
898
  for (let i = 0; i < segments.length - 1; i += 1) {
806
899
  dir = await dir.getDirectoryHandle(segments[i], { create: true });
807
900
  }
808
- const name = segments[segments.length - 1];
901
+ const name = this._singleFileName || segments[segments.length - 1];
809
902
  const handle = await dir.getFileHandle(name, { create: true });
810
903
  return { dir, name, handle };
811
904
  }
@@ -836,7 +929,9 @@ export class LibfwClient {
836
929
  async _resolveFileHandle(path) {
837
930
  if (!this._dirHandle) return null;
838
931
  try {
839
- const segments = splitPath(path);
932
+ // Same display-name mapping as `_ensureFileHandle` so a resume finds
933
+ // the file previously written under its resolved (display) name.
934
+ const segments = splitPath(await this._safeEntryName(path));
840
935
  if (segments.length === 0) return null;
841
936
  let dir = this._dirHandle;
842
937
  for (let i = 0; i < segments.length - 1; i += 1) {
@@ -945,12 +1040,14 @@ export class LibfwClient {
945
1040
  *
946
1041
  * If `files` is omitted, `showDirectoryPicker()` is used to select a
947
1042
  * local folder whose structure is mirrored on the server. Otherwise
948
- * `files` may be a `FileList`, an array of `File`s, or an array of
1043
+ * `files` may be a `FileList`, an array of `File`s, an array of
1044
+ * `{ file, relPath }` wrappers (custom per-file virtual paths, passed
1045
+ * verbatim to `resolveUploadPath`), or an array of
949
1046
  * `{ path, size, mtime }` plan entries (when you want to drive reading
950
1047
  * yourself).
951
1048
  *
952
1049
  * @param {string} token bearer token
953
- * @param {FileList|File[]|Array<{path:string,size:number,mtime:number}>} [files]
1050
+ * @param {FileList|File[]|Array<{file:File,relPath?:string}>|Array<{path:string,size:number,mtime:number}>} [files]
954
1051
  * @returns {Promise<number>} total bytes uploaded
955
1052
  * @throws {LibfwError}
956
1053
  */
@@ -977,7 +1074,7 @@ export class LibfwClient {
977
1074
  if (typeof window === 'undefined' || typeof window.showDirectoryPicker !== 'function') {
978
1075
  throw new LibfwError('File System Access API is not available in this browser', 'unsupported');
979
1076
  }
980
- const dir = await window.showDirectoryPicker();
1077
+ const dir = await this._pickDirectory();
981
1078
  this._dirHandle = dir;
982
1079
  this._uploadPlan = await this._collectDirectoryFiles(dir, '');
983
1080
  } else {
@@ -1031,9 +1128,35 @@ export class LibfwClient {
1031
1128
  }
1032
1129
  const list = Array.from(files || []);
1033
1130
  const plan = [];
1034
- for (const file of list) {
1035
- if (!(file instanceof File)) continue;
1036
- const path = file.webkitRelativePath || file.name;
1131
+ const resolve = this._options.resolveUploadPath;
1132
+ const seen = new Set();
1133
+ for (const entry of list) {
1134
+ // Accept bare `File`s as well as wrappers like `{ file, relPath }`;
1135
+ // the resolver receives the ORIGINAL entry so caller-side metadata
1136
+ // (e.g. a custom `relPath`) is visible without WeakMap workarounds.
1137
+ const file = entry instanceof File ? entry : entry && entry.file instanceof File ? entry.file : null;
1138
+ if (!file) continue;
1139
+ // A wrapper-provided `relPath` is the most specific default; fall
1140
+ // back to the browser-provided relative path, then the file name.
1141
+ const defaultPath =
1142
+ (entry && typeof entry === 'object' && typeof entry.relPath === 'string' && entry.relPath
1143
+ ? entry.relPath
1144
+ : null) ||
1145
+ file.webkitRelativePath ||
1146
+ file.name;
1147
+ // The resolver may be async; a `null`/`undefined` mapping falls back
1148
+ // to the default path (same leniency as `resolveDisplayName`).
1149
+ let path = defaultPath;
1150
+ if (typeof resolve === 'function') {
1151
+ const mapped = await resolve(entry, defaultPath);
1152
+ if (mapped != null) path = String(mapped);
1153
+ }
1154
+ // A mapping that collides would silently shadow one File with another
1155
+ // (_uploadFiles keeps only the last) and upload the same path twice.
1156
+ if (seen.has(path)) {
1157
+ throw new LibfwError(`duplicate upload path: ${path}`, 'path');
1158
+ }
1159
+ seen.add(path);
1037
1160
  this._uploadFiles.set(path, file);
1038
1161
  plan.push({ path, size: file.size, mtime: Math.floor(file.lastModified / 1000) });
1039
1162
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libfw-client",
3
- "version": "0.4.0",
3
+ "version": "0.4.2",
4
4
  "description": "High-performance streaming file & folder transfer SDK for the browser (libfw WASM engine + File System Access API + IndexedDB resume).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -17,14 +17,10 @@ export class LibfwClient {
17
17
  done_bytes(): number;
18
18
  /**
19
19
  * Download a single file at `file_path` into the chosen local directory.
20
- *
21
- * Resolves with the number of bytes written.
22
20
  */
23
21
  download_file(base_url: string, token: string, file_path: string): Promise<any>;
24
22
  /**
25
23
  * Download every file under the virtual `dirPath` (empty = root).
26
- *
27
- * Resolves with the number of bytes written.
28
24
  */
29
25
  download_folder(base_url: string, token: string, dir_path: string): Promise<any>;
30
26
  /**
@@ -74,8 +70,6 @@ export class LibfwClient {
74
70
  tune_state(): any;
75
71
  /**
76
72
  * Upload the files reported by the JS `getFileList` callback.
77
- *
78
- * Resolves with the number of bytes uploaded.
79
73
  */
80
74
  upload(base_url: string, token: string): Promise<any>;
81
75
  }
@@ -109,10 +103,11 @@ export interface InitOutput {
109
103
  readonly libfwclient_tune_state: (a: number) => any;
110
104
  readonly libfwclient_upload: (a: number, b: number, c: number, d: number, e: number) => any;
111
105
  readonly start: () => void;
112
- readonly wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___wasm_bindgen_1e05ddb24c0b7df4___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_1e05ddb24c0b7df4___JsError___true_: (a: number, b: number, c: any) => [number, number];
113
- readonly wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined_______true_: (a: number, b: number, c: any, d: any) => void;
114
- readonly wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___web_sys_cda36541fa15086d___features__gen_ProgressEvent__ProgressEvent______true_: (a: number, b: number, c: any) => void;
115
- readonly wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke_______true_: (a: number, b: number) => void;
106
+ readonly wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue__core_ed718c3d60ebd546___result__Result_____wasm_bindgen_b2c787b844a3ac79___JsError___true_: (a: number, b: number, c: any) => [number, number];
107
+ readonly wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined_______true_: (a: number, b: number, c: any, d: any) => void;
108
+ readonly wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true_: (a: number, b: number, c: any) => void;
109
+ readonly wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true__2: (a: number, b: number, c: any) => void;
110
+ readonly wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke_______true_: (a: number, b: number) => void;
116
111
  readonly __wbindgen_malloc: (a: number, b: number) => number;
117
112
  readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
118
113
  readonly __wbindgen_exn_store: (a: number) => void;
@@ -30,8 +30,6 @@ export class LibfwClient {
30
30
  }
31
31
  /**
32
32
  * Download a single file at `file_path` into the chosen local directory.
33
- *
34
- * Resolves with the number of bytes written.
35
33
  * @param {string} base_url
36
34
  * @param {string} token
37
35
  * @param {string} file_path
@@ -49,8 +47,6 @@ export class LibfwClient {
49
47
  }
50
48
  /**
51
49
  * Download every file under the virtual `dirPath` (empty = root).
52
- *
53
- * Resolves with the number of bytes written.
54
50
  * @param {string} base_url
55
51
  * @param {string} token
56
52
  * @param {string} dir_path
@@ -157,8 +153,6 @@ export class LibfwClient {
157
153
  }
158
154
  /**
159
155
  * Upload the files reported by the JS `getFileList` callback.
160
- *
161
- * Resolves with the number of bytes uploaded.
162
156
  * @param {string} base_url
163
157
  * @param {string} token
164
158
  * @returns {Promise<any>}
@@ -250,6 +244,9 @@ function __wbg_get_imports() {
250
244
  __wbg_abort_4426bc6bd4153680: function() { return handleError(function (arg0) {
251
245
  arg0.abort();
252
246
  }, arguments); },
247
+ __wbg_abort_8bae0f33e7833997: function(arg0) {
248
+ arg0.abort();
249
+ },
253
250
  __wbg_apply_3ac86a26fdb56c05: function() { return handleError(function (arg0, arg1, arg2) {
254
251
  const ret = arg0.apply(arg1, arg2);
255
252
  return ret;
@@ -270,9 +267,16 @@ function __wbg_get_imports() {
270
267
  const ret = arg0.call(arg1, arg2);
271
268
  return ret;
272
269
  }, arguments); },
270
+ __wbg_catch_c1a60df4c30d76d3: function(arg0, arg1) {
271
+ const ret = arg0.catch(arg1);
272
+ return ret;
273
+ },
273
274
  __wbg_clearInterval_2e2069e95ad09d4f: function(arg0, arg1) {
274
275
  arg0.clearInterval(arg1);
275
276
  },
277
+ __wbg_clearTimeout_8f80437be2324e09: function(arg0, arg1) {
278
+ arg0.clearTimeout(arg1);
279
+ },
276
280
  __wbg_encodeURIComponent_d0140ae6e13eb27b: function(arg0, arg1) {
277
281
  const ret = encodeURIComponent(getStringFromWasm0(arg0, arg1));
278
282
  return ret;
@@ -384,6 +388,10 @@ function __wbg_get_imports() {
384
388
  const ret = new Array();
385
389
  return ret;
386
390
  },
391
+ __wbg_new_4339b2a2675a03e3: function() { return handleError(function () {
392
+ const ret = new AbortController();
393
+ return ret;
394
+ }, arguments); },
387
395
  __wbg_new_6b1dd7bed0e5462c: function() { return handleError(function () {
388
396
  const ret = new XMLHttpRequest();
389
397
  return ret;
@@ -395,7 +403,7 @@ function __wbg_get_imports() {
395
403
  const a = state0.a;
396
404
  state0.a = 0;
397
405
  try {
398
- return wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined_______true_(a, state0.b, arg0, arg1);
406
+ return wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined_______true_(a, state0.b, arg0, arg1);
399
407
  } finally {
400
408
  state0.a = a;
401
409
  }
@@ -429,7 +437,7 @@ function __wbg_get_imports() {
429
437
  const a = state0.a;
430
438
  state0.a = 0;
431
439
  try {
432
- return wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined_______true_(a, state0.b, arg0, arg1);
440
+ return wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined_______true_(a, state0.b, arg0, arg1);
433
441
  } finally {
434
442
  state0.a = a;
435
443
  }
@@ -448,10 +456,6 @@ function __wbg_get_imports() {
448
456
  const ret = Date.now();
449
457
  return ret;
450
458
  },
451
- __wbg_of_5f1b88183ddb5d94: function(arg0, arg1) {
452
- const ret = Array.of(arg0, arg1);
453
- return ret;
454
- },
455
459
  __wbg_open_90286a4ce0ae2098: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4, arg5) {
456
460
  arg0.open(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4), arg5 !== 0);
457
461
  }, arguments); },
@@ -469,10 +473,6 @@ function __wbg_get_imports() {
469
473
  __wbg_queueMicrotask_6a09b7bc46549209: function(arg0) {
470
474
  queueMicrotask(arg0);
471
475
  },
472
- __wbg_race_ac5c7b465abcfa15: function(arg0) {
473
- const ret = Promise.race(arg0);
474
- return ret;
475
- },
476
476
  __wbg_read_8afa15f12a160ef8: function(arg0) {
477
477
  const ret = arg0.read();
478
478
  return ret;
@@ -501,10 +501,6 @@ function __wbg_get_imports() {
501
501
  __wbg_setRequestHeader_fe390ff50d349432: function() { return handleError(function (arg0, arg1, arg2, arg3, arg4) {
502
502
  arg0.setRequestHeader(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
503
503
  }, arguments); },
504
- __wbg_setTimeout_725a27c387d005c7: function() { return handleError(function (arg0, arg1, arg2, arg3) {
505
- const ret = arg0.setTimeout(arg1, arg2, arg3);
506
- return ret;
507
- }, arguments); },
508
504
  __wbg_setTimeout_cfa2cf195c3738db: function() { return handleError(function (arg0, arg1, arg2) {
509
505
  const ret = arg0.setTimeout(arg1, arg2);
510
506
  return ret;
@@ -537,6 +533,13 @@ function __wbg_get_imports() {
537
533
  __wbg_set_onreadystatechange_14ce44725c7e0789: function(arg0, arg1) {
538
534
  arg0.onreadystatechange = arg1;
539
535
  },
536
+ __wbg_set_signal_c4ef8faddb4c1446: function(arg0, arg1) {
537
+ arg0.signal = arg1;
538
+ },
539
+ __wbg_signal_dad7cb35193abd31: function(arg0) {
540
+ const ret = arg0.signal;
541
+ return ret;
542
+ },
540
543
  __wbg_stack_3b0d974bbf31e44f: function(arg0, arg1) {
541
544
  const ret = arg1.stack;
542
545
  const ptr1 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
@@ -576,6 +579,10 @@ function __wbg_get_imports() {
576
579
  const ret = arg0.then(arg1);
577
580
  return ret;
578
581
  },
582
+ __wbg_then_e0960b859f3ff223: function(arg0, arg1) {
583
+ const ret = arg0.then(arg1);
584
+ return ret;
585
+ },
579
586
  __wbg_total_21672ff1bd8d23ea: function(arg0) {
580
587
  const ret = arg0.total;
581
588
  return ret;
@@ -585,26 +592,31 @@ function __wbg_get_imports() {
585
592
  return ret;
586
593
  }, arguments); },
587
594
  __wbindgen_cast_0000000000000001: function(arg0, arg1) {
588
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 198, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
589
- const ret = makeMutClosure(arg0, arg1, wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___wasm_bindgen_1e05ddb24c0b7df4___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_1e05ddb24c0b7df4___JsError___true_);
595
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 209, ret: Result(Unit), inner_ret: Some(Result(Unit)) }, mutable: true }) -> Externref`.
596
+ const ret = makeMutClosure(arg0, arg1, wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue__core_ed718c3d60ebd546___result__Result_____wasm_bindgen_b2c787b844a3ac79___JsError___true_);
590
597
  return ret;
591
598
  },
592
599
  __wbindgen_cast_0000000000000002: function(arg0, arg1) {
593
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("ProgressEvent")], shim_idx: 5, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
594
- const ret = makeMutClosure(arg0, arg1, wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___web_sys_cda36541fa15086d___features__gen_ProgressEvent__ProgressEvent______true_);
600
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [Externref], shim_idx: 5, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
601
+ const ret = makeMutClosure(arg0, arg1, wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true_);
595
602
  return ret;
596
603
  },
597
604
  __wbindgen_cast_0000000000000003: function(arg0, arg1) {
598
- // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 7, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
599
- const ret = makeMutClosure(arg0, arg1, wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke_______true_);
605
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [NamedExternref("ProgressEvent")], shim_idx: 5, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
606
+ const ret = makeMutClosure(arg0, arg1, wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true__2);
607
+ return ret;
608
+ },
609
+ __wbindgen_cast_0000000000000004: function(arg0, arg1) {
610
+ // Cast intrinsic for `Closure(Closure { owned: true, function: Function { arguments: [], shim_idx: 8, ret: Unit, inner_ret: Some(Unit) }, mutable: true }) -> Externref`.
611
+ const ret = makeMutClosure(arg0, arg1, wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke_______true_);
600
612
  return ret;
601
613
  },
602
- __wbindgen_cast_0000000000000004: function(arg0) {
614
+ __wbindgen_cast_0000000000000005: function(arg0) {
603
615
  // Cast intrinsic for `F64 -> Externref`.
604
616
  const ret = arg0;
605
617
  return ret;
606
618
  },
607
- __wbindgen_cast_0000000000000005: function(arg0, arg1) {
619
+ __wbindgen_cast_0000000000000006: function(arg0, arg1) {
608
620
  // Cast intrinsic for `Ref(String) -> Externref`.
609
621
  const ret = getStringFromWasm0(arg0, arg1);
610
622
  return ret;
@@ -625,23 +637,27 @@ function __wbg_get_imports() {
625
637
  };
626
638
  }
627
639
 
628
- function wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke_______true_(arg0, arg1) {
629
- wasm.wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke_______true_(arg0, arg1);
640
+ function wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke_______true_(arg0, arg1) {
641
+ wasm.wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke_______true_(arg0, arg1);
642
+ }
643
+
644
+ function wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true_(arg0, arg1, arg2) {
645
+ wasm.wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true_(arg0, arg1, arg2);
630
646
  }
631
647
 
632
- function wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___web_sys_cda36541fa15086d___features__gen_ProgressEvent__ProgressEvent______true_(arg0, arg1, arg2) {
633
- wasm.wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___web_sys_cda36541fa15086d___features__gen_ProgressEvent__ProgressEvent______true_(arg0, arg1, arg2);
648
+ function wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true__2(arg0, arg1, arg2) {
649
+ wasm.wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true__2(arg0, arg1, arg2);
634
650
  }
635
651
 
636
- function wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___wasm_bindgen_1e05ddb24c0b7df4___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_1e05ddb24c0b7df4___JsError___true_(arg0, arg1, arg2) {
637
- const ret = wasm.wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___wasm_bindgen_1e05ddb24c0b7df4___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_1e05ddb24c0b7df4___JsError___true_(arg0, arg1, arg2);
652
+ function wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue__core_ed718c3d60ebd546___result__Result_____wasm_bindgen_b2c787b844a3ac79___JsError___true_(arg0, arg1, arg2) {
653
+ const ret = wasm.wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue__core_ed718c3d60ebd546___result__Result_____wasm_bindgen_b2c787b844a3ac79___JsError___true_(arg0, arg1, arg2);
638
654
  if (ret[1]) {
639
655
  throw takeFromExternrefTable0(ret[0]);
640
656
  }
641
657
  }
642
658
 
643
- function wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined_______true_(arg0, arg1, arg2, arg3) {
644
- wasm.wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined_______true_(arg0, arg1, arg2, arg3);
659
+ function wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined_______true_(arg0, arg1, arg2, arg3) {
660
+ wasm.wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined_______true_(arg0, arg1, arg2, arg3);
645
661
  }
646
662
 
647
663
  const LibfwClientFinalization = (typeof FinalizationRegistry === 'undefined')
Binary file
@@ -19,10 +19,11 @@ export const libfwclient_total_bytes: (a: number) => number;
19
19
  export const libfwclient_tune_state: (a: number) => any;
20
20
  export const libfwclient_upload: (a: number, b: number, c: number, d: number, e: number) => any;
21
21
  export const start: () => void;
22
- export const wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___wasm_bindgen_1e05ddb24c0b7df4___JsValue__core_9b3796e30d99ddb7___result__Result_____wasm_bindgen_1e05ddb24c0b7df4___JsError___true_: (a: number, b: number, c: any) => [number, number];
23
- export const wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined___js_sys_bb6c79d0abe11c10___Function_fn_wasm_bindgen_1e05ddb24c0b7df4___JsValue_____wasm_bindgen_1e05ddb24c0b7df4___sys__Undefined_______true_: (a: number, b: number, c: any, d: any) => void;
24
- export const wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke___web_sys_cda36541fa15086d___features__gen_ProgressEvent__ProgressEvent______true_: (a: number, b: number, c: any) => void;
25
- export const wasm_bindgen_1e05ddb24c0b7df4___convert__closures_____invoke_______true_: (a: number, b: number) => void;
22
+ export const wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue__core_ed718c3d60ebd546___result__Result_____wasm_bindgen_b2c787b844a3ac79___JsError___true_: (a: number, b: number, c: any) => [number, number];
23
+ export const wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined___js_sys_209827a80d159db4___Function_fn_wasm_bindgen_b2c787b844a3ac79___JsValue_____wasm_bindgen_b2c787b844a3ac79___sys__Undefined_______true_: (a: number, b: number, c: any, d: any) => void;
24
+ export const wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true_: (a: number, b: number, c: any) => void;
25
+ export const wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke___wasm_bindgen_b2c787b844a3ac79___JsValue______true__2: (a: number, b: number, c: any) => void;
26
+ export const wasm_bindgen_b2c787b844a3ac79___convert__closures_____invoke_______true_: (a: number, b: number) => void;
26
27
  export const __wbindgen_malloc: (a: number, b: number) => number;
27
28
  export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
28
29
  export const __wbindgen_exn_store: (a: number) => void;
package/pkg/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "libfw-client",
3
3
  "type": "module",
4
4
  "description": "WASM engine + JS SDK for libfw browser clients",
5
- "version": "0.4.0",
5
+ "version": "0.4.2",
6
6
  "license": "MIT",
7
7
  "repository": {
8
8
  "type": "git",
package/zip.js CHANGED
@@ -51,8 +51,21 @@ export function createZip(entries) {
51
51
  const central = [];
52
52
  let offset = 0;
53
53
 
54
+ // This writer uses classic 32-bit ZIP fields (no ZIP64): entry sizes, the
55
+ // local-header offset, the central-directory size/offset and the entry
56
+ // count are all stored as u32/u16. Exceeding any of them would silently
57
+ // corrupt the archive, so fail loudly instead.
58
+ const MAX_U32 = 0xffffffff;
59
+ const MAX_U16 = 0xffff;
60
+ if (entries.length > MAX_U16) {
61
+ throw new Error(`too many zip entries: ${entries.length} > ${MAX_U16}`);
62
+ }
63
+
54
64
  for (const entry of entries) {
55
65
  const nameBytes = encoder.encode(entry.name);
66
+ if (nameBytes.length > MAX_U16) {
67
+ throw new Error(`zip entry name too long: ${entry.name.slice(0, 64)}…`);
68
+ }
56
69
  const data = entry.data;
57
70
  const crc = crc32(data);
58
71
 
@@ -75,6 +88,9 @@ export function createZip(entries) {
75
88
  body.push(local, data);
76
89
  central.push({ nameBytes, crc, size: data.length, offset });
77
90
  offset += local.length + data.length;
91
+ if (offset > MAX_U32) {
92
+ throw new Error('zip archive exceeds the 4 GiB limit of this writer');
93
+ }
78
94
  }
79
95
 
80
96
  // Central directory.
@@ -104,6 +120,9 @@ export function createZip(entries) {
104
120
  dir.push(cd);
105
121
  cdSize += cd.length;
106
122
  }
123
+ if (cdSize > MAX_U32) {
124
+ throw new Error('zip central directory exceeds the 4 GiB limit of this writer');
125
+ }
107
126
 
108
127
  const cdOffset = offset;
109
128