@hot-updater/js 0.20.11 → 0.20.12

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/dist/index.cjs CHANGED
@@ -419,8 +419,7 @@ var require_diff = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semver
419
419
  const highVersion = v1Higher ? v1 : v2;
420
420
  const lowVersion = v1Higher ? v2 : v1;
421
421
  const highHasPre = !!highVersion.prerelease.length;
422
- const lowHasPre = !!lowVersion.prerelease.length;
423
- if (lowHasPre && !highHasPre) {
422
+ if (!!lowVersion.prerelease.length && !highHasPre) {
424
423
  if (!lowVersion.patch && !lowVersion.minor) return "major";
425
424
  if (lowVersion.compareMain(highVersion) === 0) {
426
425
  if (lowVersion.minor && !lowVersion.patch) return "minor";
@@ -628,11 +627,7 @@ var require_coerce = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semv
628
627
  }
629
628
  if (match === null) return null;
630
629
  const major$2 = match[2];
631
- const minor$2 = match[3] || "0";
632
- const patch$2 = match[4] || "0";
633
- const prerelease$2 = options.includePrerelease && match[5] ? `-${match[5]}` : "";
634
- const build = options.includePrerelease && match[6] ? `+${match[6]}` : "";
635
- return parse$1(`${major$2}.${minor$2}.${patch$2}${prerelease$2}${build}`, options);
630
+ return parse$1(`${major$2}.${match[3] || "0"}.${match[4] || "0"}${options.includePrerelease && match[5] ? `-${match[5]}` : ""}${options.includePrerelease && match[6] ? `+${match[6]}` : ""}`, options);
636
631
  };
637
632
  module.exports = coerce$1;
638
633
  }) });
@@ -647,7 +642,7 @@ var require_lrucache = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/se
647
642
  }
