@zip.js/zip.js 2.8.50 → 2.8.52

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.
@@ -26,7 +26,7 @@
26
26
  EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27
27
  */
28
28
 
29
- /* global WritableStream, AbortController */
29
+ /* global WritableStream, AbortController, DOMException */
30
30
  // deno-lint-ignore-file no-this-alias
31
31
 
32
32
  import {
@@ -34,26 +34,31 @@ import {
34
34
  Reader,
35
35
  TextReader,
36
36
  TextWriter,
37
+ getTextSize,
37
38
  Data64URIReader,
38
39
  Data64URIWriter,
39
40
  Uint8ArrayReader,
40
41
  Uint8ArrayWriter,
41
42
  BlobReader,
42
43
  BlobWriter,
43
- HttpReader
44
+ HttpReader,
45
+ ownsWritable
44
46
  } from "./io.js";
45
47
  import {
46
48
  ZipReader,
47
49
  ERR_INVALID_PASSWORD
48
50
  } from "./zip-reader.js";
49
51
  import {
50
- ZipWriter
52
+ ZipWriter,
53
+ getEntriesSize
51
54
  } from "./zip-writer.js";
52
55
  import {
53
56
  Entry
54
57
  } from "./zip-entry.js";
58
+ import { ProgressWatcherStream, callHandler } from "./codec-worker.js";
55
59
  import {
56
60
  UNDEFINED_VALUE,
61
+ FUNCTION_TYPE,
57
62
  EXTRAFIELD_TYPE_ZIP64,
58
63
  EXTRAFIELD_TYPE_AES,
59
64
  EXTRAFIELD_TYPE_NTFS,
@@ -71,7 +76,10 @@ import { toCompatibleReadable, toCompatibleWritable } from "./util/compatible-st
71
76
 
72
77
  const ERR_ENTRY_EXISTS = "Entry filename already exists";
73
78
  const ERR_READABLE_CONSUMED = "Readable stream already consumed";
79
+ const ERR_INVALID_PASS_THROUGH = "Invalid passThrough option (use readerOptions.passThrough or set uncompressedSize for each entry)";
74
80
  const ERR_ABORT_EXPORT = "zipjs-abort-export";
81
+ const ERR_ABORTED = "The operation was aborted";
82
+ const ABORT_ERROR_NAME = "AbortError";
75
83
  const INFOZIP_EXTRA_FIELD_TYPE = "infozip";
76
84
  const INTERPRETED_EXTRA_FIELD_TYPES = new Set([
77
85
  EXTRAFIELD_TYPE_ZIP64,
@@ -106,6 +114,7 @@ class ZipEntry {
106
114
  parent,
107
115
  children: [],
108
116
  uncompressedSize: params.uncompressedSize || 0,
117
+ undeterminedSize: params.undeterminedSize || params.uncompressedSize === UNDEFINED_VALUE,
109
118
  passThrough: params.passThrough
110
119
  });
111
120
  if (parent || !fs.root) {
@@ -173,16 +182,17 @@ class ZipFileEntry extends ZipEntry {
173
182
 
174
183
  async getData(writer, options = {}) {
175
184
  const zipEntry = this;
176
- if (!writer || (writer.constructor == zipEntry.Writer && zipEntry.data)) {
185
+ if (!writer || (writer.constructor == zipEntry.Writer && zipEntry.data && keepsContentType(writer, zipEntry.data))) {
177
186
  return zipEntry.data;
178
187
  } else {
179
188
  const reader = zipEntry.reader = createReader(zipEntry.Reader, zipEntry.data, options);
180
- const uncompressedSize = zipEntry.data ? zipEntry.data.uncompressedSize : reader.size;
181
- await Promise.all([initStream(reader), initStream(writer, uncompressedSize)]);
182
- const { readable } = reader;
189
+ const dataSize = zipEntry.uncompressedSize || reader.size;
190
+ await Promise.all([initStream(reader), initStream(writer, dataSize)]);
183
191
  const { signal } = options;
192
+ const readable = createProgressReadable(zipEntry, reader, options, signal);
193
+ const preventClose = !ownsWritable(writer) && Boolean(options.preventClose);
184
194
  zipEntry.uncompressedSize = reader.size;
185
- await toCompatibleReadable(readable).pipeTo(toCompatibleWritable(writer.writable), { signal });
195
+ await toCompatibleReadable(readable).pipeTo(toCompatibleWritable(writer.writable), { signal, preventClose, preventAbort: preventClose });
186
196
  return writer.getData ? writer.getData() : writer.writable;
187
197
  }
188
198
  }
@@ -313,7 +323,7 @@ class ZipDirectoryEntry extends ZipEntry {
313
323
  Reader: TextReader,
314
324
  Writer: TextWriter,
315
325
  options,
316
- uncompressedSize: text.length
326
+ uncompressedSize: getTextSize(text)
317
327
  });
318
328
  }
319
329
 
@@ -442,10 +452,16 @@ class ZipDirectoryEntry extends ZipEntry {
442
452
  }
443
453
 
444
454
  async importZip(reader, options = {}) {
445
- await initStream(reader);
446
- const zipReader = new ZipReader(reader, options);
455
+ let zipReader;
456
+ if (reader && typeof reader.getEntries == FUNCTION_TYPE) {
457
+ zipReader = reader;
458
+ options = Object.assign({}, zipReader.options, options);
459
+ } else {
460
+ await initStream(reader);
461
+ zipReader = new ZipReader(reader, options);
462
+ }
447
463
  const importedEntries = [];
448
- const entries = await zipReader.getEntries();
464
+ const entries = await zipReader.getEntries(options);
449
465
  for (const entry of entries) {
450
466
  let parent = this;
451
467
  try {
@@ -467,7 +483,7 @@ class ZipDirectoryEntry extends ZipEntry {
467
483
  importedEntries.push(addChild(parent, name, {
468
484
  data: entry,
469
485
  Reader: getZipBlobReader(Object.assign({}, options)),
470
- uncompressedSize: entry.uncompressedSize,
486
+ uncompressedSize: options.passThrough ? entry.compressedSize : entry.uncompressedSize,
471
487
  passThrough: options.passThrough
472
488
  }));
473
489
  } else {
@@ -509,11 +525,25 @@ class ZipDirectoryEntry extends ZipEntry {
509
525
  }
510
526
  const [readers] = await Promise.all([initReaders(zipEntry, options.readerOptions), initStream(writer)]);
511
527
  const zipWriter = new ZipWriter(writer, options);
512
- await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], "uncompressedSize"), options, readers);
513
- await zipWriter.close();
528
+ await exportZip(zipWriter, zipEntry, getTotalSize([zipEntry], getUncompressedSize), options, readers);
529
+ await zipWriter.close(options.globalComment);
514
530
  return writer.getData ? writer.getData() : writer.writable;
515
531
  }
516
532
 
533
+ getExportedSize(options = {}) {
534
+ const zipEntry = this;
535
+ options = Object.assign({}, options);
536
+ if (options.bufferedWrite === UNDEFINED_VALUE) {
537
+ options.bufferedWrite = true;
538
+ }
539
+ const writeOrderGuaranteed = !options.bufferedWrite ||
540
+ (options.keepOrder !== false && zipEntry.children.every(child => !child.children.length));
541
+ return getEntriesSize(options, Array.from(zipEntry.getChildren({ recursive: true }), child => {
542
+ const { name, entryOptions } = getChildEntryOptions(child, zipEntry, options);
543
+ return { name, size: child.directory ? 0 : getDeterminedSize(child, isPassThrough(child, options)), options: entryOptions };
544
+ }), writeOrderGuaranteed, options.globalComment);
545
+ }
546
+
517
547
  getChildByName(name) {
518
548
  const children = this.children;
519
549
  for (let childIndex = 0; childIndex < children.length; childIndex++) {
@@ -524,6 +554,10 @@ class ZipDirectoryEntry extends ZipEntry {
524
554
  }
525
555
  }
526
556
 
557
+ getChildren(options = {}) {
558
+ return collectChildren(this, options.recursive);
559
+ }
560
+
527
561
  isPasswordProtected() {
528
562
  const children = this.children;
529
563
  for (let childIndex = 0; childIndex < children.length; childIndex++) {
@@ -615,6 +649,10 @@ class FS {
615
649
  return this.root.getChildByName(name);
616
650
  }
617
651
 
652
+ getChildren(options) {
653
+ return this.root.getChildren(options);
654
+ }
655
+
618
656
  addDirectory(name, options) {
619
657
  return this.root.addDirectory(name, options);
620
658
  }
@@ -713,6 +751,10 @@ class FS {
713
751
  return this.root.exportZip(writer, options);
714
752
  }
715
753
 
754
+ getExportedSize(options) {
755
+ return this.root.getExportedSize(options);
756
+ }
757
+
716
758
  isPasswordProtected() {
717
759
  return this.root.isPasswordProtected();
718
760
  }
@@ -723,14 +765,14 @@ class FS {
723
765
  }
724
766
 
725
767
  const fs = { FS, ZipDirectoryEntry, ZipFileEntry };
726
- export { fs, ERR_ENTRY_EXISTS, ERR_READABLE_CONSUMED };
768
+ export { fs, ERR_ENTRY_EXISTS, ERR_READABLE_CONSUMED, ERR_INVALID_PASS_THROUGH, ERR_ABORTED };
727
769
 
728
- function getTotalSize(entries, propertyName) {
770
+ function getTotalSize(entries, getEntrySize) {
729
771
  let size = 0;
730
772
  const pendingEntries = Array.from(entries);
731
773
  while (pendingEntries.length) {
732
774
  const entry = pendingEntries.pop();
733
- size += entry[propertyName] || 0;
775
+ size += getEntrySize(entry) || 0;
734
776
  for (const child of entry.children) {
735
777
  pendingEntries.push(child);
736
778
  }
@@ -738,6 +780,15 @@ function getTotalSize(entries, propertyName) {
738
780
  return size;
739
781
  }
740
782
 
783
+ function getUncompressedSize(entry) {
784
+ return entry.uncompressedSize;
785
+ }
786
+
787
+ function getExtractedSize(entry, passThrough) {
788
+ const { data } = entry;
789
+ return passThrough && data instanceof Entry ? data.compressedSize : entry.uncompressedSize;
790
+ }
791
+
741
792
  function getReadableReader(readable) {
742
793
  let consumed;
743
794
  return function () {
@@ -760,10 +811,17 @@ function getZipBlobReader(options) {
760
811
 
761
812
  async init() {
762
813
  const zipBlobReader = this;
763
- zipBlobReader.size = zipBlobReader.entry.uncompressedSize;
764
- const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign({}, options, zipBlobReader.options));
814
+ const readerOptions = Object.assign({}, options, zipBlobReader.options);
815
+ const { checkOverlappingEntry, checkOverlappingEntryOnly } = readerOptions;
816
+ const data = await zipBlobReader.entry.getData(new BlobWriter(), Object.assign(readerOptions, {
817
+ checkPasswordOnly: false,
818
+ checkOverlappingEntry: checkOverlappingEntryOnly || checkOverlappingEntry,
819
+ checkOverlappingEntryOnly: false,
820
+ preventClose: false
821
+ }));
765
822
  zipBlobReader.data = data;
766
823
  zipBlobReader.blobReader = new BlobReader(data);
824
+ zipBlobReader.size = data.size;
767
825
  super.init();
768
826
  }
769
827
 
@@ -777,6 +835,30 @@ function createReader(Reader, data, options) {
777
835
  return Reader.prototype ? new Reader(data, options) : Reader(data, options);
778
836
  }
779
837
 
838
+ function keepsContentType(writer, data) {
839
+ const { contentType } = writer;
840
+ if (contentType === UNDEFINED_VALUE) {
841
+ return true;
842
+ } else if (writer.constructor == BlobWriter) {
843
+ return data.type == contentType;
844
+ } else if (writer.constructor == Data64URIWriter) {
845
+ return data.startsWith(writer.data);
846
+ } else {
847
+ return true;
848
+ }
849
+ }
850
+
851
+ function createProgressReadable(zipEntry, reader, options, signal) {
852
+ const { onstart, onprogress, onend } = options;
853
+ const { readable } = reader;
854
+ const coreReaderReportsProgress = zipEntry.data instanceof Entry;
855
+ if (coreReaderReportsProgress || (!onstart && !onprogress && !onend)) {
856
+ return readable;
857
+ } else {
858
+ return toCompatibleReadable(readable).pipeThrough(new ProgressWatcherStream({ onstart, onprogress, onend, size: reader.size }), { signal });
859
+ }
860
+ }
861
+
780
862
  async function initReaders(entry, options) {
781
863
  const fileEntries = [];
782
864
  const pendingEntries = [entry];
@@ -809,6 +891,7 @@ async function initReaders(entry, options) {
809
891
  }
810
892
  if (reader.size !== UNDEFINED_VALUE) {
811
893
  child.uncompressedSize = reader.size;
894
+ child.undeterminedSize = false;
812
895
  }
813
896
  }));
814
897
  return readers;
@@ -858,6 +941,90 @@ function aggregateEntryErrors(errors) {
858
941
  return error;
859
942
  }
860
943
 
944
+ function getChildEntryOptions(child, selectedEntry, options) {
945
+ const name = options.relativePath ? child.getRelativeName(selectedEntry) : child.getFullname();
946
+ const childOptions = child.options || {};
947
+ let zipEntryMetadata = {};
948
+ let passThroughOptions = {};
949
+ if (child.data instanceof Entry) {
950
+ const {
951
+ externalFileAttributes,
952
+ versionMadeBy,
953
+ comment,
954
+ lastModDate,
955
+ creationDate,
956
+ lastAccessDate,
957
+ uncompressedSize,
958
+ encrypted,
959
+ zipCrypto,
960
+ crc32,
961
+ compressionMethod,
962
+ extraFieldAES,
963
+ internalFileAttributes,
964
+ extraField,
965
+ uid,
966
+ gid
967
+ } = child.data;
968
+ zipEntryMetadata = {
969
+ externalFileAttributes,
970
+ versionMadeBy,
971
+ comment,
972
+ lastModDate,
973
+ creationDate,
974
+ lastAccessDate,
975
+ internalFileAttributes
976
+ };
977
+ const userExtraField = getUserExtraField(extraField);
978
+ if (userExtraField) {
979
+ zipEntryMetadata.extraField = userExtraField;
980
+ }
981
+ if (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE) {
982
+ Object.assign(zipEntryMetadata, {
983
+ uid,
984
+ gid,
985
+ unixExtraFieldType: INFOZIP_EXTRA_FIELD_TYPE
986
+ });
987
+ }
988
+ if (isPassThrough(child, options)) {
989
+ let level, encryptionStrength;
990
+ if (compressionMethod === 0) {
991
+ level = 0;
992
+ }
993
+ if (extraFieldAES) {
994
+ encryptionStrength = extraFieldAES.strength;
995
+ }
996
+ passThroughOptions = {
997
+ passThrough: true,
998
+ encrypted,
999
+ zipCrypto,
1000
+ crc32,
1001
+ uncompressedSize,
1002
+ level,
1003
+ encryptionStrength,
1004
+ compressionMethod
1005
+ };
1006
+ }
1007
+ }
1008
+ const entryOptions = Object.assign({}, zipEntryMetadata, options, passThroughOptions, childOptions, { directory: child.directory });
1009
+ if (!child.directory && entryOptions.passThrough && entryOptions.uncompressedSize === UNDEFINED_VALUE) {
1010
+ throw new Error(ERR_INVALID_PASS_THROUGH);
1011
+ }
1012
+ return { name, entryOptions };
1013
+ }
1014
+
1015
+ function getDeterminedSize(child, passThrough) {
1016
+ const { reader } = child;
1017
+ if (reader && reader.size !== UNDEFINED_VALUE) {
1018
+ return reader.size;
1019
+ }
1020
+ return child.undeterminedSize ? UNDEFINED_VALUE : getExtractedSize(child, passThrough);
1021
+ }
1022
+
1023
+ function isPassThrough(child, options) {
1024
+ const { readerOptions } = options;
1025
+ return Boolean(!child.directory && (child.passThrough || (readerOptions && readerOptions.passThrough)));
1026
+ }
1027
+
861
1028
  function getUserExtraField(extraField) {
862
1029
  if (extraField) {
863
1030
  const userExtraField = new Map();
@@ -873,104 +1040,53 @@ function getUserExtraField(extraField) {
873
1040
  }
874
1041
 
875
1042
  async function exportZip(zipWriter, entry, totalSize, options, readers) {
1043
+ const { onstart, onprogress, onend, onentryprogress } = options;
876
1044
  const selectedEntry = entry;
877
- const entryOffsets = new Map();
878
- await process(zipWriter, entry);
879
-
880
- async function process(zipWriter, entry) {
881
- await exportChild();
882
-
883
- async function exportChild() {
884
- if (options.bufferedWrite) {
885
- const results = await Promise.allSettled(entry.children.map(processChild));
886
- const errorResult = results.find(result => result.status == "rejected");
887
- if (errorResult) {
888
- throw errorResult.reason;
889
- }
890
- } else {
891
- for (const child of entry.children) {
892
- await processChild(child);
893
- }
894
- }
1045
+ const totalEntries = getTotalSize(entry.children, () => 1);
1046
+ let writtenSize = 0;
1047
+ let writtenEntries = 0;
1048
+ if (onstart) {
1049
+ await callHandler(onstart, totalSize);
1050
+ }
1051
+ if (options.bufferedWrite) {
1052
+ await processChildren(entry);
1053
+ } else {
1054
+ for (const child of entry.getChildren({ recursive: true })) {
1055
+ await addChild(child);
895
1056
  }
1057
+ }
1058
+ if (onend) {
1059
+ await callHandler(onend, writtenSize);
1060
+ }
896
1061
 
897
- async function processChild(child) {
898
- const name = options.relativePath ? child.getRelativeName(selectedEntry) : child.getFullname();
899
- const childOptions = child.options || {};
900
- let zipEntryOptions = {};
901
- if (child.data instanceof Entry) {
902
- const {
903
- externalFileAttributes,
904
- versionMadeBy,
905
- comment,
906
- lastModDate,
907
- creationDate,
908
- lastAccessDate,
909
- uncompressedSize,
910
- encrypted,
911
- zipCrypto,
912
- crc32,
913
- compressionMethod,
914
- extraFieldAES,
915
- internalFileAttributes,
916
- extraField,
917
- uid,
918
- gid
919
- } = child.data;
920
- zipEntryOptions = {
921
- externalFileAttributes,
922
- versionMadeBy,
923
- comment,
924
- lastModDate,
925
- creationDate,
926
- lastAccessDate,
927
- internalFileAttributes
928
- };
929
- const userExtraField = getUserExtraField(extraField);
930
- if (userExtraField) {
931
- zipEntryOptions.extraField = userExtraField;
932
- }
933
- if (uid !== UNDEFINED_VALUE || gid !== UNDEFINED_VALUE) {
934
- Object.assign(zipEntryOptions, {
935
- uid,
936
- gid,
937
- unixExtraFieldType: options.unixExtraFieldType || INFOZIP_EXTRA_FIELD_TYPE
938
- });
939
- }
940
- if (child.passThrough) {
941
- let level, encryptionStrength;
942
- if (compressionMethod === 0) {
943
- level = 0;
944
- }
945
- if (extraFieldAES) {
946
- encryptionStrength = extraFieldAES.strength;
947
- }
948
- zipEntryOptions = Object.assign(zipEntryOptions, {
949
- passThrough: true,
950
- encrypted,
951
- zipCrypto,
952
- crc32,
953
- uncompressedSize,
954
- level,
955
- encryptionStrength,
956
- compressionMethod
957
- });
1062
+ async function processChildren(entry) {
1063
+ const results = await Promise.allSettled(entry.children.map(async child => {
1064
+ await addChild(child);
1065
+ await processChildren(child);
1066
+ }));
1067
+ const errorResult = results.find(result => result.status == "rejected");
1068
+ if (errorResult) {
1069
+ throw errorResult.reason;
1070
+ }
1071
+ }
1072
+
1073
+ async function addChild(child) {
1074
+ const { name, entryOptions } = getChildEntryOptions(child, selectedEntry, options);
1075
+ let entryWrittenSize = 0;
1076
+ const entryMetadata = await zipWriter.add(name, readers.get(child), Object.assign(entryOptions, {
1077
+ onstart: UNDEFINED_VALUE,
1078
+ onend: UNDEFINED_VALUE,
1079
+ onprogress: async indexProgress => {
1080
+ writtenSize += indexProgress - entryWrittenSize;
1081
+ entryWrittenSize = indexProgress;
1082
+ if (onprogress) {
1083
+ await callHandler(onprogress, writtenSize, totalSize);
958
1084
  }
959
1085
  }
960
- await zipWriter.add(name, readers.get(child), Object.assign({}, options, zipEntryOptions, childOptions, {
961
- directory: child.directory,
962
- onprogress: async indexProgress => {
963
- if (options.onprogress) {
964
- entryOffsets.set(name, indexProgress);
965
- try {
966
- await options.onprogress(Array.from(entryOffsets.values()).reduce((previousValue, currentValue) => previousValue + currentValue), totalSize);
967
- } catch {
968
- // ignored
969
- }
970
- }
971
- }
972
- }));
973
- await process(zipWriter, child);
1086
+ }));
1087
+ writtenEntries++;
1088
+ if (onentryprogress) {
1089
+ await callHandler(onentryprogress, writtenEntries, totalEntries, entryMetadata);
974
1090
  }
975
1091
  }
976
1092
  }
@@ -999,7 +1115,7 @@ function addFileSystemHandle(zipEntry, handle, options) {
999
1115
  })
1000
1116
  );
1001
1117
  } else if (handle.kind == "directory") {
1002
- const directoryEntry = parentEntry.addDirectory(handle.name);
1118
+ const directoryEntry = parentEntry.addDirectory(handle.name, options);
1003
1119
  addedEntries.push(directoryEntry);
1004
1120
  for await (const childHandle of handle.values()) {
1005
1121
  await addFile(directoryEntry, childHandle, addedEntries, entryName);
@@ -1021,14 +1137,29 @@ function addFileSystemHandle(zipEntry, handle, options) {
1021
1137
  }
1022
1138
 
1023
1139
  async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
1024
- const totalSize = getTotalSize([zipEntry], "uncompressedSize");
1025
- const writtenSizes = new Map();
1140
+ const { onstart, onprogress, onend } = options;
1026
1141
  const abortController = new AbortController();
1027
1142
  const { signal } = abortController;
1028
1143
  const releaseSignal = forwardAbort(options.signal, abortController);
1144
+ const getDataOptions = Object.assign({}, options, options.readerOptions, {
1145
+ signal,
1146
+ onstart: UNDEFINED_VALUE,
1147
+ onprogress: UNDEFINED_VALUE,
1148
+ onend: UNDEFINED_VALUE,
1149
+ preventClose: false
1150
+ });
1151
+ const totalSize = getTotalSize([zipEntry], entry => getExtractedSize(entry, getDataOptions.passThrough));
1029
1152
  const exportedEntryNames = [];
1153
+ let exportAborted = false;
1154
+ let writtenSize = 0;
1030
1155
  try {
1156
+ if (onstart) {
1157
+ await callHandler(onstart, totalSize);
1158
+ }
1031
1159
  await exportChildren(zipEntry, directoryHandle);
1160
+ if (onend) {
1161
+ await callHandler(onend, writtenSize);
1162
+ }
1032
1163
  } catch (error) {
1033
1164
  try {
1034
1165
  error.exportedEntryNames = exportedEntryNames;
@@ -1041,6 +1172,25 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
1041
1172
  }
1042
1173
  return directoryHandle;
1043
1174
 
1175
+ function createProgressWritable(writable) {
1176
+ const writer = writable.getWriter();
1177
+ return new WritableStream({
1178
+ async write(chunk) {
1179
+ await writer.write(chunk);
1180
+ writtenSize += chunk.length;
1181
+ if (onprogress) {
1182
+ await callHandler(onprogress, writtenSize, totalSize);
1183
+ }
1184
+ },
1185
+ close() {
1186
+ return writer.close();
1187
+ },
1188
+ abort(reason) {
1189
+ return writer.abort(reason);
1190
+ }
1191
+ });
1192
+ }
1193
+
1044
1194
  async function exportChildren(entry, parentHandle) {
1045
1195
  if (options.concurrent) {
1046
1196
  const results = await Promise.allSettled(entry.children.map(child => exportChild(child, parentHandle)));
@@ -1059,10 +1209,10 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
1059
1209
 
1060
1210
  async function exportChild(child, parentHandle) {
1061
1211
  if (signal.aborted) {
1062
- if (isExportAborted(signal.reason)) {
1212
+ if (exportAborted || isExportAborted(signal.reason)) {
1063
1213
  return;
1064
1214
  }
1065
- throw signal.reason;
1215
+ throw signal.reason === UNDEFINED_VALUE ? new DOMException(ERR_ABORTED, ABORT_ERROR_NAME) : signal.reason;
1066
1216
  }
1067
1217
  try {
1068
1218
  if (child.directory) {
@@ -1071,22 +1221,15 @@ async function exportFileSystemHandle(zipEntry, directoryHandle, options) {
1071
1221
  } else {
1072
1222
  const fileHandle = await parentHandle.getFileHandle(child.name, { create: true });
1073
1223
  const writable = await fileHandle.createWritable();
1074
- await child.getData({ writable }, Object.assign({}, options, {
1075
- signal,
1076
- onprogress: async progress => {
1077
- if (options.onprogress) {
1078
- writtenSizes.set(child.id, progress);
1079
- try {
1080
- await options.onprogress(Array.from(writtenSizes.values()).reduce((previousValue, currentValue) => previousValue + currentValue, 0), totalSize);
1081
- } catch {
1082
- // ignored
1083
- }
1084
- }
1085
- }
1086
- }));
1224
+ try {
1225
+ await child.getData({ writable: createProgressWritable(writable) }, getDataOptions);
1226
+ } catch (error) {
1227
+ throw exportAborted ? new Error(ERR_ABORT_EXPORT) : error;
1228
+ }
1087
1229
  exportedEntryNames.push(child.getRelativeName(zipEntry));
1088
1230
  }
1089
1231
  } catch (error) {
1232
+ exportAborted = true;
1090
1233
  abortController.abort(new Error(ERR_ABORT_EXPORT));
1091
1234
  try {
1092
1235
  if (error.entryName === UNDEFINED_VALUE) {
@@ -1158,6 +1301,21 @@ function resetFS(fs) {
1158
1301
  fs.root = new ZipDirectoryEntry(fs);
1159
1302
  }
1160
1303
 
1304
+ function collectChildren(directory, recursive) {
1305
+ const children = [];
1306
+ const pendingDirectories = [directory];
1307
+ let directoryIndex = 0;
1308
+ while (directoryIndex < pendingDirectories.length) {
1309
+ for (const child of pendingDirectories[directoryIndex++].children) {
1310
+ children.push(child);
1311
+ if (recursive) {
1312
+ pendingDirectories.push(child);
1313
+ }
1314
+ }
1315
+ }
1316
+ return children;
1317
+ }
1318
+
1161
1319
  function registerEntries(fs, entry) {
1162
1320
  const pendingEntries = [entry];
1163
1321
  while (pendingEntries.length) {