@zip.js/zip.js 2.8.34 → 2.8.36

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.
Files changed (52) hide show
  1. package/README.md +13 -1
  2. package/deno.json +5 -2
  3. package/dist/README.md +3 -1
  4. package/dist/zip-core.js +494 -221
  5. package/dist/zip-core.min.js +1 -1
  6. package/dist/zip-fs-core.js +471 -221
  7. package/dist/zip-fs-core.min.js +1 -1
  8. package/dist/zip-fs-native.js +498 -227
  9. package/dist/zip-fs-native.min.js +1 -1
  10. package/dist/zip-fs.js +500 -229
  11. package/dist/zip-fs.min.js +1 -1
  12. package/dist/zip-legacy.js +496 -223
  13. package/dist/zip-legacy.min.js +1 -1
  14. package/dist/zip-native.js +497 -224
  15. package/dist/zip-native.min.js +1 -1
  16. package/dist/zip-web-worker-native.js +1 -1
  17. package/dist/zip-web-worker.js +1 -1
  18. package/dist/zip.js +499 -226
  19. package/dist/zip.min.js +1 -1
  20. package/index-native.cjs +498 -227
  21. package/index-native.min.js +1 -1
  22. package/index.cjs +500 -229
  23. package/index.d.cts +61 -0
  24. package/index.d.ts +61 -0
  25. package/index.min.js +1 -1
  26. package/lib/core/codec-pool.js +2 -2
  27. package/lib/core/codec-worker.js +6 -2
  28. package/lib/core/configuration.js +22 -1
  29. package/lib/core/io.js +230 -56
  30. package/lib/core/streams/aes-crypto-stream.js +1 -10
  31. package/lib/core/streams/codec-stream.js +37 -16
  32. package/lib/core/streams/zip-entry-stream.js +19 -28
  33. package/lib/core/util/array.js +48 -0
  34. package/lib/core/web-worker-base.js +3 -3
  35. package/lib/core/web-worker-inline-native.js +1 -1
  36. package/lib/core/web-worker-inline-wasm.js +1 -1
  37. package/lib/core/zip-fs.js +2 -3
  38. package/lib/core/zip-reader.js +37 -40
  39. package/lib/core/zip-writer.js +58 -51
  40. package/lib/core/zlib-streams-inline.js +1 -1
  41. package/lib/zip-core-base.js +5 -3
  42. package/lib/zip-fs-native.js +2 -2
  43. package/lib/zip-fs-wasm.js +2 -2
  44. package/lib/zip-legacy.js +2 -2
  45. package/lib/zip-module-native.js +2 -2
  46. package/lib/zip-module-wasm.js +3 -3
  47. package/lib/zip-native.js +2 -2
  48. package/lib/zip-wasm.js +2 -2
  49. package/package.json +3 -3
  50. package/worker-message-property-names.js +3 -0
  51. package/.github/workflows/publish.yml +0 -30
  52. package/reserved-property-names.js +0 -50