648
643
  get(key) {
649
644
  const value = this.map.get(key);
650
- if (value === void 0) return void 0;
645
+ if (value === void 0) return;
651
646
  else {
652
647
  this.map.delete(key);
653
648
  this.map.set(key, value);
@@ -658,8 +653,7 @@ var require_lrucache = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/se
658
653
  return this.map.delete(key);
659
654
  }
660
655
  set(key, value) {
661
- const deleted = this.delete(key);
662
- if (!deleted && value !== void 0) {
656
+ if (!this.delete(key) && value !== void 0) {
663
657
  if (this.map.size >= this.max) {
664
658
  const firstKey = this.map.keys().next().value;
665
659
  this.delete(firstKey);
@@ -727,8 +721,7 @@ var require_range = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semve
727
721
  return this.range;
728
722
  }
729
723
  parseRange(range) {
730
- const memoOpts = (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE);
731
- const memoKey = memoOpts + ":" + range;
724
+ const memoKey = ((this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE)) + ":" + range;
732
725
  const cached = cache$1.get(memoKey);
733
726
  if (cached) return cached;
734
727
  const loose = this.options.loose;
@@ -782,8 +775,7 @@ var require_range = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semve
782
775
  }
783
776
  };
784
777
  module.exports = Range$11;
785
- const LRU = require_lrucache();
786
- const cache$1 = new LRU();
778
+ const cache$1 = new (require_lrucache())();
787
779
  const parseOptions$1 = require_parse_options();
788
780
  const Comparator$4 = require_comparator();
789
781
  const debug$1 = require_debug();
@@ -1242,16 +1234,13 @@ var require_simplify = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/se
1242
1234
  let first = null;
1243
1235
  let prev = null;
1244
1236
  const v = versions.sort((a, b) => compare$2(a, b, options));
1245
- for (const version of v) {
1246
- const included = satisfies$2(version, range, options);
1247
- if (included) {
1248
- prev = version;
1249
- if (!first) first = version;
1250
- } else {
1251
- if (prev) set.push([first, prev]);
1252
- prev = null;
1253
- first = null;
1254
- }
1237
+ for (const version of v) if (satisfies$2(version, range, options)) {
1238
+ prev = version;
1239
+ if (!first) first = version;
1240
+ } else {
1241
+ if (prev) set.push([first, prev]);
1242
+ prev = null;
1243
+ first = null;
1255
1244
  }
1256
1245
  if (first) set.push([first, null]);
1257
1246
  const ranges = [];
@@ -1464,6 +1453,20 @@ const semverSatisfies = (targetAppVersion, currentVersion) => {
1464
1453
  return import_semver.default.satisfies(currentCoerce.version, targetAppVersion);
1465
1454
  };
1466
1455
 
1456
+ //#endregion
1457
+ //#region src/filterCompatibleAppVersions.ts
1458
+ /**
1459
+ * Filters target app versions that are compatible with the current app version.
1460
+ * Returns only versions that are compatible with the current version according to semver rules.
1461
+ *
1462
+ * @param targetAppVersionList - List of target app versions to filter
1463
+ * @param currentVersion - Current app version
1464
+ * @returns Array of target app versions compatible with the current version
1465
+ */
1466
+ const filterCompatibleAppVersions = (targetAppVersionList, currentVersion) => {
1467
+ return targetAppVersionList.filter((version) => semverSatisfies(version, currentVersion)).sort((a, b) => b.localeCompare(a));
1468
+ };
1469
+
1467
1470
  //#endregion
1468
1471
  //#region src/getUpdateInfo.ts
1469
1472
  const INIT_BUNDLE_ROLLBACK_UPDATE_INFO = {
@@ -1500,7 +1503,7 @@ const appVersionStrategy = async (bundles, { channel = "production", minBundleId
1500
1503
  let latestCandidate = null;
1501
1504
  let updateCandidate = null;
1502
1505
  let rollbackCandidate = null;
1503
- let currentBundle = void 0;
1506
+ let currentBundle;
1504
1507
  for (const b of candidateBundles) {
1505
1508
  if (!latestCandidate || b.id.localeCompare(latestCandidate.id) > 0) latestCandidate = b;
1506
1509
  if (b.id === bundleId) currentBundle = b;
@@ -1538,7 +1541,7 @@ const fingerprintStrategy = async (bundles, { channel = "production", minBundleI
1538
1541
  let latestCandidate = null;
1539
1542
  let updateCandidate = null;
1540
1543
  let rollbackCandidate = null;
1541
- let currentBundle = void 0;
1544
+ let currentBundle;
1542
1545
  for (const b of candidateBundles) {
1543
1546
  if (!latestCandidate || b.id.localeCompare(latestCandidate.id) > 0) latestCandidate = b;
1544
1547
  if (b.id === bundleId) currentBundle = b;
@@ -1564,21 +1567,6 @@ const fingerprintStrategy = async (bundles, { channel = "production", minBundleI
1564
1567
  return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
1565
1568
  };
1566
1569
 
1567
- //#endregion
1568
- //#region src/filterCompatibleAppVersions.ts
1569
- /**
1570
- * Filters target app versions that are compatible with the current app version.
1571
- * Returns only versions that are compatible with the current version according to semver rules.
1572
- *
1573
- * @param targetAppVersionList - List of target app versions to filter
1574
- * @param currentVersion - Current app version
1575
- * @returns Array of target app versions compatible with the current version
1576
- */
1577
- const filterCompatibleAppVersions = (targetAppVersionList, currentVersion) => {
1578
- const compatibleAppVersionList = targetAppVersionList.filter((version) => semverSatisfies(version, currentVersion));
1579
- return compatibleAppVersionList.sort((a, b) => b.localeCompare(a));
1580
- };
1581
-
1582
1570
  //#endregion
1583
1571
  //#region ../../node_modules/.pnpm/jose@6.0.10/node_modules/jose/dist/webapi/lib/buffer_utils.js
1584
1572
  const encoder = new TextEncoder();
@@ -1741,8 +1729,7 @@ function checkSigCryptoKey(key, alg, usage) {
1741
1729
  case "HS512": {
1742
1730
  if (!isAlgorithm(key.algorithm, "HMAC")) throw unusable("HMAC");
1743
1731
  const expected = parseInt(alg.slice(2), 10);
1744
- const actual = getHashLength(key.algorithm.hash);
1745
- if (actual !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1732
+ if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1746
1733
  break;
1747
1734
  }
1748
1735
  case "RS256":
@@ -1750,8 +1737,7 @@ function checkSigCryptoKey(key, alg, usage) {
1750
1737
  case "RS512": {
1751
1738
  if (!isAlgorithm(key.algorithm, "RSASSA-PKCS1-v1_5")) throw unusable("RSASSA-PKCS1-v1_5");
1752
1739
  const expected = parseInt(alg.slice(2), 10);
1753
- const actual = getHashLength(key.algorithm.hash);
1754
- if (actual !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1740
+ if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1755
1741
  break;
1756
1742
  }
1757
1743
  case "PS256":
@@ -1759,8 +1745,7 @@ function checkSigCryptoKey(key, alg, usage) {
1759
1745
  case "PS512": {
1760
1746
  if (!isAlgorithm(key.algorithm, "RSA-PSS")) throw unusable("RSA-PSS");
1761
1747
  const expected = parseInt(alg.slice(2), 10);
1762
- const actual = getHashLength(key.algorithm.hash);
1763
- if (actual !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1748
+ if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1764
1749
  break;
1765
1750
  }
1766
1751
  case "Ed25519":
@@ -1772,8 +1757,7 @@ function checkSigCryptoKey(key, alg, usage) {
1772
1757
  case "ES512": {
1773
1758
  if (!isAlgorithm(key.algorithm, "ECDSA")) throw unusable("ECDSA");
1774
1759
  const expected = getNamedCurve(alg);
1775
- const actual = key.algorithm.namedCurve;
1776
- if (actual !== expected) throw unusable(expected, "algorithm.namedCurve");
1760
+ if (key.algorithm.namedCurve !== expected) throw unusable(expected, "algorithm.namedCurve");
1777
1761
  break;
1778
1762
  }
1779
1763
  default: throw new TypeError("CryptoKey does not support this operation");
@@ -1987,7 +1971,7 @@ var validate_crit_default = (Err, recognizedDefault, recognizedOption, protected
1987
1971
  //#region ../../node_modules/.pnpm/jose@6.0.10/node_modules/jose/dist/webapi/lib/validate_algorithms.js
1988
1972
  var validate_algorithms_default = (option, algorithms) => {
1989
1973
  if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== "string"))) throw new TypeError(`"${option}" option must be an array of strings`);
1990
- if (!algorithms) return void 0;
1974
+ if (!algorithms) return;
1991
1975
  return new Set(algorithms);
1992
1976
  };
1993
1977
 
@@ -2076,12 +2060,11 @@ const handleKeyObject = (keyObject, alg) => {
2076
2060
  }, extractable, [isPublic ? "verify" : "sign"]);
2077
2061
  }
2078
2062
  if (keyObject.asymmetricKeyType === "ec") {
2079
- const nist = new Map([
2063
+ const namedCurve = new Map([
2080
2064
  ["prime256v1", "P-256"],
2081
2065
  ["secp384r1", "P-384"],
2082
2066
  ["secp521r1", "P-521"]
2083
- ]);
2084
- const namedCurve = nist.get(keyObject.asymmetricKeyDetails?.namedCurve);
2067
+ ]).get(keyObject.asymmetricKeyDetails?.namedCurve);
2085
2068
  if (!namedCurve) throw new TypeError("given KeyObject instance cannot be used for this algorithm");
2086
2069
  if (alg === "ES256" && namedCurve === "P-256") cryptoKey = keyObject.toCryptoKey({
2087
2070
  name: "ECDSA",
@@ -2115,8 +2098,7 @@ var normalize_key_default = async (key, alg) => {
2115
2098
  } catch (err) {
2116
2099
  if (err instanceof TypeError) throw err;
2117
2100
  }
2118
- let jwk = key.export({ format: "jwk" });
2119
- return handleJWK(key, jwk, alg);
2101
+ return handleJWK(key, key.export({ format: "jwk" }), alg);
2120
2102
  }
2121
2103
  if (isJWK(key)) {
2122
2104
  if (key.k) return decode(key.k);
@@ -2204,8 +2186,7 @@ const asymmetricTypeCheck = (alg, key, usage) => {
2204
2186
  }
2205
2187
  };
2206
2188
  var check_key_type_default = (alg, key, usage) => {
2207
- const symmetric = alg.startsWith("HS") || alg === "dir" || alg.startsWith("PBES2") || /^A(?:128|192|256)(?:GCM)?(?:KW)?$/.test(alg) || /^A(?:128|192|256)CBC-HS(?:256|384|512)$/.test(alg);
2208
- if (symmetric) symmetricTypeCheck(alg, key, usage);
2189
+ if (alg.startsWith("HS") || alg === "dir" || alg.startsWith("PBES2") || /^A(?:128|192|256)(?:GCM)?(?:KW)?$/.test(alg) || /^A(?:128|192|256)CBC-HS(?:256|384|512)$/.test(alg)) symmetricTypeCheck(alg, key, usage);
2209
2190
  else asymmetricTypeCheck(alg, key, usage);
2210
2191
  };
2211
2192
 
@@ -2321,8 +2302,7 @@ async function flattenedVerify(jws, key, options) {
2321
2302
  throw new JWSInvalid("Failed to base64url decode the signature");
2322
2303
  }
2323
2304
  const k = await normalize_key_default(key, alg);
2324
- const verified = await verify_default(alg, k, signature, data);
2325
- if (!verified) throw new JWSSignatureVerificationFailed();
2305
+ if (!await verify_default(alg, k, signature, data)) throw new JWSSignatureVerificationFailed();
2326
2306
  let payload;
2327
2307
  if (b64) try {
2328
2308
  payload = decode(jws.payload);
@@ -2537,9 +2517,8 @@ var JWTClaimsBuilder = class {
2537
2517
  async function jwtVerify(jwt, key, options) {
2538
2518
  const verified = await compactVerify(jwt, key, options);
2539
2519
  if (verified.protectedHeader.crit?.includes("b64") && verified.protectedHeader.b64 === false) throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
2540
- const payload = validateClaimsSet(verified.protectedHeader, verified.payload, options);
2541
2520
  const result = {
2542
- payload,
2521
+ payload: validateClaimsSet(verified.protectedHeader, verified.payload, options),
2543
2522
  protectedHeader: verified.protectedHeader
2544
2523
  };
2545
2524
  if (typeof key === "function") return {
@@ -2600,10 +2579,8 @@ var FlattenedSign = class {
2600
2579
  if (this.#protectedHeader) protectedHeader = encoder.encode(encode(JSON.stringify(this.#protectedHeader)));
2601
2580
  else protectedHeader = encoder.encode("");
2602
2581
  const data = concat(protectedHeader, encoder.encode("."), payload);
2603
- const k = await normalize_key_default(key, alg);
2604
- const signature = await sign_default(alg, k, data);
2605
2582
  const jws = {
2606
- signature: encode(signature),
2583
+ signature: encode(await sign_default(alg, await normalize_key_default(key, alg), data)),
2607
2584
  payload: ""
2608
2585
  };
2609
2586
  if (b64) jws.payload = decoder.decode(payload);
@@ -2679,41 +2656,6 @@ var SignJWT = class {
2679
2656
  }
2680
2657
  };
2681
2658
 
2682
- //#endregion
2683
- //#region src/withJwtSignedUrl.ts
2684
- /**
2685
- * Creates a JWT-signed download URL based on the provided update information.
2686
- *
2687
- * @param {Object} options - Function options
2688
- * @param {T|null} options.data - Update information (null if none)
2689
- * @param {string} options.reqUrl - Request URL (base URL for token generation)
2690
- * @param {string} options.jwtSecret - Secret key for JWT signing
2691
- * @returns {Promise<T|null>} - Update response object with fileUrl or null
2692
- */
2693
- const withJwtSignedUrl = async ({ data, reqUrl, jwtSecret }) => {
2694
- if (!data) return null;
2695
- const { storageUri,...rest } = data;
2696
- if (data.id === __hot_updater_core.NIL_UUID || !storageUri) return {
2697
- ...rest,
2698
- fileUrl: null
2699
- };
2700
- const storageUrl = new URL(storageUri);
2701
- const key = `${storageUrl.host}${storageUrl.pathname}`;
2702
- const token = await signToken(key, jwtSecret);
2703
- const url = new URL(reqUrl);
2704
- url.pathname = key;
2705
- url.searchParams.set("token", token);
2706
- return {
2707
- ...rest,
2708
- fileUrl: url.toString()
2709
- };
2710
- };
2711
- const signToken = async (key, jwtSecret) => {
2712
- const secretKey = new TextEncoder().encode(jwtSecret);
2713
- const token = await new SignJWT({ key }).setProtectedHeader({ alg: "HS256" }).setExpirationTime("60s").sign(secretKey);
2714
- return token;
2715
- };
2716
-
2717
2659
  //#endregion
2718
2660
  //#region src/verifyJwtSignedUrl.ts
2719
2661
  /**
@@ -2726,8 +2668,7 @@ const verifyJwtToken = async ({ path, token, jwtSecret }) => {
2726
2668
  error: "Missing token"
2727
2669
  };
2728
2670
  try {
2729
- const secretKey = new TextEncoder().encode(jwtSecret);
2730
- const { payload } = await jwtVerify(token, secretKey);
2671
+ const { payload } = await jwtVerify(token, new TextEncoder().encode(jwtSecret));
2731
2672
  if (!payload || payload.key !== key) return {
2732
2673
  valid: false,
2733
2674
  error: "Token does not match requested file"
@@ -2736,7 +2677,7 @@ const verifyJwtToken = async ({ path, token, jwtSecret }) => {
2736
2677
  valid: true,
2737
2678
  key
2738
2679
  };
2739
- } catch (error) {
2680
+ } catch {
2740
2681
  return {
2741
2682
  valid: false,
2742
2683
  error: "Invalid or expired token"
@@ -2754,13 +2695,12 @@ const getFileResponse = async ({ key, handler }) => {
2754
2695
  };
2755
2696
  const pathParts = key.split("/");
2756
2697
  const fileName = pathParts[pathParts.length - 1];
2757
- const headers = {
2758
- "Content-Type": object.contentType || "application/octet-stream",
2759
- "Content-Disposition": `attachment; filename=${fileName}`
2760
- };
2761
2698
  return {
2762
2699
  status: 200,
2763
- responseHeaders: headers,
2700
+ responseHeaders: {
2701
+ "Content-Type": object.contentType || "application/octet-stream",
2702
+ "Content-Disposition": `attachment; filename=${fileName}`
2703
+ },
2764
2704
  responseBody: object.body
2765
2705
  };
2766
2706
  };
@@ -2791,6 +2731,40 @@ const verifyJwtSignedUrl = async ({ path, token, jwtSecret, handler }) => {
2791
2731
  });
2792
2732
  };
2793
2733
 
2734
+ //#endregion
2735
+ //#region src/withJwtSignedUrl.ts
2736
+ /**
2737
+ * Creates a JWT-signed download URL based on the provided update information.
2738
+ *
2739
+ * @param {Object} options - Function options
2740
+ * @param {T|null} options.data - Update information (null if none)
2741
+ * @param {string} options.reqUrl - Request URL (base URL for token generation)
2742
+ * @param {string} options.jwtSecret - Secret key for JWT signing
2743
+ * @returns {Promise<T|null>} - Update response object with fileUrl or null
2744
+ */
2745
+ const withJwtSignedUrl = async ({ data, reqUrl, jwtSecret }) => {
2746
+ if (!data) return null;
2747
+ const { storageUri,...rest } = data;
2748
+ if (data.id === __hot_updater_core.NIL_UUID || !storageUri) return {
2749
+ ...rest,
2750
+ fileUrl: null
2751
+ };
2752
+ const storageUrl = new URL(storageUri);
2753
+ const key = `${storageUrl.host}${storageUrl.pathname}`;
2754
+ const token = await signToken(key, jwtSecret);
2755
+ const url = new URL(reqUrl);
2756
+ url.pathname = key;
2757
+ url.searchParams.set("token", token);
2758
+ return {
2759
+ ...rest,
2760
+ fileUrl: url.toString()
2761
+ };
2762
+ };
2763
+ const signToken = async (key, jwtSecret) => {
2764
+ const secretKey = new TextEncoder().encode(jwtSecret);
2765
+ return await new SignJWT({ key }).setProtectedHeader({ alg: "HS256" }).setExpirationTime("60s").sign(secretKey);
2766
+ };
2767
+
2794
2768
  //#endregion
2795
2769
  exports.filterCompatibleAppVersions = filterCompatibleAppVersions;
2796
2770
  exports.getUpdateInfo = getUpdateInfo;
package/dist/index.d.cts CHANGED
@@ -1,12 +1,7 @@
1
1
  import { Bundle, GetBundlesArgs, UpdateInfo } from "@hot-updater/core";
2
2
 
3
- //#region src/getUpdateInfo.d.ts
4
- declare const getUpdateInfo: (bundles: Bundle[], args: GetBundlesArgs) => Promise<UpdateInfo | null>;
5
- //#endregion
6
- //#region src/semverSatisfies.d.ts
7
- declare const semverSatisfies: (targetAppVersion: string, currentVersion: string) => boolean;
8
- //#endregion
9
3
  //#region src/filterCompatibleAppVersions.d.ts
4
+
10
5
  /**
11
6
  * Filters target app versions that are compatible with the current app version.
12
7
  * Returns only versions that are compatible with the current version according to semver rules.
@@ -17,31 +12,11 @@ declare const semverSatisfies: (targetAppVersion: string, currentVersion: string
17
12
  */
18
13
  declare const filterCompatibleAppVersions: (targetAppVersionList: string[], currentVersion: string) => string[];
19
14
  //#endregion
20
- //#region src/withJwtSignedUrl.d.ts
21
- /**
22
- * Creates a JWT-signed download URL based on the provided update information.
23
- *
24
- * @param {Object} options - Function options
25
- * @param {T|null} options.data - Update information (null if none)
26
- * @param {string} options.reqUrl - Request URL (base URL for token generation)
27
- * @param {string} options.jwtSecret - Secret key for JWT signing
28
- * @returns {Promise<T|null>} - Update response object with fileUrl or null
29
- */
30
- declare const withJwtSignedUrl: <T extends {
31
- id: string;
32
- storageUri: string | null;
33
- }>({
34
- data,
35
- reqUrl,
36
- jwtSecret
37
- }: {
38
- data: T | null;
39
- reqUrl: string;
40
- jwtSecret: string;
41
- }) => Promise<(Omit<T, "storageUri"> & {
42
- fileUrl: string | null;
43
- }) | null>;
44
- declare const signToken: (key: string, jwtSecret: string) => Promise<string>;
15
+ //#region src/getUpdateInfo.d.ts
16
+ declare const getUpdateInfo: (bundles: Bundle[], args: GetBundlesArgs) => Promise<UpdateInfo | null>;
17
+ //#endregion
18
+ //#region src/semverSatisfies.d.ts
19
+ declare const semverSatisfies: (targetAppVersion: string, currentVersion: string) => boolean;
45
20
  //#endregion
46
21
  //#region src/verifyJwtSignedUrl.d.ts
47
22
  type SuccessResponse = {
@@ -90,4 +65,30 @@ declare const verifyJwtSignedUrl: ({
90
65
  } | null>;
91
66
  }) => Promise<VerifyJwtSignedUrlResponse>;
92
67
  //#endregion
68
+ //#region src/withJwtSignedUrl.d.ts
69
+ /**
70
+ * Creates a JWT-signed download URL based on the provided update information.
71
+ *
72
+ * @param {Object} options - Function options
73
+ * @param {T|null} options.data - Update information (null if none)
74
+ * @param {string} options.reqUrl - Request URL (base URL for token generation)
75
+ * @param {string} options.jwtSecret - Secret key for JWT signing
76
+ * @returns {Promise<T|null>} - Update response object with fileUrl or null
77
+ */
78
+ declare const withJwtSignedUrl: <T extends {
79
+ id: string;
80
+ storageUri: string | null;
81
+ }>({
82
+ data,
83
+ reqUrl,
84
+ jwtSecret
85
+ }: {
86
+ data: T | null;
87
+ reqUrl: string;
88
+ jwtSecret: string;
89
+ }) => Promise<(Omit<T, "storageUri"> & {
90
+ fileUrl: string | null;
91
+ }) | null>;
92
+ declare const signToken: (key: string, jwtSecret: string) => Promise<string>;
93
+ //#endregion
93
94
  export { ErrorResponse, SuccessResponse, VerifyJwtSignedUrlResponse, filterCompatibleAppVersions, getUpdateInfo, semverSatisfies, signToken, verifyJwtSignedUrl, verifyJwtToken, withJwtSignedUrl };
package/dist/index.d.ts CHANGED
@@ -1,12 +1,7 @@
1
1
  import { Bundle, GetBundlesArgs, UpdateInfo } from "@hot-updater/core";
2
2
 
3
- //#region src/getUpdateInfo.d.ts
4
- declare const getUpdateInfo: (bundles: Bundle[], args: GetBundlesArgs) => Promise<UpdateInfo | null>;
5
- //#endregion
6
- //#region src/semverSatisfies.d.ts
7
- declare const semverSatisfies: (targetAppVersion: string, currentVersion: string) => boolean;
8
- //#endregion
9
3
  //#region src/filterCompatibleAppVersions.d.ts
4
+
10
5
  /**
11
6
  * Filters target app versions that are compatible with the current app version.
12
7
  * Returns only versions that are compatible with the current version according to semver rules.
@@ -17,31 +12,11 @@ declare const semverSatisfies: (targetAppVersion: string, currentVersion: string
17
12
  */
18
13
  declare const filterCompatibleAppVersions: (targetAppVersionList: string[], currentVersion: string) => string[];
19
14
  //#endregion
20
- //#region src/withJwtSignedUrl.d.ts
21
- /**
22
- * Creates a JWT-signed download URL based on the provided update information.
23
- *
24
- * @param {Object} options - Function options
25
- * @param {T|null} options.data - Update information (null if none)
26
- * @param {string} options.reqUrl - Request URL (base URL for token generation)
27
- * @param {string} options.jwtSecret - Secret key for JWT signing
28
- * @returns {Promise<T|null>} - Update response object with fileUrl or null
29
- */
30
- declare const withJwtSignedUrl: <T extends {
31
- id: string;
32
- storageUri: string | null;
33
- }>({
34
- data,
35
- reqUrl,
36
- jwtSecret
37
- }: {
38
- data: T | null;
39
- reqUrl: string;
40
- jwtSecret: string;
41
- }) => Promise<(Omit<T, "storageUri"> & {
42
- fileUrl: string | null;
43
- }) | null>;
44
- declare const signToken: (key: string, jwtSecret: string) => Promise<string>;
15
+ //#region src/getUpdateInfo.d.ts
16
+ declare const getUpdateInfo: (bundles: Bundle[], args: GetBundlesArgs) => Promise<UpdateInfo | null>;
17
+ //#endregion
18
+ //#region src/semverSatisfies.d.ts
19
+ declare const semverSatisfies: (targetAppVersion: string, currentVersion: string) => boolean;
45
20
  //#endregion
46
21
  //#region src/verifyJwtSignedUrl.d.ts
47
22
  type SuccessResponse = {
@@ -90,4 +65,30 @@ declare const verifyJwtSignedUrl: ({
90
65
  } | null>;
91
66
  }) => Promise<VerifyJwtSignedUrlResponse>;
92
67
  //#endregion
68
+ //#region src/withJwtSignedUrl.d.ts
69
+ /**
70
+ * Creates a JWT-signed download URL based on the provided update information.
71
+ *
72
+ * @param {Object} options - Function options
73
+ * @param {T|null} options.data - Update information (null if none)
74
+ * @param {string} options.reqUrl - Request URL (base URL for token generation)
75
+ * @param {string} options.jwtSecret - Secret key for JWT signing
76
+ * @returns {Promise<T|null>} - Update response object with fileUrl or null
77
+ */
78
+ declare const withJwtSignedUrl: <T extends {
79
+ id: string;
80
+ storageUri: string | null;
81
+ }>({
82
+ data,
83
+ reqUrl,
84
+ jwtSecret
85
+ }: {
86
+ data: T | null;
87
+ reqUrl: string;
88
+ jwtSecret: string;
89
+ }) => Promise<(Omit<T, "storageUri"> & {
90
+ fileUrl: string | null;
91
+ }) | null>;
92
+ declare const signToken: (key: string, jwtSecret: string) => Promise<string>;
93
+ //#endregion
93
94
  export { ErrorResponse, SuccessResponse, VerifyJwtSignedUrlResponse, filterCompatibleAppVersions, getUpdateInfo, semverSatisfies, signToken, verifyJwtSignedUrl, verifyJwtToken, withJwtSignedUrl };
package/dist/index.js CHANGED
@@ -418,8 +418,7 @@ var require_diff = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semver
418
418
  const highVersion = v1Higher ? v1 : v2;
419
419
  const lowVersion = v1Higher ? v2 : v1;
420
420
  const highHasPre = !!highVersion.prerelease.length;
421
- const lowHasPre = !!lowVersion.prerelease.length;
422
- if (lowHasPre && !highHasPre) {
421
+ if (!!lowVersion.prerelease.length && !highHasPre) {
423
422
  if (!lowVersion.patch && !lowVersion.minor) return "major";
424
423
  if (lowVersion.compareMain(highVersion) === 0) {
425
424
  if (lowVersion.minor && !lowVersion.patch) return "minor";
@@ -627,11 +626,7 @@ var require_coerce = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semv
627
626
  }
628
627
  if (match === null) return null;
629
628
  const major$2 = match[2];
630
- const minor$2 = match[3] || "0";
631
- const patch$2 = match[4] || "0";
632
- const prerelease$2 = options.includePrerelease && match[5] ? `-${match[5]}` : "";
633
- const build = options.includePrerelease && match[6] ? `+${match[6]}` : "";
634
- return parse$1(`${major$2}.${minor$2}.${patch$2}${prerelease$2}${build}`, options);
629
+ return parse$1(`${major$2}.${match[3] || "0"}.${match[4] || "0"}${options.includePrerelease && match[5] ? `-${match[5]}` : ""}${options.includePrerelease && match[6] ? `+${match[6]}` : ""}`, options);
635
630
  };
636
631
  module.exports = coerce$1;
637
632
  }) });
@@ -646,7 +641,7 @@ var require_lrucache = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/se
646
641
  }
647
642
  get(key) {
648
643
  const value = this.map.get(key);
649
- if (value === void 0) return void 0;
644
+ if (value === void 0) return;
650
645
  else {
651
646
  this.map.delete(key);
652
647
  this.map.set(key, value);
@@ -657,8 +652,7 @@ var require_lrucache = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/se
657
652
  return this.map.delete(key);
658
653
  }
659
654
  set(key, value) {
660
- const deleted = this.delete(key);
661
- if (!deleted && value !== void 0) {
655
+ if (!this.delete(key) && value !== void 0) {
662
656
  if (this.map.size >= this.max) {
663
657
  const firstKey = this.map.keys().next().value;
664
658
  this.delete(firstKey);
@@ -726,8 +720,7 @@ var require_range = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semve
726
720
  return this.range;
727
721
  }
728
722
  parseRange(range) {
729
- const memoOpts = (this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE);
730
- const memoKey = memoOpts + ":" + range;
723
+ const memoKey = ((this.options.includePrerelease && FLAG_INCLUDE_PRERELEASE) | (this.options.loose && FLAG_LOOSE)) + ":" + range;
731
724
  const cached = cache$1.get(memoKey);
732
725
  if (cached) return cached;
733
726
  const loose = this.options.loose;
@@ -781,8 +774,7 @@ var require_range = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/semve
781
774
  }
782
775
  };
783
776
  module.exports = Range$11;
784
- const LRU = require_lrucache();
785
- const cache$1 = new LRU();
777
+ const cache$1 = new (require_lrucache())();
786
778
  const parseOptions$1 = require_parse_options();
787
779
  const Comparator$4 = require_comparator();
788
780
  const debug$1 = require_debug();
@@ -1241,16 +1233,13 @@ var require_simplify = /* @__PURE__ */ __commonJS({ "../../node_modules/.pnpm/se
1241
1233
  let first = null;
1242
1234
  let prev = null;
1243
1235
  const v = versions.sort((a, b) => compare$2(a, b, options));
1244
- for (const version of v) {
1245
- const included = satisfies$2(version, range, options);
1246
- if (included) {
1247
- prev = version;
1248
- if (!first) first = version;
1249
- } else {
1250
- if (prev) set.push([first, prev]);
1251
- prev = null;
1252
- first = null;
1253
- }
1236
+ for (const version of v) if (satisfies$2(version, range, options)) {
1237
+ prev = version;
1238
+ if (!first) first = version;
1239
+ } else {
1240
+ if (prev) set.push([first, prev]);
1241
+ prev = null;
1242
+ first = null;
1254
1243
  }
1255
1244
  if (first) set.push([first, null]);
1256
1245
  const ranges = [];
@@ -1463,6 +1452,20 @@ const semverSatisfies = (targetAppVersion, currentVersion) => {
1463
1452
  return import_semver.default.satisfies(currentCoerce.version, targetAppVersion);
1464
1453
  };
1465
1454
 
1455
+ //#endregion
1456
+ //#region src/filterCompatibleAppVersions.ts
1457
+ /**
1458
+ * Filters target app versions that are compatible with the current app version.
1459
+ * Returns only versions that are compatible with the current version according to semver rules.
1460
+ *
1461
+ * @param targetAppVersionList - List of target app versions to filter
1462
+ * @param currentVersion - Current app version
1463
+ * @returns Array of target app versions compatible with the current version
1464
+ */
1465
+ const filterCompatibleAppVersions = (targetAppVersionList, currentVersion) => {
1466
+ return targetAppVersionList.filter((version) => semverSatisfies(version, currentVersion)).sort((a, b) => b.localeCompare(a));
1467
+ };
1468
+
1466
1469
  //#endregion
1467
1470
  //#region src/getUpdateInfo.ts
1468
1471
  const INIT_BUNDLE_ROLLBACK_UPDATE_INFO = {
@@ -1499,7 +1502,7 @@ const appVersionStrategy = async (bundles, { channel = "production", minBundleId
1499
1502
  let latestCandidate = null;
1500
1503
  let updateCandidate = null;
1501
1504
  let rollbackCandidate = null;
1502
- let currentBundle = void 0;
1505
+ let currentBundle;
1503
1506
  for (const b of candidateBundles) {
1504
1507
  if (!latestCandidate || b.id.localeCompare(latestCandidate.id) > 0) latestCandidate = b;
1505
1508
  if (b.id === bundleId) currentBundle = b;
@@ -1537,7 +1540,7 @@ const fingerprintStrategy = async (bundles, { channel = "production", minBundleI
1537
1540
  let latestCandidate = null;
1538
1541
  let updateCandidate = null;
1539
1542
  let rollbackCandidate = null;
1540
- let currentBundle = void 0;
1543
+ let currentBundle;
1541
1544
  for (const b of candidateBundles) {
1542
1545
  if (!latestCandidate || b.id.localeCompare(latestCandidate.id) > 0) latestCandidate = b;
1543
1546
  if (b.id === bundleId) currentBundle = b;
@@ -1563,21 +1566,6 @@ const fingerprintStrategy = async (bundles, { channel = "production", minBundleI
1563
1566
  return INIT_BUNDLE_ROLLBACK_UPDATE_INFO;
1564
1567
  };
1565
1568
 
1566
- //#endregion
1567
- //#region src/filterCompatibleAppVersions.ts
1568
- /**
1569
- * Filters target app versions that are compatible with the current app version.
1570
- * Returns only versions that are compatible with the current version according to semver rules.
1571
- *
1572
- * @param targetAppVersionList - List of target app versions to filter
1573
- * @param currentVersion - Current app version
1574
- * @returns Array of target app versions compatible with the current version
1575
- */
1576
- const filterCompatibleAppVersions = (targetAppVersionList, currentVersion) => {
1577
- const compatibleAppVersionList = targetAppVersionList.filter((version) => semverSatisfies(version, currentVersion));
1578
- return compatibleAppVersionList.sort((a, b) => b.localeCompare(a));
1579
- };
1580
-
1581
1569
  //#endregion
1582
1570
  //#region ../../node_modules/.pnpm/jose@6.0.10/node_modules/jose/dist/webapi/lib/buffer_utils.js
1583
1571
  const encoder = new TextEncoder();
@@ -1740,8 +1728,7 @@ function checkSigCryptoKey(key, alg, usage) {
1740
1728
  case "HS512": {
1741
1729
  if (!isAlgorithm(key.algorithm, "HMAC")) throw unusable("HMAC");
1742
1730
  const expected = parseInt(alg.slice(2), 10);
1743
- const actual = getHashLength(key.algorithm.hash);
1744
- if (actual !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1731
+ if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1745
1732
  break;
1746
1733
  }
1747
1734
  case "RS256":
@@ -1749,8 +1736,7 @@ function checkSigCryptoKey(key, alg, usage) {
1749
1736
  case "RS512": {
1750
1737
  if (!isAlgorithm(key.algorithm, "RSASSA-PKCS1-v1_5")) throw unusable("RSASSA-PKCS1-v1_5");
1751
1738
  const expected = parseInt(alg.slice(2), 10);
1752
- const actual = getHashLength(key.algorithm.hash);
1753
- if (actual !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1739
+ if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1754
1740
  break;
1755
1741
  }
1756
1742
  case "PS256":
@@ -1758,8 +1744,7 @@ function checkSigCryptoKey(key, alg, usage) {
1758
1744
  case "PS512": {
1759
1745
  if (!isAlgorithm(key.algorithm, "RSA-PSS")) throw unusable("RSA-PSS");
1760
1746
  const expected = parseInt(alg.slice(2), 10);
1761
- const actual = getHashLength(key.algorithm.hash);
1762
- if (actual !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1747
+ if (getHashLength(key.algorithm.hash) !== expected) throw unusable(`SHA-${expected}`, "algorithm.hash");
1763
1748
  break;
1764
1749
  }
1765
1750
  case "Ed25519":
@@ -1771,8 +1756,7 @@ function checkSigCryptoKey(key, alg, usage) {
1771
1756
  case "ES512": {
1772
1757
  if (!isAlgorithm(key.algorithm, "ECDSA")) throw unusable("ECDSA");
1773
1758
  const expected = getNamedCurve(alg);
1774
- const actual = key.algorithm.namedCurve;
1775
- if (actual !== expected) throw unusable(expected, "algorithm.namedCurve");
1759
+ if (key.algorithm.namedCurve !== expected) throw unusable(expected, "algorithm.namedCurve");
1776
1760
  break;
1777
1761
  }
1778
1762
  default: throw new TypeError("CryptoKey does not support this operation");
@@ -1986,7 +1970,7 @@ var validate_crit_default = (Err, recognizedDefault, recognizedOption, protected
1986
1970
  //#region ../../node_modules/.pnpm/jose@6.0.10/node_modules/jose/dist/webapi/lib/validate_algorithms.js
1987
1971
  var validate_algorithms_default = (option, algorithms) => {
1988
1972
  if (algorithms !== void 0 && (!Array.isArray(algorithms) || algorithms.some((s) => typeof s !== "string"))) throw new TypeError(`"${option}" option must be an array of strings`);
1989
- if (!algorithms) return void 0;
1973
+ if (!algorithms) return;
1990
1974
  return new Set(algorithms);
1991
1975
  };
1992
1976
 
@@ -2075,12 +2059,11 @@ const handleKeyObject = (keyObject, alg) => {
2075
2059
  }, extractable, [isPublic ? "verify" : "sign"]);
2076
2060
  }
2077
2061
  if (keyObject.asymmetricKeyType === "ec") {
2078
- const nist = new Map([
2062
+ const namedCurve = new Map([
2079
2063
  ["prime256v1", "P-256"],
2080
2064
  ["secp384r1", "P-384"],
2081
2065
  ["secp521r1", "P-521"]
2082
- ]);
2083
- const namedCurve = nist.get(keyObject.asymmetricKeyDetails?.namedCurve);
2066
+ ]).get(keyObject.asymmetricKeyDetails?.namedCurve);
2084
2067
  if (!namedCurve) throw new TypeError("given KeyObject instance cannot be used for this algorithm");
2085
2068
  if (alg === "ES256" && namedCurve === "P-256") cryptoKey = keyObject.toCryptoKey({
2086
2069
  name: "ECDSA",
@@ -2114,8 +2097,7 @@ var normalize_key_default = async (key, alg) => {
2114
2097
  } catch (err) {
2115
2098
  if (err instanceof TypeError) throw err;
2116
2099
  }
2117
- let jwk = key.export({ format: "jwk" });
2118
- return handleJWK(key, jwk, alg);
2100
+ return handleJWK(key, key.export({ format: "jwk" }), alg);
2119
2101
  }
2120
2102
  if (isJWK(key)) {
2121
2103
  if (key.k) return decode(key.k);
@@ -2203,8 +2185,7 @@ const asymmetricTypeCheck = (alg, key, usage) => {
2203
2185
  }
2204
2186
  };
2205
2187
  var check_key_type_default = (alg, key, usage) => {
2206
- const symmetric = alg.startsWith("HS") || alg === "dir" || alg.startsWith("PBES2") || /^A(?:128|192|256)(?:GCM)?(?:KW)?$/.test(alg) || /^A(?:128|192|256)CBC-HS(?:256|384|512)$/.test(alg);
2207
- if (symmetric) symmetricTypeCheck(alg, key, usage);
2188
+ if (alg.startsWith("HS") || alg === "dir" || alg.startsWith("PBES2") || /^A(?:128|192|256)(?:GCM)?(?:KW)?$/.test(alg) || /^A(?:128|192|256)CBC-HS(?:256|384|512)$/.test(alg)) symmetricTypeCheck(alg, key, usage);
2208
2189
  else asymmetricTypeCheck(alg, key, usage);
2209
2190
  };
2210
2191
 
@@ -2320,8 +2301,7 @@ async function flattenedVerify(jws, key, options) {
2320
2301
  throw new JWSInvalid("Failed to base64url decode the signature");
2321
2302
  }
2322
2303
  const k = await normalize_key_default(key, alg);
2323
- const verified = await verify_default(alg, k, signature, data);
2324
- if (!verified) throw new JWSSignatureVerificationFailed();
2304
+ if (!await verify_default(alg, k, signature, data)) throw new JWSSignatureVerificationFailed();
2325
2305
  let payload;
2326
2306
  if (b64) try {
2327
2307
  payload = decode(jws.payload);
@@ -2536,9 +2516,8 @@ var JWTClaimsBuilder = class {
2536
2516
  async function jwtVerify(jwt, key, options) {
2537
2517
  const verified = await compactVerify(jwt, key, options);
2538
2518
  if (verified.protectedHeader.crit?.includes("b64") && verified.protectedHeader.b64 === false) throw new JWTInvalid("JWTs MUST NOT use unencoded payload");
2539
- const payload = validateClaimsSet(verified.protectedHeader, verified.payload, options);
2540
2519
  const result = {
2541
- payload,
2520
+ payload: validateClaimsSet(verified.protectedHeader, verified.payload, options),
2542
2521
  protectedHeader: verified.protectedHeader
2543
2522
  };
2544
2523
  if (typeof key === "function") return {
@@ -2599,10 +2578,8 @@ var FlattenedSign = class {
2599
2578
  if (this.#protectedHeader) protectedHeader = encoder.encode(encode(JSON.stringify(this.#protectedHeader)));
2600
2579
  else protectedHeader = encoder.encode("");
2601
2580
  const data = concat(protectedHeader, encoder.encode("."), payload);
2602
- const k = await normalize_key_default(key, alg);
2603
- const signature = await sign_default(alg, k, data);
2604
2581
  const jws = {
2605
- signature: encode(signature),
2582
+ signature: encode(await sign_default(alg, await normalize_key_default(key, alg), data)),
2606
2583
  payload: ""
2607
2584
  };
2608
2585
  if (b64) jws.payload = decoder.decode(payload);
@@ -2678,41 +2655,6 @@ var SignJWT = class {
2678
2655
  }
2679
2656
  };
2680
2657
 
2681
- //#endregion
2682
- //#region src/withJwtSignedUrl.ts
2683
- /**
2684
- * Creates a JWT-signed download URL based on the provided update information.
2685
- *
2686
- * @param {Object} options - Function options
2687
- * @param {T|null} options.data - Update information (null if none)
2688
- * @param {string} options.reqUrl - Request URL (base URL for token generation)
2689
- * @param {string} options.jwtSecret - Secret key for JWT signing
2690
- * @returns {Promise<T|null>} - Update response object with fileUrl or null
2691
- */
2692
- const withJwtSignedUrl = async ({ data, reqUrl, jwtSecret }) => {
2693
- if (!data) return null;
2694
- const { storageUri,...rest } = data;
2695
- if (data.id === NIL_UUID || !storageUri) return {
2696
- ...rest,
2697
- fileUrl: null
2698
- };
2699
- const storageUrl = new URL(storageUri);
2700
- const key = `${storageUrl.host}${storageUrl.pathname}`;
2701
- const token = await signToken(key, jwtSecret);
2702
- const url = new URL(reqUrl);
2703
- url.pathname = key;
2704
- url.searchParams.set("token", token);
2705
- return {
2706
- ...rest,
2707
- fileUrl: url.toString()
2708
- };
2709
- };
2710
- const signToken = async (key, jwtSecret) => {
2711
- const secretKey = new TextEncoder().encode(jwtSecret);
2712
- const token = await new SignJWT({ key }).setProtectedHeader({ alg: "HS256" }).setExpirationTime("60s").sign(secretKey);
2713
- return token;
2714
- };
2715
-
2716
2658
  //#endregion
2717
2659
  //#region src/verifyJwtSignedUrl.ts
2718
2660
  /**
@@ -2725,8 +2667,7 @@ const verifyJwtToken = async ({ path, token, jwtSecret }) => {
2725
2667
  error: "Missing token"
2726
2668
  };
2727
2669
  try {
2728
- const secretKey = new TextEncoder().encode(jwtSecret);
2729
- const { payload } = await jwtVerify(token, secretKey);
2670
+ const { payload } = await jwtVerify(token, new TextEncoder().encode(jwtSecret));
2730
2671
  if (!payload || payload.key !== key) return {
2731
2672
  valid: false,
2732
2673
  error: "Token does not match requested file"
@@ -2735,7 +2676,7 @@ const verifyJwtToken = async ({ path, token, jwtSecret }) => {
2735
2676
  valid: true,
2736
2677
  key
2737
2678
  };
2738
- } catch (error) {
2679
+ } catch {
2739
2680
  return {
2740
2681
  valid: false,
2741
2682
  error: "Invalid or expired token"
@@ -2753,13 +2694,12 @@ const getFileResponse = async ({ key, handler }) => {
2753
2694
  };
2754
2695
  const pathParts = key.split("/");
2755
2696
  const fileName = pathParts[pathParts.length - 1];
2756
- const headers = {
2757
- "Content-Type": object.contentType || "application/octet-stream",
2758
- "Content-Disposition": `attachment; filename=${fileName}`
2759
- };
2760
2697
  return {
2761
2698
  status: 200,
2762
- responseHeaders: headers,
2699
+ responseHeaders: {
2700
+ "Content-Type": object.contentType || "application/octet-stream",
2701
+ "Content-Disposition": `attachment; filename=${fileName}`
2702
+ },
2763
2703
  responseBody: object.body
2764
2704
  };
2765
2705
  };
@@ -2790,5 +2730,39 @@ const verifyJwtSignedUrl = async ({ path, token, jwtSecret, handler }) => {
2790
2730
  });
2791
2731
  };
2792
2732
 
2733
+ //#endregion
2734
+ //#region src/withJwtSignedUrl.ts
2735
+ /**
2736
+ * Creates a JWT-signed download URL based on the provided update information.
2737
+ *
2738
+ * @param {Object} options - Function options
2739
+ * @param {T|null} options.data - Update information (null if none)
2740
+ * @param {string} options.reqUrl - Request URL (base URL for token generation)
2741
+ * @param {string} options.jwtSecret - Secret key for JWT signing
2742
+ * @returns {Promise<T|null>} - Update response object with fileUrl or null
2743
+ */
2744
+ const withJwtSignedUrl = async ({ data, reqUrl, jwtSecret }) => {
2745
+ if (!data) return null;
2746
+ const { storageUri,...rest } = data;
2747
+ if (data.id === NIL_UUID || !storageUri) return {
2748
+ ...rest,
2749
+ fileUrl: null
2750
+ };
2751
+ const storageUrl = new URL(storageUri);
2752
+ const key = `${storageUrl.host}${storageUrl.pathname}`;
2753
+ const token = await signToken(key, jwtSecret);
2754
+ const url = new URL(reqUrl);
2755
+ url.pathname = key;
2756
+ url.searchParams.set("token", token);
2757
+ return {
2758
+ ...rest,
2759
+ fileUrl: url.toString()
2760
+ };
2761
+ };
2762
+ const signToken = async (key, jwtSecret) => {
2763
+ const secretKey = new TextEncoder().encode(jwtSecret);
2764
+ return await new SignJWT({ key }).setProtectedHeader({ alg: "HS256" }).setExpirationTime("60s").sign(secretKey);
2765
+ };
2766
+
2793
2767
  //#endregion
2794
2768
  export { filterCompatibleAppVersions, getUpdateInfo, semverSatisfies, signToken, verifyJwtSignedUrl, verifyJwtToken, withJwtSignedUrl };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hot-updater/js",
3
- "version": "0.20.11",
3
+ "version": "0.20.12",
4
4
  "type": "module",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "sideEffects": false,
@@ -38,7 +38,7 @@
38
38
  "access": "public"
39
39
  },
40
40
  "dependencies": {
41
- "@hot-updater/core": "0.20.11"
41
+ "@hot-updater/core": "0.20.12"
42
42
  },
43
43
  "devDependencies": {
44
44
  "@types/node": "^20",