@absolutejs/absolute 0.20.0-beta.84 → 0.20.0-beta.85

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.
@@ -20760,14 +20760,18 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
20760
20760
  const expoCodeSigning = resolveExpoCodeSigning(options.expoCodeSigning);
20761
20761
  return async (request) => {
20762
20762
  const origin = request.headers.get("origin");
20763
- const cors = origin && allowedOrigins.has(origin) ? { "access-control-allow-origin": origin, vary: "Origin" } : {};
20763
+ const cors = origin && allowedOrigins.has(origin) ? {
20764
+ "access-control-allow-origin": origin,
20765
+ "access-control-expose-headers": "content-range,etag",
20766
+ vary: "Origin"
20767
+ } : {};
20764
20768
  if (request.method === "OPTIONS") {
20765
20769
  if (!origin || !allowedOrigins.has(origin))
20766
20770
  return new Response(null, { status: 403 });
20767
20771
  return new Response(null, {
20768
20772
  headers: {
20769
20773
  ...cors,
20770
- "access-control-allow-headers": "x-absolute-mobile-app,x-absolute-mobile-channel,x-absolute-mobile-installation,x-absolute-mobile-release,x-absolute-mobile-runtime",
20774
+ "access-control-allow-headers": "if-range,range,x-absolute-mobile-app,x-absolute-mobile-channel,x-absolute-mobile-installation,x-absolute-mobile-release,x-absolute-mobile-runtime",
20771
20775
  "access-control-allow-methods": "GET,OPTIONS",
20772
20776
  "access-control-max-age": "600"
20773
20777
  },
@@ -20895,14 +20899,42 @@ var MOBILE_UPDATE_REGISTRY_FORMAT = 1, DEFAULT_PREFIX = "absolutejs/mobile-updat
20895
20899
  });
20896
20900
  if (!file)
20897
20901
  return new Response(null, { status: 404 });
20898
- return new Response(new Blob([new Uint8Array(file.bytes).buffer]), {
20902
+ const etag = `"${file.file.sha256}"`;
20903
+ const range = request.headers.get("range");
20904
+ const useRange = range !== null && (!request.headers.has("if-range") || request.headers.get("if-range") === etag);
20905
+ let contents = file.bytes;
20906
+ let status = 200;
20907
+ let contentRange;
20908
+ if (useRange) {
20909
+ const parsed = /^bytes=(\d+)-(\d*)$/.exec(range);
20910
+ const start = parsed?.[1] === undefined ? NaN : Number(parsed[1]);
20911
+ const requestedEnd = parsed?.[2] === undefined || parsed[2] === "" ? file.bytes.byteLength - 1 : Number(parsed[2]);
20912
+ if (!Number.isSafeInteger(start) || !Number.isSafeInteger(requestedEnd) || start < 0 || start >= file.bytes.byteLength || requestedEnd < start)
20913
+ return new Response(null, {
20914
+ headers: {
20915
+ ...cors,
20916
+ "accept-ranges": "bytes",
20917
+ "content-range": `bytes */${file.bytes.byteLength}`,
20918
+ etag
20919
+ },
20920
+ status: 416
20921
+ });
20922
+ const end = Math.min(requestedEnd, file.bytes.byteLength - 1);
20923
+ contents = file.bytes.slice(start, end + 1);
20924
+ status = 206;
20925
+ contentRange = `bytes ${start}-${end}/${file.bytes.byteLength}`;
20926
+ }
20927
+ return new Response(new Blob([new Uint8Array(contents).buffer]), {
20899
20928
  headers: {
20900
20929
  ...cors,
20930
+ "accept-ranges": "bytes",
20901
20931
  "cache-control": "public, max-age=31536000, immutable",
20902
- "content-length": String(file.file.bytes),
20932
+ "content-length": String(contents.byteLength),
20933
+ ...contentRange ? { "content-range": contentRange } : {},
20903
20934
  "content-type": expoContentType(file.file.path.includes(".") ? file.file.path.slice(file.file.path.lastIndexOf(".") + 1) : undefined, false),
20904
- etag: `"${file.file.sha256}"`
20905
- }
20935
+ etag
20936
+ },
20937
+ status
20906
20938
  });
20907
20939
  };
20908
20940
  };
@@ -38705,17 +38737,18 @@ var fileUrl = (manifestUrl, releaseId, path) => {
38705
38737
  throw new TypeError("Mobile update asset escaped its signed release origin.");
38706
38738
  return result;
38707
38739
  };
38708
- var readChunks = async (reader, maximum2, chunks = [], received = 0) => {
38740
+ var readChunks = async (reader, maximum2, onChunk, chunks = [], received = 0) => {
38709
38741
  const result = await reader.read();
38710
38742
  if (result.done)
38711
38743
  return { chunks, received };
38712
38744
  const total = received + result.value.byteLength;
38713
38745
  if (total > maximum2)
38714
38746
  throw new TypeError("Mobile update response exceeds its signed size.");
38747
+ await onChunk?.(result.value, received);
38715
38748
  chunks.push(result.value);
38716
- return readChunks(reader, maximum2, chunks, total);
38749
+ return readChunks(reader, maximum2, onChunk, chunks, total);
38717
38750
  };
38718
- var readBounded = async (response, maximum2) => {
38751
+ var readBounded = async (response, maximum2, onChunk) => {
38719
38752
  const declared = Number(response.headers.get("content-length"));
38720
38753
  if (Number.isFinite(declared) && declared > maximum2)
38721
38754
  throw new TypeError("Mobile update response exceeds its signed size.");
@@ -38723,8 +38756,9 @@ var readBounded = async (response, maximum2) => {
38723
38756
  return new Uint8Array;
38724
38757
  const reader = response.body.getReader();
38725
38758
  let result;
38759
+ const chunks = [];
38726
38760
  try {
38727
- result = await readChunks(reader, maximum2);
38761
+ result = await readChunks(reader, maximum2, onChunk, chunks);
38728
38762
  } catch (error) {
38729
38763
  await reader.cancel().catch(() => {
38730
38764
  return;
@@ -38733,7 +38767,7 @@ var readBounded = async (response, maximum2) => {
38733
38767
  }
38734
38768
  const contents = new Uint8Array(result.received);
38735
38769
  let offset = 0;
38736
- for (const chunk of result.chunks) {
38770
+ for (const chunk of chunks) {
38737
38771
  contents.set(chunk, offset);
38738
38772
  offset += chunk.byteLength;
38739
38773
  }
@@ -38754,49 +38788,172 @@ var requireCompatible = (manifest, config) => {
38754
38788
  if (manifest.runtimeFingerprint !== config.runtimeFingerprint)
38755
38789
  throw new TypeError("Mobile update requires a different native runtime.");
38756
38790
  };
38791
+ var networkConcurrency = (requested) => {
38792
+ const bounded = Math.max(1, Math.min(6, Math.floor(requested ?? 3)));
38793
+ const navigatorValue = Reflect.get(globalThis, "navigator");
38794
+ const connection = typeof navigatorValue === "object" && navigatorValue !== null ? Reflect.get(navigatorValue, "connection") : undefined;
38795
+ if (typeof connection !== "object" || connection === null)
38796
+ return bounded;
38797
+ if (Reflect.get(connection, "saveData") === true)
38798
+ return 1;
38799
+ const effectiveType = Reflect.get(connection, "effectiveType");
38800
+ if (effectiveType === "slow-2g" || effectiveType === "2g")
38801
+ return 1;
38802
+ if (effectiveType === "3g")
38803
+ return Math.min(2, bounded);
38804
+ return bounded;
38805
+ };
38806
+ var combine = (prefix, suffix) => {
38807
+ const result = new Uint8Array(prefix.byteLength + suffix.byteLength);
38808
+ result.set(prefix);
38809
+ result.set(suffix, prefix.byteLength);
38810
+ return result;
38811
+ };
38812
+ var validContentRange = (value, start, total) => value === `bytes ${start}-${total - 1}/${total}`;
38813
+ var combineSignals = (signals) => {
38814
+ const nativeAny = Reflect.get(AbortSignal, "any");
38815
+ if (typeof nativeAny === "function")
38816
+ return Reflect.apply(nativeAny, AbortSignal, [signals]);
38817
+ const controller = new AbortController;
38818
+ const abort = () => controller.abort();
38819
+ if (signals.some((signal) => signal.aborted))
38820
+ abort();
38821
+ else
38822
+ signals.forEach((signal) => signal.addEventListener("abort", abort, { once: true }));
38823
+ return controller.signal;
38824
+ };
38757
38825
  var createAbsoluteMobileUpdateClient = (options) => {
38758
38826
  const manifestUrl = exactManifestUrl(options.config.manifestUrl);
38759
38827
  const request = options.fetch ?? globalThis.fetch;
38760
- const downloadFiles = async (manifest, index = 0, transfer = {
38761
- downloadedBytes: 0,
38762
- downloadedFiles: 0,
38763
- reusedBytes: 0,
38764
- reusedFiles: 0,
38765
- totalBytes: manifest.files.reduce((total, file) => total + file.bytes, 0),
38766
- totalFiles: manifest.files.length
38767
- }) => {
38768
- const file = manifest.files[index];
38769
- if (!file)
38770
- return transfer;
38771
- const reusable = await options.store.readReusable?.(file);
38772
- if (reusable && reusable.byteLength === file.bytes && await options.verifier.digest(reusable) === file.sha256) {
38773
- await options.store.write(file, reusable);
38774
- return downloadFiles(manifest, index + 1, {
38775
- ...transfer,
38776
- reusedBytes: transfer.reusedBytes + reusable.byteLength,
38777
- reusedFiles: transfer.reusedFiles + 1
38828
+ const downloadFiles = async (manifest) => {
38829
+ const startedAt = performance.now();
38830
+ const transfer = {
38831
+ avoidedBytes: 0,
38832
+ completedFiles: 0,
38833
+ downloadedBytes: 0,
38834
+ downloadedFiles: 0,
38835
+ durationMs: 0,
38836
+ resumedBytes: 0,
38837
+ resumedFiles: 0,
38838
+ reusedBytes: 0,
38839
+ reusedFiles: 0,
38840
+ throughputBytesPerSecond: 0,
38841
+ totalBytes: manifest.files.reduce((total, file) => total + file.bytes, 0),
38842
+ totalFiles: manifest.files.length
38843
+ };
38844
+ const updateTiming = () => {
38845
+ transfer.durationMs = Math.max(0, performance.now() - startedAt);
38846
+ transfer.avoidedBytes = transfer.reusedBytes + transfer.resumedBytes;
38847
+ transfer.throughputBytesPerSecond = transfer.durationMs > 0 ? Math.round(transfer.downloadedBytes * 1000 / transfer.durationMs) : transfer.downloadedBytes;
38848
+ };
38849
+ const progress = () => {
38850
+ updateTiming();
38851
+ try {
38852
+ options.onProgress?.({
38853
+ ...transfer,
38854
+ kind: "download-progress"
38855
+ });
38856
+ } catch {}
38857
+ };
38858
+ const controller = new AbortController;
38859
+ let next = 0;
38860
+ let firstError;
38861
+ const downloadFile = async (file) => {
38862
+ const staged = await options.store.readStaged?.(file);
38863
+ if (staged?.byteLength === file.bytes && await options.verifier.digest(staged) === file.sha256) {
38864
+ transfer.resumedBytes += staged.byteLength;
38865
+ transfer.resumedFiles += 1;
38866
+ transfer.completedFiles += 1;
38867
+ progress();
38868
+ return;
38869
+ }
38870
+ const reusable = await options.store.readReusable?.(file);
38871
+ if (reusable?.byteLength === file.bytes && await options.verifier.digest(reusable) === file.sha256) {
38872
+ await options.store.write(file, reusable);
38873
+ transfer.reusedBytes += reusable.byteLength;
38874
+ transfer.reusedFiles += 1;
38875
+ transfer.completedFiles += 1;
38876
+ progress();
38877
+ return;
38878
+ }
38879
+ const candidate = await options.store.readPartial?.(file);
38880
+ if (candidate?.byteLength === file.bytes && await options.verifier.digest(candidate) === file.sha256) {
38881
+ await options.store.write(file, candidate);
38882
+ transfer.resumedBytes += candidate.byteLength;
38883
+ transfer.resumedFiles += 1;
38884
+ transfer.completedFiles += 1;
38885
+ progress();
38886
+ return;
38887
+ }
38888
+ const partial = candidate && candidate.byteLength > 0 && candidate.byteLength < file.bytes ? candidate : new Uint8Array;
38889
+ const headers = new Headers;
38890
+ if (partial.byteLength > 0) {
38891
+ headers.set("if-range", `"${file.sha256}"`);
38892
+ headers.set("range", `bytes=${partial.byteLength}-`);
38893
+ }
38894
+ const asset2 = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
38895
+ cache: "no-store",
38896
+ credentials: "omit",
38897
+ headers,
38898
+ redirect: "error",
38899
+ signal: combineSignals([
38900
+ controller.signal,
38901
+ AbortSignal.timeout(30000)
38902
+ ])
38778
38903
  });
38779
- }
38780
- const asset2 = await request(fileUrl(manifestUrl, manifest.releaseId, file.path), {
38781
- cache: "no-store",
38782
- credentials: "omit",
38783
- redirect: "error",
38784
- signal: AbortSignal.timeout(30000)
38785
- });
38786
- if (!asset2.ok)
38787
- throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset2.status}.`);
38788
- const contents = await readBounded(asset2, file.bytes);
38789
- const downloadedBytes = transfer.downloadedBytes + contents.byteLength;
38790
- if (contents.byteLength !== file.bytes || downloadedBytes > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
38791
- throw new TypeError(`Mobile update asset ${file.path} has an invalid size.`);
38792
- if (await options.verifier.digest(contents) !== file.sha256)
38793
- throw new TypeError(`Mobile update asset ${file.path} failed integrity verification.`);
38794
- await options.store.write(file, contents);
38795
- return downloadFiles(manifest, index + 1, {
38796
- ...transfer,
38797
- downloadedBytes,
38798
- downloadedFiles: transfer.downloadedFiles + 1
38799
- });
38904
+ if (!asset2.ok)
38905
+ throw new TypeError(`Mobile update asset ${file.path} failed with HTTP ${asset2.status}.`);
38906
+ const ranged = asset2.status === 206;
38907
+ if (ranged && (partial.byteLength === 0 || !validContentRange(asset2.headers.get("content-range"), partial.byteLength, file.bytes)))
38908
+ throw new TypeError(`Mobile update asset ${file.path} returned an invalid byte range.`);
38909
+ const prefix = ranged ? partial : new Uint8Array;
38910
+ if (ranged) {
38911
+ transfer.resumedBytes += partial.byteLength;
38912
+ transfer.resumedFiles += 1;
38913
+ }
38914
+ const downloaded = await readBounded(asset2, file.bytes - prefix.byteLength, options.store.appendPartial ? async (chunk, offset) => {
38915
+ await options.store.appendPartial?.(file, chunk, prefix.byteLength + offset);
38916
+ transfer.downloadedBytes += chunk.byteLength;
38917
+ progress();
38918
+ } : undefined);
38919
+ if (!options.store.appendPartial) {
38920
+ transfer.downloadedBytes += downloaded.byteLength;
38921
+ progress();
38922
+ }
38923
+ if (transfer.downloadedBytes > ABSOLUTE_MOBILE_UPDATE_MAX_TOTAL_BYTES)
38924
+ throw new TypeError("Mobile update exceeds the maximum transfer size.");
38925
+ const contents = combine(prefix, downloaded);
38926
+ if (contents.byteLength !== file.bytes)
38927
+ throw new TypeError(`Mobile update asset ${file.path} has an invalid size.`);
38928
+ if (await options.verifier.digest(contents) !== file.sha256)
38929
+ throw new TypeError(`Mobile update asset ${file.path} failed integrity verification.`);
38930
+ await options.store.write(file, contents);
38931
+ transfer.downloadedFiles += 1;
38932
+ transfer.completedFiles += 1;
38933
+ progress();
38934
+ };
38935
+ const worker = async () => {
38936
+ if (firstError)
38937
+ return;
38938
+ const index = next++;
38939
+ const file = manifest.files[index];
38940
+ if (!file)
38941
+ return;
38942
+ try {
38943
+ await downloadFile(file);
38944
+ } catch (error) {
38945
+ firstError ??= error;
38946
+ controller.abort();
38947
+ }
38948
+ await worker();
38949
+ };
38950
+ await Promise.all(Array.from({
38951
+ length: Math.min(networkConcurrency(options.concurrency), manifest.files.length)
38952
+ }, () => worker()));
38953
+ if (firstError)
38954
+ throw firstError;
38955
+ updateTiming();
38956
+ return transfer;
38800
38957
  };
38801
38958
  const check = async (download = false) => {
38802
38959
  const response = await request(manifestUrl, {
@@ -38833,7 +38990,10 @@ var createAbsoluteMobileUpdateClient = (options) => {
38833
38990
  transfer = await downloadFiles(manifest);
38834
38991
  await options.store.commit(manifest);
38835
38992
  } catch (error) {
38836
- await options.store.abort(manifest.releaseId);
38993
+ if (options.store.suspend)
38994
+ await options.store.suspend(manifest.releaseId);
38995
+ else
38996
+ await options.store.abort(manifest.releaseId);
38837
38997
  throw error;
38838
38998
  }
38839
38999
  return { kind: "downloaded", manifest, transfer };
@@ -39333,5 +39493,5 @@ export {
39333
39493
  writeAbsoluteMobileUpdateRegistry
39334
39494
  };
39335
39495
 
39336
- //# debugId=A051836D5390197D64756E2164756E21
39496
+ //# debugId=B713A6D1CA49D10364756E2164756E21
39337
39497
  //# sourceMappingURL=index.js.map