@@ -63,8 +63,8 @@ async function runWorker(stream, workerOptions) {
63
63
  const { options, config } = workerOptions;
64
64
  const { transferStreams, useWebWorkers, useCompressionStream, compressed, signed, encrypted } = options;
65
65
  const { workerURI, maxWorkers } = config;
66
- workerOptions.transferStreams = transferStreams || transferStreams === UNDEFINED_VALUE;
67
- const streamCopy = !compressed && !signed && !encrypted && !workerOptions.transferStreams;
66
+ workerOptions.transferStreams = transferStreams || (transferStreams === UNDEFINED_VALUE && config.transferStreams);
67
+ const streamCopy = !compressed && !signed && !encrypted;
68
68
  workerOptions.useWebWorkers = !streamCopy && (useWebWorkers || (useWebWorkers === UNDEFINED_VALUE && config.useWebWorkers));
69
69
  workerOptions.workerURI = workerOptions.useWebWorkers && workerURI ? workerURI : UNDEFINED_VALUE;
70
70
  options.useCompressionStream = useCompressionStream || (useCompressionStream === UNDEFINED_VALUE && config.useCompressionStream);
@@ -34,6 +34,7 @@ import {
34
34
  FUNCTION_TYPE
35
35
  } from "./constants.js";
36
36
  import { getChunkSize } from "./configuration.js";
37
+ import { toExactUint8Array } from "./util/array.js";
37
38
  import {
38
39
  CODEC_DEFLATE,
39
40
  CodecStream,
@@ -203,7 +204,10 @@ async function runWorker({ options, readable, writable, onTaskFinished }, config
203
204
  }
204
205
  }
205
206
  codecStream = new CodecStream(options, config);
206
- await readable.pipeThrough(codecStream).pipeTo(writable, { preventClose: true, preventAbort: true });
207
+ await readable
208
+ .pipeThrough(codecStream)
209
+ .pipeThrough(new ChunkStream(getChunkSize(config)))
210
+ .pipeTo(writable, { preventClose: true, preventAbort: true });
207
211
  const {
208
212
  signature,
209
213
  inputSize,
@@ -402,7 +406,7 @@ function sendMessage(message, { worker, writer, transferStreams }) {
402
406
  const { value, readable, writable } = message;
403
407
  const transferables = [];
404
408
  if (value) {
405
- message.value = value.byteOffset || value.byteLength != value.buffer.byteLength ? new Uint8Array(value) : value;
409
+ message.value = toExactUint8Array(value);
406
410
  transferables.push(message.value.buffer);
407
411
  }
408
412
  if (transferStreams && transferStreamsSupported) {
@@ -51,6 +51,7 @@ const DEFAULT_CONFIGURATION = {
51
51
  workerStarvationTimeout: 5000,
52
52
  useWebWorkers: true,
53
53
  useCompressionStream: true,
54
+ transferStreams: true,
54
55
  CompressionStream: typeof CompressionStream != UNDEFINED_TYPE && CompressionStream,
55
56
  DecompressionStream: typeof DecompressionStream != UNDEFINED_TYPE && DecompressionStream
56
57
  };
@@ -65,16 +66,19 @@ const CONFIGURABLE_PROPERTY_NAMES = [
65
66
  "workerStarvationTimeout",
66
67
  "useCompressionStream",
67
68
  "useWebWorkers",
69
+ "transferStreams",
68
70
  "CompressionStream",
69
71
  "DecompressionStream",
70
72
  "CompressionStreamZlib",
71
73
  "DecompressionStreamZlib"
72
74
  ];
73
75
 
74
- const config = Object.assign({}, DEFAULT_CONFIGURATION);
76
+ const config = { ...DEFAULT_CONFIGURATION };
75
77
 
76
78
  export {
77
79
  configure,
80
+ setDefaultConfiguration,
81
+ resetConfiguration,
78
82
  getConfiguration,
79
83
  getChunkSize
80
84
  };
@@ -95,3 +99,20 @@ function configure(configuration) {
95
99
  }
96
100
  }
97
101
  }
102
+
103
+ function setDefaultConfiguration(configuration) {
104
+ for (const propertyName of CONFIGURABLE_PROPERTY_NAMES) {
105
+ const propertyValue = configuration[propertyName];
106
+ if (propertyValue !== UNDEFINED_VALUE) {
107
+ DEFAULT_CONFIGURATION[propertyName] = propertyValue;
108
+ }
109
+ }
110
+ configure(configuration);
111
+ }
112
+
113
+ function resetConfiguration() {
114
+ for (const propertyName of CONFIGURABLE_PROPERTY_NAMES) {
115
+ delete config[propertyName];
116
+ }
117
+ Object.assign(config, DEFAULT_CONFIGURATION);
118
+ }
package/lib/core/io.js CHANGED
@@ -31,15 +31,16 @@
31
31
 
32
32
  import {
33
33
  UNDEFINED_VALUE,
34
- INFINITY_VALUE,
35
34
  FUNCTION_TYPE,
36
35
  END_OF_CENTRAL_DIR_LENGTH,
37
36
  EMPTY_UINT8_ARRAY
38
37
  } from "./constants.js";
39
- import { getConfiguration } from "./configuration.js";
38
+ import { getConfiguration, getChunkSize } from "./configuration.js";
39
+ import { concat, toExactUint8Array } from "./util/array.js";
40
40
 
41
41
  const ERR_HTTP_STATUS = "HTTP error ";
42
42
  const ERR_HTTP_RANGE = "HTTP Range not supported";
43
+ const ERR_HTTP_RESOURCE_CHANGED = "HTTP resource changed";
43
44
  const ERR_ITERATOR_COMPLETED_TOO_SOON = "Writer iterator completed too soon";
44
45
  const ERR_WRITER_NOT_INITIALIZED = "Writer not initialized";
45
46
 
@@ -50,13 +51,16 @@ const HTTP_HEADER_CONTENT_RANGE = "Content-Range";
50
51
  const HTTP_HEADER_ACCEPT_RANGES = "Accept-Ranges";
51
52
  const HTTP_HEADER_RANGE = "Range";
52
53
  const HTTP_HEADER_CONTENT_TYPE = "Content-Type";
54
+ const HTTP_HEADER_ETAG = "Etag";
55
+ const HTTP_HEADER_LAST_MODIFIED = "Last-Modified";
53
56
  const HTTP_METHOD_HEAD = "HEAD";
54
57
  const HTTP_METHOD_GET = "GET";
55
58
  const HTTP_RANGE_UNIT = "bytes";
56
- const DEFAULT_CHUNK_SIZE = 64 * 1024;
57
59
  const DEFAULT_BUFFER_SIZE = 256 * 1024;
60
+ const DEFAULT_MAXIMUM_RANGE_SIZE = 16 * 1024 * 1024;
58
61
 
59
62
  const PROPERTY_NAME_WRITABLE = "writable";
63
+ const DISK_BOUNDARY = Symbol();
60
64
 
61
65
  class Stream {
62
66
 
@@ -75,13 +79,13 @@ class Reader extends Stream {
75
79
  return this.createReadable();
76
80
  }
77
81
 
78
- createReadable({ offset = 0, size, diskNumberStart, chunkSize = DEFAULT_CHUNK_SIZE } = {}) {
82
+ createReadable({ offset = 0, size, chunkSize = getChunkSize(getConfiguration()) } = {}) {
79
83
  const reader = this;
80
84
  let chunkOffset = 0;
81
85
  return new ReadableStream({
82
86
  async pull(controller) {
83
87
  const dataSize = size === UNDEFINED_VALUE ? chunkSize : Math.min(chunkSize, size - chunkOffset);
84
- const data = await readUint8Array(reader, offset + chunkOffset, dataSize, diskNumberStart);
88
+ const data = await readUint8Array(reader, offset + chunkOffset, dataSize);
85
89
  controller.enqueue(data);
86
90
  if ((chunkOffset + chunkSize > size) || (size === UNDEFINED_VALUE && !data.length && dataSize)) {
87
91
  controller.close();
@@ -103,7 +107,7 @@ class Writer extends Stream {
103
107
  if (!writer.initialized) {
104
108
  throw new Error(ERR_WRITER_NOT_INITIALIZED);
105
109
  }
106
- return writer.writeUint8Array(chunk);
110
+ return writer.writeUint8Array(toExactUint8Array(chunk));
107
111
  }
108
112
  });
109
113
  Object.defineProperty(writer, PROPERTY_NAME_WRITABLE, {
@@ -190,6 +194,27 @@ class Data64URIWriter extends Writer {
190
194
  }
191
195
  }
192
196
 
197
+ let blobSliceReliable;
198
+ let blobSliceProbe;
199
+
200
+ function probeBlobSliceReliability() {
201
+ blobSliceProbe = (async () => {
202
+ try {
203
+ const slicedBlob = new Blob([new Uint8Array(3)]).slice(1, 2);
204
+ const streamReader = slicedBlob.stream().getReader();
205
+ let streamedLength = 0;
206
+ let result = await streamReader.read();
207
+ while (!result.done) {
208
+ streamedLength += result.value.length;
209
+ result = await streamReader.read();
210
+ }
211
+ blobSliceReliable = streamedLength == 1;
212
+ } catch {
213
+ blobSliceReliable = false;
214
+ }
215
+ })();
216
+ }
217
+
193
218
  class BlobReader extends Reader {
194
219
 
195
220
  constructor(blob) {
@@ -198,14 +223,32 @@ class BlobReader extends Reader {
198
223
  blob,
199
224
  size: blob.size
200
225
  });
226
+ if (!blobSliceProbe) {
227
+ probeBlobSliceReliability();
228
+ }
229
+ }
230
+
231
+ createReadable(options) {
232
+ const reader = this;
233
+ const { blob, size } = reader;
234
+ const { offset = 0, size: readSize = size - offset } = options || {};
235
+ if (!offset && readSize >= size) {
236
+ return blob.stream();
237
+ }
238
+ if (blobSliceReliable) {
239
+ return blob.slice(offset, offset + readSize).stream();
240
+ }
241
+ return super.createReadable(options);
201
242
  }
202
243
 
203
244
  async readUint8Array(offset, length) {
204
245
  const reader = this;
205
246
  const offsetEnd = offset + length;
206
- const blob = offset || offsetEnd < reader.size ? reader.blob.slice(offset, offsetEnd) : reader.blob;
247
+ const readsWholeBlob = !offset && offsetEnd >= reader.size;
248
+ const blob = readsWholeBlob ? reader.blob : reader.blob.slice(offset, offsetEnd);
207
249
  let arrayBuffer = await blob.arrayBuffer();
208
- if (arrayBuffer.byteLength > length) {
250
+ const sliceIgnoredByBuggyImplementation = arrayBuffer.byteLength > length;
251
+ if (sliceIgnoredByBuggyImplementation) {
209
252
  arrayBuffer = arrayBuffer.slice(offset, offsetEnd);
210
253
  }
211
254
  return new Uint8Array(arrayBuffer);
@@ -286,6 +329,18 @@ class FetchReader extends Reader {
286
329
  super.init();
287
330
  }
288
331
 
332
+ createReadable(options) {
333
+ const reader = this;
334
+ const { useRangeHeader, forceRangeRequests, size } = reader;
335
+ if ((useRangeHeader || forceRangeRequests) && size !== UNDEFINED_VALUE) {
336
+ const { offset = 0, size: readSize = size - offset } = options || {};
337
+ if (readSize > 0 && offset < size) {
338
+ return createRangeReadable(reader, offset, Math.min(readSize, size - offset));
339
+ }
340
+ }
341
+ return super.createReadable(options);
342
+ }
343
+
289
344
  readUint8Array(index, length) {
290
345
  return readUint8ArrayHttpReader(this, index, length, sendFetchRequest, getFetchRequestData);
291
346
  }
@@ -314,6 +369,8 @@ function createHttpReader(httpReader, url, options) {
314
369
  useRangeHeader,
315
370
  forceRangeRequests,
316
371
  combineSizeEocd,
372
+ checkResourceChanges = true,
373
+ maximumRangeSize = DEFAULT_MAXIMUM_RANGE_SIZE,
317
374
  fetch
318
375
  } = options;
319
376
  options = Object.assign({}, options);
@@ -321,6 +378,8 @@ function createHttpReader(httpReader, url, options) {
321
378
  delete options.useRangeHeader;
322
379
  delete options.forceRangeRequests;
323
380
  delete options.combineSizeEocd;
381
+ delete options.checkResourceChanges;
382
+ delete options.maximumRangeSize;
324
383
  delete options.useXHR;
325
384
  delete options.fetch;
326
385
  Object.assign(httpReader, {
@@ -330,6 +389,8 @@ function createHttpReader(httpReader, url, options) {
330
389
  useRangeHeader,
331
390
  forceRangeRequests,
332
391
  combineSizeEocd,
392
+ checkResourceChanges,
393
+ maximumRangeSize,
333
394
  fetch
334
395
  });
335
396
  }
@@ -354,17 +415,8 @@ async function initHttpReader(httpReader, sendRequest, getRequestData) {
354
415
  httpReader.eocdCache = eocdCache;
355
416
  }
356
417
  }
357
- let contentSize;
358
- const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
359
- if (contentRangeHeader) {
360
- const splitHeader = contentRangeHeader.trim().split(/\s*\/\s*/);
361
- if (splitHeader.length) {
362
- const headerValue = splitHeader[1];
363
- if (headerValue && headerValue != "*") {
364
- contentSize = Number(headerValue);
365
- }
366
- }
367
- }
418
+ setResourceValidators(httpReader, response);
419
+ const contentSize = getContentRangeSize(response);
368
420
  if (contentSize === UNDEFINED_VALUE) {
369
421
  await getContentLength(httpReader, sendRequest, getRequestData);
370
422
  } else {
@@ -405,6 +457,8 @@ async function readUint8ArrayHttpReader(httpReader, index, length, sendRequest,
405
457
  throw new Error(ERR_HTTP_RANGE);
406
458
  }
407
459
  }
460
+ checkResourceValidators(httpReader, response);
461
+ setResourceValidators(httpReader, response);
408
462
  const data = new Uint8Array(await response.arrayBuffer());
409
463
  if (data.length != length) {
410
464
  throw new Error(ERR_HTTP_RANGE);
@@ -416,7 +470,114 @@ async function readUint8ArrayHttpReader(httpReader, index, length, sendRequest,
416
470
  if (!data) {
417
471
  await getRequestData(httpReader, options);
418
472
  }
419
- return new Uint8Array(httpReader.data.subarray(index, index + length));
473
+ return httpReader.data.subarray(index, index + length);
474
+ }
475
+ }
476
+
477
+ function createRangeReadable(httpReader, offset, size) {
478
+ let bodyReader;
479
+ let windowOffset = offset;
480
+ let windowRemainingLength = 0;
481
+ let remainingLength = size;
482
+ return new ReadableStream({
483
+ start() {
484
+ return openWindow();
485
+ },
486
+ async pull(controller) {
487
+ if (!bodyReader) {
488
+ await openWindow();
489
+ }
490
+ const { value, done } = await bodyReader.read();
491
+ if (done) {
492
+ throw new Error(ERR_HTTP_RANGE);
493
+ }
494
+ const chunk = value.length > windowRemainingLength ? value.subarray(0, windowRemainingLength) : value;
495
+ windowRemainingLength -= chunk.length;
496
+ remainingLength -= chunk.length;
497
+ if (chunk.length) {
498
+ controller.enqueue(chunk);
499
+ }
500
+ if (!windowRemainingLength) {
501
+ await closeWindow();
502
+ if (!remainingLength) {
503
+ controller.close();
504
+ }
505
+ }
506
+ },
507
+ cancel(reason) {
508
+ return bodyReader && bodyReader.cancel(reason);
509
+ }
510
+ });
511
+
512
+ async function openWindow() {
513
+ const windowLength = Math.min(httpReader.maximumRangeSize, remainingLength);
514
+ const response = await sendFetchRequest(HTTP_METHOD_GET, httpReader, getRangeHeaders(httpReader, windowOffset, windowLength));
515
+ if (response.status != 206) {
516
+ throw new Error(ERR_HTTP_RANGE);
517
+ }
518
+ const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
519
+ if (contentRangeHeader) {
520
+ const rangeStart = Number(contentRangeHeader.trim().split(/[\s-]+/)[1]);
521
+ if (!Number.isNaN(rangeStart) && rangeStart != windowOffset) {
522
+ throw new Error(ERR_HTTP_RANGE);
523
+ }
524
+ }
525
+ checkResourceValidators(httpReader, response);
526
+ setResourceValidators(httpReader, response);
527
+ windowOffset += windowLength;
528
+ windowRemainingLength = windowLength;
529
+ bodyReader = response.body.getReader();
530
+ }
531
+
532
+ async function closeWindow() {
533
+ const currentBodyReader = bodyReader;
534
+ bodyReader = UNDEFINED_VALUE;
535
+ await currentBodyReader.cancel();
536
+ }
537
+ }
538
+
539
+ function getContentRangeSize(response) {
540
+ const contentRangeHeader = response.headers.get(HTTP_HEADER_CONTENT_RANGE);
541
+ if (contentRangeHeader) {
542
+ const headerValue = contentRangeHeader.trim().split(/\s*\/\s*/)[1];
543
+ if (headerValue && headerValue != "*") {
544
+ const contentSize = Number(headerValue);
545
+ if (!Number.isNaN(contentSize)) {
546
+ return contentSize;
547
+ }
548
+ }
549
+ }
550
+ }
551
+
552
+ function getResourceValidators({ headers }) {
553
+ return {
554
+ etag: headers.get(HTTP_HEADER_ETAG) || UNDEFINED_VALUE,
555
+ lastModified: headers.get(HTTP_HEADER_LAST_MODIFIED) || UNDEFINED_VALUE
556
+ };
557
+ }
558
+
559
+ function setResourceValidators(httpReader, response) {
560
+ const { checkResourceChanges, resourceValidators } = httpReader;
561
+ if (checkResourceChanges && !resourceValidators && response.status == 206) {
562
+ httpReader.resourceValidators = getResourceValidators(response);
563
+ }
564
+ }
565
+
566
+ function checkResourceValidators(httpReader, response) {
567
+ const { checkResourceChanges, resourceValidators, size } = httpReader;
568
+ if (checkResourceChanges) {
569
+ const contentRangeSize = getContentRangeSize(response);
570
+ if (contentRangeSize !== UNDEFINED_VALUE && size !== UNDEFINED_VALUE && contentRangeSize != size) {
571
+ throw new Error(ERR_HTTP_RESOURCE_CHANGED);
572
+ }
573
+ if (resourceValidators) {
574
+ const validators = getResourceValidators(response);
575
+ const changed = Object.entries(resourceValidators).some(([name, value]) =>
576
+ value !== UNDEFINED_VALUE && validators[name] !== UNDEFINED_VALUE && value != validators[name]);
577
+ if (changed) {
578
+ throw new Error(ERR_HTTP_RESOURCE_CHANGED);
579
+ }
580
+ }
420
581
  }
421
582
  }
422
583
 
@@ -527,6 +688,10 @@ class HttpReader extends Reader {
527
688
  super.init();
528
689
  }
529
690
 
691
+ createReadable(options) {
692
+ return this.reader.createReadable(options);
693
+ }
694
+
530
695
  readUint8Array(index, length) {
531
696
  return this.reader.readUint8Array(index, length);
532
697
  }
@@ -607,25 +772,26 @@ class SplitDataReader extends Reader {
607
772
  const reader = this;
608
773
  const { readers } = reader;
609
774
  reader.lastDiskNumber = 0;
610
- reader.lastDiskOffset = 0;
611
- await Promise.all(readers.map(async (diskReader, indexDiskReader) => {
612
- await initStream(diskReader);
613
- if (indexDiskReader != readers.length - 1) {
614
- reader.lastDiskOffset += diskReader.size;
615
- }
775
+ await Promise.all(readers.map(diskReader => initStream(diskReader)));
776
+ reader.diskOffsets = readers.map(diskReader => {
777
+ const diskOffset = reader.size;
616
778
  reader.size += diskReader.size;
617
- }));
779
+ return diskOffset;
780
+ });
618
781
  super.init();
619
782
  }
620
783
 
621
- async readUint8Array(offset, length, diskNumber = 0) {
784
+ getDiskOffset(diskNumber) {
785
+ const { diskOffsets, size } = this;
786
+ const diskOffset = diskOffsets.at(diskNumber);
787
+ return diskOffset === UNDEFINED_VALUE ? size : diskOffset;
788
+ }
789
+
790
+ async readUint8Array(offset, length) {
622
791
  const reader = this;
623
792
  const { readers } = this;
624
793
  let result;
625
- let currentDiskNumber = diskNumber;
626
- if (currentDiskNumber == -1) {
627
- currentDiskNumber = readers.length - 1;
628
- }
794
+ let currentDiskNumber = 0;
629
795
  let currentReaderOffset = offset;
630
796
  while (readers[currentDiskNumber] && currentReaderOffset >= readers[currentDiskNumber].size) {
631
797
  currentReaderOffset -= readers[currentDiskNumber].size;
@@ -638,14 +804,9 @@ class SplitDataReader extends Reader {
638
804
  result = await readUint8Array(currentReader, currentReaderOffset, length);
639
805
  } else {
640
806
  const chunkLength = currentReaderSize - currentReaderOffset;
641
- result = new Uint8Array(length);
642
807
  const firstPart = await readUint8Array(currentReader, currentReaderOffset, chunkLength);
643
- result.set(firstPart, 0);
644
- const secondPart = await reader.readUint8Array(offset + chunkLength, length - chunkLength, diskNumber);
645
- result.set(secondPart, chunkLength);
646
- if (firstPart.length + secondPart.length < length) {
647
- result = result.subarray(0, firstPart.length + secondPart.length);
648
- }
808
+ const secondPart = await reader.readUint8Array(offset + chunkLength, length - chunkLength);
809
+ result = concat(firstPart, secondPart);
649
810
  }
650
811
  } else {
651
812
  result = EMPTY_UINT8_ARRAY;
@@ -670,6 +831,12 @@ class SplitDataWriter extends Stream {
670
831
  let diskSourceWriter, diskWritable, diskWriter;
671
832
  const writable = new WritableStream({
672
833
  async write(chunk) {
834
+ if (chunk === DISK_BOUNDARY) {
835
+ if (diskWriter) {
836
+ await endDisk();
837
+ }
838
+ return;
839
+ }
673
840
  const { availableSize } = writer;
674
841
  if (!diskWriter) {
675
842
  const { value, done } = await writerGenerator.next();
@@ -689,11 +856,7 @@ class SplitDataWriter extends Stream {
689
856
  await this.write(chunk);
690
857
  } else if (chunk.length >= availableSize) {
691
858
  await writeChunk(chunk.subarray(0, availableSize));
692
- await closeDisk();
693
- writer.diskOffset += diskSourceWriter.size;
694
- writer.diskNumber++;
695
- diskWriter = null;
696
- writer.availableSize = writer.maxSize;
859
+ await endDisk();
697
860
  if (chunk.length > availableSize) {
698
861
  await this.write(chunk.subarray(availableSize));
699
862
  }
@@ -704,7 +867,7 @@ class SplitDataWriter extends Stream {
704
867
  async close() {
705
868
  if (diskWriter) {
706
869
  await diskWriter.ready;
707
- await closeDisk();
870
+ await closeDiskWriter();
708
871
  }
709
872
  },
710
873
  async abort(reason) {
@@ -729,10 +892,28 @@ class SplitDataWriter extends Stream {
729
892
  }
730
893
  }
731
894
 
732
- async function closeDisk() {
895
+ async function endDisk() {
896
+ await closeDiskWriter();
897
+ writer.diskOffset += diskSourceWriter.size;
898
+ writer.diskNumber++;
899
+ diskWriter = null;
900
+ writer.availableSize = writer.maxSize;
901
+ }
902
+
903
+ async function closeDiskWriter() {
733
904
  await diskWriter.close();
734
905
  }
735
906
  }
907
+
908
+ async closeDisk() {
909
+ const streamWriter = this.writable.getWriter();
910
+ try {
911
+ await streamWriter.ready;
912
+ await streamWriter.write(DISK_BOUNDARY);
913
+ } finally {
914
+ streamWriter.releaseLock();
915
+ }
916
+ }
736
917
  }
737
918
 
738
919
  class GenericReader {
@@ -764,14 +945,6 @@ class GenericWriter {
764
945
  if (writer.size === UNDEFINED_VALUE) {
765
946
  writer.size = 0;
766
947
  }
767
- if (!(writer instanceof SplitDataWriter)) {
768
- Object.assign(writer, {
769
- diskNumber: 0,
770
- diskOffset: 0,
771
- availableSize: INFINITY_VALUE,
772
- maxSize: INFINITY_VALUE
773
- });
774
- }
775
948
  return writer;
776
949
  }
777
950
  }
@@ -790,8 +963,8 @@ async function initStream(stream, initSize) {
790
963
  }
791
964
  }
792
965
 
793
- function readUint8Array(reader, offset, size, diskNumber) {
794
- return reader.readUint8Array(offset, size, diskNumber);
966
+ function readUint8Array(reader, offset, size) {
967
+ return reader.readUint8Array(offset, size);
795
968
  }
796
969
 
797
970
  export {
@@ -814,6 +987,7 @@ export {
814
987
  SplitDataReader,
815
988
  SplitDataWriter,
816
989
  ERR_HTTP_RANGE,
990
+ ERR_HTTP_RESOURCE_CHANGED,
817
991
  ERR_ITERATOR_COMPLETED_TOO_SOON,
818
992
  ERR_WRITER_NOT_INITIALIZED
819
993
  };
@@ -31,6 +31,7 @@
31
31
 
32
32
  import { UNDEFINED_VALUE, UNDEFINED_TYPE, FUNCTION_TYPE, EMPTY_UINT8_ARRAY } from "../constants.js";
33
33
  import { encodeText } from "./../util/encode-text.js";
34
+ import { concat } from "./../util/array.js";
34
35
  import {
35
36
  cipher,
36
37
  codec,
@@ -291,16 +292,6 @@ function encodePassword(password, rawPassword) {
291
292
  }
292
293
  }
293
294
 
294
- function concat(leftArray, rightArray) {
295
- let array = leftArray;
296
- if (leftArray.length + rightArray.length) {
297
- array = new Uint8Array(leftArray.length + rightArray.length);
298
- array.set(leftArray, 0);
299
- array.set(rightArray, leftArray.length);
300
- }
301
- return array;
302
- }
303
-
304
295
  function expand(inputArray, length) {
305
296
  if (length && length > inputArray.length) {
306
297
  const array = inputArray;
@@ -133,33 +133,54 @@ class CodecStream extends TransformStream {
133
133
  class ChunkStream extends TransformStream {
134
134
 
135
135
  constructor(chunkSize) {
136
- let pendingChunk;
137
- if (!(chunkSize >= 1)) {
136
+ const pendingChunks = [];
137
+ let pendingLength = 0;
138
+ if (!Number.isFinite(chunkSize) || chunkSize < 1) {
138
139
  chunkSize = DEFAULT_CHUNK_SIZE;
139
140
  }
140
141
  super({
141
- transform,
142
+ transform(chunk, controller) {
143
+ pendingChunks.push(chunk);
144
+ pendingLength += chunk.length;
145
+ while (pendingLength > chunkSize) {
146
+ controller.enqueue(shiftChunk());
147
+ }
148
+ },
142
149
  flush(controller) {
143
- if (pendingChunk && pendingChunk.length) {
144
- controller.enqueue(pendingChunk);
150
+ if (pendingLength) {
151
+ controller.enqueue(concatChunks(pendingChunks, pendingLength));
145
152
  }
146
153
  }
147
154
  });
148
155
 
149
- function transform(chunk, controller) {
150
- if (pendingChunk) {
151
- const newChunk = new Uint8Array(pendingChunk.length + chunk.length);
152
- newChunk.set(pendingChunk);
153
- newChunk.set(chunk, pendingChunk.length);
154
- chunk = newChunk;
155
- pendingChunk = null;
156
+ function shiftChunk() {
157
+ const result = new Uint8Array(chunkSize);
158
+ let resultOffset = 0;
159
+ while (resultOffset < chunkSize) {
160
+ const firstChunk = pendingChunks[0];
161
+ const remainingLength = chunkSize - resultOffset;
162
+ if (firstChunk.length <= remainingLength) {
163
+ result.set(firstChunk, resultOffset);
164
+ resultOffset += firstChunk.length;
165
+ pendingChunks.shift();
166
+ } else {
167
+ result.set(firstChunk.subarray(0, remainingLength), resultOffset);
168
+ pendingChunks[0] = firstChunk.subarray(remainingLength);
169
+ resultOffset += remainingLength;
170
+ }
156
171
  }
172
+ pendingLength -= chunkSize;
173
+ return result;
174
+ }
175
+
176
+ function concatChunks(chunks, length) {
177
+ const result = new Uint8Array(length);
157
178
  let offset = 0;
158
- while (chunk.length - offset > chunkSize) {
159
- controller.enqueue(chunk.slice(offset, offset + chunkSize));
160
- offset += chunkSize;
179
+ for (const chunk of chunks) {
180
+ result.set(chunk, offset);
181
+ offset += chunk.length;
161
182
  }
162
- pendingChunk = offset ? chunk.slice(offset) : chunk;
183
+ return result;
163
184
  }
164
185
  }
165
186
  }