@hot-updater/firebase 0.30.12 → 0.31.1

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.
@@ -34,6 +34,35 @@ fs_promises = __toESM$1(fs_promises);
34
34
  let path = require("path");
35
35
  path = __toESM$1(path);
36
36
  //#region ../../packages/core/dist/index.mjs
37
+ const stripBundleArtifactMetadata = (metadata) => metadata;
38
+ const getManifestStorageUri = (bundle) => bundle.manifestStorageUri ?? null;
39
+ const getManifestFileHash = (bundle) => bundle.manifestFileHash ?? null;
40
+ const getAssetBaseStorageUri = (bundle) => bundle.assetBaseStorageUri ?? null;
41
+ const isBundlePatchArtifact = (value) => {
42
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
43
+ const candidate = value;
44
+ return typeof candidate.baseBundleId === "string" && typeof candidate.baseFileHash === "string" && typeof candidate.patchFileHash === "string" && typeof candidate.patchStorageUri === "string";
45
+ };
46
+ const readBundlePatchArray = (patches) => {
47
+ if (!Array.isArray(patches)) return [];
48
+ return patches.filter(isBundlePatchArtifact);
49
+ };
50
+ const getBundlePatches = (bundle) => {
51
+ const patches = readBundlePatchArray(bundle.patches);
52
+ const seenBaseBundleIds = /* @__PURE__ */ new Set();
53
+ return patches.filter((patch) => {
54
+ if (seenBaseBundleIds.has(patch.baseBundleId)) return false;
55
+ seenBaseBundleIds.add(patch.baseBundleId);
56
+ return true;
57
+ });
58
+ };
59
+ const getPrimaryPatch = (bundle) => {
60
+ return getBundlePatches(bundle)[0] ?? null;
61
+ };
62
+ const getPatchBaseBundleId = (bundle) => bundle.patchBaseBundleId ?? getPrimaryPatch(bundle)?.baseBundleId ?? null;
63
+ const getPatchBaseFileHash = (bundle) => bundle.patchBaseFileHash ?? getPrimaryPatch(bundle)?.baseFileHash ?? null;
64
+ const getPatchFileHash = (bundle) => bundle.patchFileHash ?? getPrimaryPatch(bundle)?.patchFileHash ?? null;
65
+ const getPatchStorageUri = (bundle) => bundle.patchStorageUri ?? getPrimaryPatch(bundle)?.patchStorageUri ?? null;
37
66
  const NUMERIC_COHORT_SIZE = 1e3;
38
67
  const DEFAULT_ROLLOUT_COHORT_COUNT = NUMERIC_COHORT_SIZE;
39
68
  function parseNumericCohortValue(cohort) {
@@ -1551,7 +1580,7 @@ function mergeWith(target, source, merge) {
1551
1580
  }
1552
1581
  //#endregion
1553
1582
  //#region ../plugin-core/dist/createDatabasePlugin.mjs
1554
- const REPLACE_ON_UPDATE_KEYS = ["targetCohorts"];
1583
+ const REPLACE_ON_UPDATE_KEYS = ["patches", "targetCohorts"];
1555
1584
  const DEFAULT_DESC_ORDER = {
1556
1585
  field: "id",
1557
1586
  direction: "desc"
@@ -4556,56 +4585,109 @@ const createStorageKeyBuilder = (basePath) => (...args) => {
4556
4585
  };
4557
4586
  //#endregion
4558
4587
  //#region ../plugin-core/dist/createStoragePlugin.mjs
4588
+ const wrapNodeProfile = (node, hooks) => ({
4589
+ ...node,
4590
+ async upload(key, filePath) {
4591
+ const result = await node.upload(key, filePath);
4592
+ await hooks?.onStorageUploaded?.();
4593
+ return result;
4594
+ }
4595
+ });
4596
+ const createProfiledStoragePlugin = ({ createProfiles, name, profileShape, supportedProtocol }, hooks) => {
4597
+ let cachedProfiles = null;
4598
+ let cachedNodeProfile;
4599
+ let cachedRuntimeProfile;
4600
+ const getProfiles = () => {
4601
+ cachedProfiles ??= createProfiles();
4602
+ return cachedProfiles;
4603
+ };
4604
+ const getNodeProfile = () => {
4605
+ const node = getProfiles().node;
4606
+ if (!node) return;
4607
+ cachedNodeProfile ??= wrapNodeProfile(node, hooks);
4608
+ return cachedNodeProfile;
4609
+ };
4610
+ const requireNodeProfile = () => {
4611
+ const node = getNodeProfile();
4612
+ if (!node) throw new Error(`${name} does not implement the node storage profile for protocol "${supportedProtocol}".`);
4613
+ return node;
4614
+ };
4615
+ const getRuntimeProfile = () => {
4616
+ const runtime = getProfiles().runtime;
4617
+ if (!runtime) return;
4618
+ cachedRuntimeProfile ??= runtime;
4619
+ return cachedRuntimeProfile;
4620
+ };
4621
+ const requireRuntimeProfile = () => {
4622
+ const runtime = getRuntimeProfile();
4623
+ if (!runtime) throw new Error(`${name} does not implement the runtime storage profile for protocol "${supportedProtocol}".`);
4624
+ return runtime;
4625
+ };
4626
+ const profiles = {};
4627
+ if (profileShape?.node) profiles.node = {
4628
+ async delete(storageUri) {
4629
+ return requireNodeProfile().delete(storageUri);
4630
+ },
4631
+ async downloadFile(storageUri, filePath) {
4632
+ return requireNodeProfile().downloadFile(storageUri, filePath);
4633
+ },
4634
+ async upload(key, filePath) {
4635
+ return requireNodeProfile().upload(key, filePath);
4636
+ }
4637
+ };
4638
+ else if (profileShape?.node !== false) Object.defineProperty(profiles, "node", {
4639
+ enumerable: true,
4640
+ get: getNodeProfile
4641
+ });
4642
+ if (profileShape?.runtime) profiles.runtime = {
4643
+ async getDownloadUrl(storageUri, context) {
4644
+ return requireRuntimeProfile().getDownloadUrl(storageUri, context);
4645
+ },
4646
+ async readText(storageUri, context) {
4647
+ return requireRuntimeProfile().readText(storageUri, context);
4648
+ }
4649
+ };
4650
+ else if (profileShape?.runtime !== false) Object.defineProperty(profiles, "runtime", {
4651
+ enumerable: true,
4652
+ get: getRuntimeProfile
4653
+ });
4654
+ return {
4655
+ name,
4656
+ supportedProtocol,
4657
+ profiles
4658
+ };
4659
+ };
4559
4660
  /**
4560
- * Creates a storage plugin with lazy initialization and automatic hook execution.
4561
- *
4562
- * This factory function abstracts the double currying pattern used by all storage plugins,
4563
- * ensuring consistent lazy initialization behavior across different storage providers.
4564
- * Hooks are automatically executed at appropriate times without requiring manual invocation.
4565
- *
4566
- * @param options - Configuration options for the storage plugin
4567
- * @returns A double-curried function that lazily initializes the storage plugin
4568
- *
4569
- * @example
4570
- * ```typescript
4571
- * export const s3Storage = createStoragePlugin<S3StorageConfig>({
4572
- * name: "s3Storage",
4573
- * supportedProtocol: "s3",
4574
- * factory: (config) => {
4575
- * const client = new S3Client(config);
4576
- * return {
4577
- * async upload(key, filePath) { ... },
4578
- * async delete(storageUri) { ... },
4579
- * async getDownloadUrl(storageUri, context) { ... }
4580
- * };
4581
- * }
4582
- * });
4583
- * ```
4661
+ * Creates an update-check runtime storage plugin.
4584
4662
  */
4585
- const createStoragePlugin = (options) => {
4663
+ const createRuntimeStoragePlugin = (options) => {
4586
4664
  return (config, hooks) => {
4587
- return () => {
4588
- let cachedMethods = null;
4589
- const getMethods = () => {
4590
- if (!cachedMethods) cachedMethods = options.factory(config);
4591
- return cachedMethods;
4592
- };
4593
- return {
4594
- name: options.name,
4595
- supportedProtocol: options.supportedProtocol,
4596
- async upload(key, filePath) {
4597
- const result = await getMethods().upload(key, filePath);
4598
- await hooks?.onStorageUploaded?.();
4599
- return result;
4600
- },
4601
- async delete(storageUri) {
4602
- return getMethods().delete(storageUri);
4603
- },
4604
- async getDownloadUrl(storageUri, context) {
4605
- return getMethods().getDownloadUrl(storageUri, context);
4606
- }
4607
- };
4608
- };
4665
+ return () => createProfiledStoragePlugin({
4666
+ createProfiles: () => ({ runtime: options.factory(config) }),
4667
+ name: options.name,
4668
+ profileShape: {
4669
+ node: false,
4670
+ runtime: true
4671
+ },
4672
+ supportedProtocol: options.supportedProtocol
4673
+ }, hooks);
4674
+ };
4675
+ };
4676
+ /**
4677
+ * Creates a storage plugin that can be used by both Node tooling and update
4678
+ * check runtimes.
4679
+ */
4680
+ const createUniversalStoragePlugin = (options) => {
4681
+ return (config, hooks) => {
4682
+ return () => createProfiledStoragePlugin({
4683
+ createProfiles: () => options.factory(config),
4684
+ name: options.name,
4685
+ profileShape: {
4686
+ node: true,
4687
+ runtime: true
4688
+ },
4689
+ supportedProtocol: options.supportedProtocol
4690
+ }, hooks);
4609
4691
  };
4610
4692
  };
4611
4693
  //#endregion
@@ -4688,22 +4770,42 @@ const chunkValues = (values, size) => {
4688
4770
  for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
4689
4771
  return chunks;
4690
4772
  };
4691
- const convertToBundle = (firestoreData) => ({
4692
- channel: firestoreData.channel,
4693
- enabled: Boolean(firestoreData.enabled),
4694
- shouldForceUpdate: Boolean(firestoreData.should_force_update),
4695
- fileHash: firestoreData.file_hash,
4696
- gitCommitHash: firestoreData.git_commit_hash,
4697
- id: firestoreData.id,
4698
- message: firestoreData.message,
4699
- platform: firestoreData.platform,
4700
- targetAppVersion: firestoreData.target_app_version,
4701
- storageUri: firestoreData.storage_uri,
4702
- fingerprintHash: firestoreData.fingerprint_hash,
4703
- metadata: firestoreData?.metadata ?? {},
4704
- rolloutCohortCount: firestoreData.rollout_cohort_count ?? 1e3,
4705
- targetCohorts: firestoreData.target_cohorts ?? null
4706
- });
4773
+ const convertToBundle = (firestoreData) => {
4774
+ const rawMetadata = firestoreData.metadata;
4775
+ const storedPatches = firestoreData.patches;
4776
+ const patches = storedPatches && Array.isArray(storedPatches) ? storedPatches : getBundlePatches({
4777
+ metadata: rawMetadata,
4778
+ patchBaseBundleId: firestoreData.patch_base_bundle_id ?? null,
4779
+ patchBaseFileHash: firestoreData.patch_base_file_hash ?? null,
4780
+ patchFileHash: firestoreData.patch_file_hash ?? null,
4781
+ patchStorageUri: firestoreData.patch_storage_uri ?? null
4782
+ });
4783
+ const primaryPatch = patches[0] ?? null;
4784
+ return {
4785
+ channel: firestoreData.channel,
4786
+ enabled: Boolean(firestoreData.enabled),
4787
+ shouldForceUpdate: Boolean(firestoreData.should_force_update),
4788
+ fileHash: firestoreData.file_hash,
4789
+ gitCommitHash: firestoreData.git_commit_hash,
4790
+ id: firestoreData.id,
4791
+ message: firestoreData.message,
4792
+ platform: firestoreData.platform,
4793
+ targetAppVersion: firestoreData.target_app_version,
4794
+ storageUri: firestoreData.storage_uri,
4795
+ fingerprintHash: firestoreData.fingerprint_hash,
4796
+ metadata: stripBundleArtifactMetadata(rawMetadata),
4797
+ manifestStorageUri: firestoreData.manifest_storage_uri ?? null,
4798
+ manifestFileHash: firestoreData.manifest_file_hash ?? null,
4799
+ assetBaseStorageUri: firestoreData.asset_base_storage_uri ?? null,
4800
+ patches,
4801
+ patchBaseBundleId: primaryPatch?.baseBundleId ?? firestoreData.patch_base_bundle_id ?? null,
4802
+ patchBaseFileHash: primaryPatch?.baseFileHash ?? firestoreData.patch_base_file_hash ?? null,
4803
+ patchFileHash: primaryPatch?.patchFileHash ?? firestoreData.patch_file_hash ?? null,
4804
+ patchStorageUri: primaryPatch?.patchStorageUri ?? firestoreData.patch_storage_uri ?? null,
4805
+ rolloutCohortCount: firestoreData.rollout_cohort_count ?? 1e3,
4806
+ targetCohorts: firestoreData.target_cohorts ?? null
4807
+ };
4808
+ };
4707
4809
  const firebaseDatabase = createDatabasePlugin({
4708
4810
  name: "firebaseDatabase",
4709
4811
  factory: (config) => {
@@ -4795,7 +4897,15 @@ const firebaseDatabase = createDatabasePlugin({
4795
4897
  target_app_version: data.targetAppVersion,
4796
4898
  storage_uri: data.storageUri,
4797
4899
  fingerprint_hash: data.fingerprintHash,
4798
- metadata: data.metadata ?? {},
4900
+ metadata: stripBundleArtifactMetadata(data.metadata) ?? {},
4901
+ manifest_storage_uri: getManifestStorageUri(data),
4902
+ manifest_file_hash: getManifestFileHash(data),
4903
+ asset_base_storage_uri: getAssetBaseStorageUri(data),
4904
+ patches: data.patches ?? null,
4905
+ patch_base_bundle_id: getPatchBaseBundleId(data),
4906
+ patch_base_file_hash: getPatchBaseFileHash(data),
4907
+ patch_file_hash: getPatchFileHash(data),
4908
+ patch_storage_uri: getPatchStorageUri(data),
4799
4909
  rollout_cohort_count: data.rolloutCohortCount ?? 1e3,
4800
4910
  target_cohorts: data.targetCohorts ?? null
4801
4911
  };
@@ -4831,7 +4941,15 @@ const firebaseDatabase = createDatabasePlugin({
4831
4941
  target_app_version: data.targetAppVersion || null,
4832
4942
  storage_uri: data.storageUri,
4833
4943
  fingerprint_hash: data.fingerprintHash,
4834
- metadata: data.metadata ?? {},
4944
+ metadata: stripBundleArtifactMetadata(data.metadata) ?? {},
4945
+ manifest_storage_uri: getManifestStorageUri(data),
4946
+ manifest_file_hash: getManifestFileHash(data),
4947
+ asset_base_storage_uri: getAssetBaseStorageUri(data),
4948
+ patches: data.patches ?? null,
4949
+ patch_base_bundle_id: getPatchBaseBundleId(data),
4950
+ patch_base_file_hash: getPatchBaseFileHash(data),
4951
+ patch_file_hash: getPatchFileHash(data),
4952
+ patch_storage_uri: getPatchStorageUri(data),
4835
4953
  rollout_cohort_count: data.rolloutCohortCount ?? 1e3,
4836
4954
  target_cohorts: data.targetCohorts ?? null
4837
4955
  }, { merge: true });
@@ -4857,7 +4975,7 @@ const firebaseDatabase = createDatabasePlugin({
4857
4975
  });
4858
4976
  //#endregion
4859
4977
  //#region src/firebaseStorage.ts
4860
- const firebaseStorage = createStoragePlugin({
4978
+ const firebaseStorage = createUniversalStoragePlugin({
4861
4979
  name: "firebaseStorage",
4862
4980
  supportedProtocol: "gs",
4863
4981
  factory: (config) => {
@@ -4870,44 +4988,65 @@ const firebaseStorage = createStoragePlugin({
4870
4988
  const bucket = app.storage().bucket(config.storageBucket);
4871
4989
  const getStorageKey = createStorageKeyBuilder(config.basePath);
4872
4990
  return {
4873
- async delete(storageUri) {
4874
- const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
4875
- if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
4876
- try {
4877
- const [files] = await bucket.getFiles({ prefix: key });
4878
- await Promise.all(files.map((file) => file.delete()));
4879
- } catch (e) {
4880
- console.error("Error listing or deleting files:", e);
4881
- throw new Error("Bundle Not Found");
4991
+ node: {
4992
+ async delete(storageUri) {
4993
+ const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
4994
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
4995
+ try {
4996
+ const [files] = await bucket.getFiles({ prefix: key });
4997
+ await Promise.all(files.map((file) => file.delete()));
4998
+ } catch (e) {
4999
+ console.error("Error listing or deleting files:", e);
5000
+ throw new Error("Bundle Not Found");
5001
+ }
5002
+ },
5003
+ async upload(key, filePath) {
5004
+ try {
5005
+ const fileContent = await fs_promises.default.readFile(filePath);
5006
+ const contentType = getContentType(filePath);
5007
+ const storageKey = getStorageKey(key, path.default.basename(filePath));
5008
+ await bucket.file(storageKey).save(fileContent, { metadata: {
5009
+ contentType,
5010
+ cacheControl: "public, max-age=31536000, immutable"
5011
+ } });
5012
+ return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
5013
+ } catch (error) {
5014
+ console.error("Error uploading bundle:", error);
5015
+ if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
5016
+ throw error;
5017
+ }
5018
+ },
5019
+ async downloadFile(storageUri, filePath) {
5020
+ const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
5021
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
5022
+ await fs_promises.default.mkdir(path.default.dirname(filePath), { recursive: true });
5023
+ await bucket.file(key).download({ destination: filePath });
4882
5024
  }
4883
5025
  },
4884
- async upload(key, filePath) {
4885
- try {
4886
- const fileContent = await fs_promises.default.readFile(filePath);
4887
- const contentType = getContentType(filePath);
4888
- const storageKey = getStorageKey(key, path.default.basename(filePath));
4889
- await bucket.file(storageKey).save(fileContent, { metadata: {
4890
- contentType,
4891
- cacheControl: "public, max-age=31536000, immutable"
4892
- } });
4893
- return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
4894
- } catch (error) {
4895
- console.error("Error uploading bundle:", error);
4896
- if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
4897
- throw error;
5026
+ runtime: {
5027
+ async readText(storageUri) {
5028
+ const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
5029
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
5030
+ try {
5031
+ const [contents] = await bucket.file(key).download();
5032
+ return contents.toString("utf8");
5033
+ } catch (error) {
5034
+ if (typeof error === "object" && error !== null && "code" in error && error.code === 404) return null;
5035
+ throw error;
5036
+ }
5037
+ },
5038
+ async getDownloadUrl(storageUri) {
5039
+ const u = new URL(storageUri);
5040
+ if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
5041
+ const key = u.pathname.slice(1);
5042
+ if (!key) throw new Error("Invalid Firebase storage URI: missing key");
5043
+ const [signedUrl] = await bucket.file(key).getSignedUrl({
5044
+ action: "read",
5045
+ expires: Date.now() + 3600 * 1e3
5046
+ });
5047
+ if (!signedUrl) throw new Error("Failed to generate download URL");
5048
+ return { fileUrl: signedUrl };
4898
5049
  }
4899
- },
4900
- async getDownloadUrl(storageUri) {
4901
- const u = new URL(storageUri);
4902
- if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
4903
- const key = u.pathname.slice(1);
4904
- if (!key) throw new Error("Invalid Firebase storage URI: missing key");
4905
- const [signedUrl] = await bucket.file(key).getSignedUrl({
4906
- action: "read",
4907
- expires: Date.now() + 3600 * 1e3
4908
- });
4909
- if (!signedUrl) throw new Error("Failed to generate download URL");
4910
- return { fileUrl: signedUrl };
4911
5050
  }
4912
5051
  };
4913
5052
  }
@@ -4917,34 +5056,31 @@ const firebaseStorage = createStoragePlugin({
4917
5056
  const trimTrailingSlash = (value) => {
4918
5057
  return value.endsWith("/") ? value.slice(0, -1) : value;
4919
5058
  };
4920
- const firebaseFunctionsStorage = (config, hooks) => {
4921
- const fallbackStorageFactory = config.storageBucket ? firebaseStorage({
4922
- ...config,
4923
- storageBucket: config.storageBucket
4924
- }, hooks) : null;
4925
- return () => {
4926
- const fallbackStorage = fallbackStorageFactory?.() ?? null;
5059
+ const createFirebaseFunctionsStorage = createRuntimeStoragePlugin({
5060
+ name: "firebaseFunctionsStorage",
5061
+ supportedProtocol: "gs",
5062
+ factory: (config) => {
5063
+ const fallbackStorage = firebaseStorage({
5064
+ ...config,
5065
+ storageBucket: config.storageBucket
5066
+ })();
4927
5067
  return {
4928
- name: "firebaseFunctionsStorage",
4929
- supportedProtocol: "gs",
4930
- async upload(key, filePath) {
4931
- if (!fallbackStorage) throw new Error("firebaseFunctionsStorage requires storageBucket to support upload().");
4932
- return fallbackStorage.upload(key, filePath);
4933
- },
4934
- async delete(storageUri) {
4935
- if (!fallbackStorage) throw new Error("firebaseFunctionsStorage requires storageBucket to support delete().");
4936
- return fallbackStorage.delete(storageUri);
5068
+ async readText(storageUri, context) {
5069
+ return fallbackStorage.profiles.runtime.readText(storageUri, context);
4937
5070
  },
4938
5071
  async getDownloadUrl(storageUri, context) {
4939
5072
  if (config.cdnUrl) {
4940
5073
  const storageUrl = new URL(storageUri);
4941
5074
  if (storageUrl.protocol === "gs:") return { fileUrl: `${trimTrailingSlash(config.cdnUrl)}/${storageUrl.pathname.replace(/^\/+/, "")}` };
4942
5075
  }
4943
- if (!fallbackStorage) throw new Error("firebaseFunctionsStorage requires storageBucket or cdnUrl to resolve download URLs.");
4944
- return fallbackStorage.getDownloadUrl(storageUri, context);
5076
+ return fallbackStorage.profiles.runtime.getDownloadUrl(storageUri, context);
4945
5077
  }
4946
5078
  };
4947
- };
5079
+ }
5080
+ });
5081
+ const firebaseFunctionsStorage = (config, hooks) => {
5082
+ if (!config.storageBucket) throw new Error("firebaseFunctionsStorage requires storageBucket for the runtime storage profile.");
5083
+ return createFirebaseFunctionsStorage(config, hooks);
4948
5084
  };
4949
5085
  //#endregion
4950
5086
  //#region firebase/functions/index.ts
@@ -4953,7 +5089,7 @@ if (!firebase_admin.default.apps.length) firebase_admin.default.initializeApp();
4953
5089
  const adminOptions = firebase_admin.default.app().options;
4954
5090
  const storageBucket = adminOptions.storageBucket;
4955
5091
  const cdnUrl = process.env.HOT_UPDATER_CDN_URL;
4956
- if (!storageBucket && !cdnUrl) throw new Error("Firebase runtime requires storageBucket or HOT_UPDATER_CDN_URL to resolve bundle URLs.");
5092
+ if (!storageBucket) throw new Error("Firebase runtime requires storageBucket to read bundle manifests.");
4957
5093
  const hotUpdater = (0, _hot_updater_server_runtime.createHotUpdater)({
4958
5094
  database: firebaseDatabase(adminOptions),
4959
5095
  storages: [firebaseFunctionsStorage({
@@ -6689,7 +6689,6 @@ function App() {
6689
6689
  export default HotUpdater.wrap({
6690
6690
  baseURL: "%%source%%",
6691
6691
  updateStrategy: "appVersion", // or "fingerprint"
6692
- updateMode: "auto",
6693
6692
  })(App);`;
6694
6693
  const REGIONS = [
6695
6694
  {
@@ -6684,7 +6684,6 @@ function App() {
6684
6684
  export default HotUpdater.wrap({
6685
6685
  baseURL: "%%source%%",
6686
6686
  updateStrategy: "appVersion", // or "fingerprint"
6687
- updateMode: "auto",
6688
6687
  })(App);`;
6689
6688
  const REGIONS = [
6690
6689
  {
package/dist/index.cjs CHANGED
@@ -76,22 +76,42 @@ const chunkValues = (values, size) => {
76
76
  for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
77
77
  return chunks;
78
78
  };
79
- const convertToBundle = (firestoreData) => ({
80
- channel: firestoreData.channel,
81
- enabled: Boolean(firestoreData.enabled),
82
- shouldForceUpdate: Boolean(firestoreData.should_force_update),
83
- fileHash: firestoreData.file_hash,
84
- gitCommitHash: firestoreData.git_commit_hash,
85
- id: firestoreData.id,
86
- message: firestoreData.message,
87
- platform: firestoreData.platform,
88
- targetAppVersion: firestoreData.target_app_version,
89
- storageUri: firestoreData.storage_uri,
90
- fingerprintHash: firestoreData.fingerprint_hash,
91
- metadata: firestoreData?.metadata ?? {},
92
- rolloutCohortCount: firestoreData.rollout_cohort_count ?? _hot_updater_core.DEFAULT_ROLLOUT_COHORT_COUNT,
93
- targetCohorts: firestoreData.target_cohorts ?? null
94
- });
79
+ const convertToBundle = (firestoreData) => {
80
+ const rawMetadata = firestoreData.metadata;
81
+ const storedPatches = firestoreData.patches;
82
+ const patches = storedPatches && Array.isArray(storedPatches) ? storedPatches : (0, _hot_updater_core.getBundlePatches)({
83
+ metadata: rawMetadata,
84
+ patchBaseBundleId: firestoreData.patch_base_bundle_id ?? null,
85
+ patchBaseFileHash: firestoreData.patch_base_file_hash ?? null,
86
+ patchFileHash: firestoreData.patch_file_hash ?? null,
87
+ patchStorageUri: firestoreData.patch_storage_uri ?? null
88
+ });
89
+ const primaryPatch = patches[0] ?? null;
90
+ return {
91
+ channel: firestoreData.channel,
92
+ enabled: Boolean(firestoreData.enabled),
93
+ shouldForceUpdate: Boolean(firestoreData.should_force_update),
94
+ fileHash: firestoreData.file_hash,
95
+ gitCommitHash: firestoreData.git_commit_hash,
96
+ id: firestoreData.id,
97
+ message: firestoreData.message,
98
+ platform: firestoreData.platform,
99
+ targetAppVersion: firestoreData.target_app_version,
100
+ storageUri: firestoreData.storage_uri,
101
+ fingerprintHash: firestoreData.fingerprint_hash,
102
+ metadata: (0, _hot_updater_core.stripBundleArtifactMetadata)(rawMetadata),
103
+ manifestStorageUri: firestoreData.manifest_storage_uri ?? null,
104
+ manifestFileHash: firestoreData.manifest_file_hash ?? null,
105
+ assetBaseStorageUri: firestoreData.asset_base_storage_uri ?? null,
106
+ patches,
107
+ patchBaseBundleId: primaryPatch?.baseBundleId ?? firestoreData.patch_base_bundle_id ?? null,
108
+ patchBaseFileHash: primaryPatch?.baseFileHash ?? firestoreData.patch_base_file_hash ?? null,
109
+ patchFileHash: primaryPatch?.patchFileHash ?? firestoreData.patch_file_hash ?? null,
110
+ patchStorageUri: primaryPatch?.patchStorageUri ?? firestoreData.patch_storage_uri ?? null,
111
+ rolloutCohortCount: firestoreData.rollout_cohort_count ?? _hot_updater_core.DEFAULT_ROLLOUT_COHORT_COUNT,
112
+ targetCohorts: firestoreData.target_cohorts ?? null
113
+ };
114
+ };
95
115
  const firebaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
96
116
  name: "firebaseDatabase",
97
117
  factory: (config) => {
@@ -183,7 +203,15 @@ const firebaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
183
203
  target_app_version: data.targetAppVersion,
184
204
  storage_uri: data.storageUri,
185
205
  fingerprint_hash: data.fingerprintHash,
186
- metadata: data.metadata ?? {},
206
+ metadata: (0, _hot_updater_core.stripBundleArtifactMetadata)(data.metadata) ?? {},
207
+ manifest_storage_uri: (0, _hot_updater_core.getManifestStorageUri)(data),
208
+ manifest_file_hash: (0, _hot_updater_core.getManifestFileHash)(data),
209
+ asset_base_storage_uri: (0, _hot_updater_core.getAssetBaseStorageUri)(data),
210
+ patches: data.patches ?? null,
211
+ patch_base_bundle_id: (0, _hot_updater_core.getPatchBaseBundleId)(data),
212
+ patch_base_file_hash: (0, _hot_updater_core.getPatchBaseFileHash)(data),
213
+ patch_file_hash: (0, _hot_updater_core.getPatchFileHash)(data),
214
+ patch_storage_uri: (0, _hot_updater_core.getPatchStorageUri)(data),
187
215
  rollout_cohort_count: data.rolloutCohortCount ?? _hot_updater_core.DEFAULT_ROLLOUT_COHORT_COUNT,
188
216
  target_cohorts: data.targetCohorts ?? null
189
217
  };
@@ -219,7 +247,15 @@ const firebaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
219
247
  target_app_version: data.targetAppVersion || null,
220
248
  storage_uri: data.storageUri,
221
249
  fingerprint_hash: data.fingerprintHash,
222
- metadata: data.metadata ?? {},
250
+ metadata: (0, _hot_updater_core.stripBundleArtifactMetadata)(data.metadata) ?? {},
251
+ manifest_storage_uri: (0, _hot_updater_core.getManifestStorageUri)(data),
252
+ manifest_file_hash: (0, _hot_updater_core.getManifestFileHash)(data),
253
+ asset_base_storage_uri: (0, _hot_updater_core.getAssetBaseStorageUri)(data),
254
+ patches: data.patches ?? null,
255
+ patch_base_bundle_id: (0, _hot_updater_core.getPatchBaseBundleId)(data),
256
+ patch_base_file_hash: (0, _hot_updater_core.getPatchBaseFileHash)(data),
257
+ patch_file_hash: (0, _hot_updater_core.getPatchFileHash)(data),
258
+ patch_storage_uri: (0, _hot_updater_core.getPatchStorageUri)(data),
223
259
  rollout_cohort_count: data.rolloutCohortCount ?? _hot_updater_core.DEFAULT_ROLLOUT_COHORT_COUNT,
224
260
  target_cohorts: data.targetCohorts ?? null
225
261
  }, { merge: true });
@@ -245,7 +281,7 @@ const firebaseDatabase = (0, _hot_updater_plugin_core.createDatabasePlugin)({
245
281
  });
246
282
  //#endregion
247
283
  //#region src/firebaseStorage.ts
248
- const firebaseStorage = (0, _hot_updater_plugin_core.createStoragePlugin)({
284
+ const firebaseStorage = (0, _hot_updater_plugin_core.createUniversalStoragePlugin)({
249
285
  name: "firebaseStorage",
250
286
  supportedProtocol: "gs",
251
287
  factory: (config) => {
@@ -258,44 +294,65 @@ const firebaseStorage = (0, _hot_updater_plugin_core.createStoragePlugin)({
258
294
  const bucket = app.storage().bucket(config.storageBucket);
259
295
  const getStorageKey = (0, _hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
260
296
  return {
261
- async delete(storageUri) {
262
- const { bucket: bucketName, key } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "gs");
263
- if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
264
- try {
265
- const [files] = await bucket.getFiles({ prefix: key });
266
- await Promise.all(files.map((file) => file.delete()));
267
- } catch (e) {
268
- console.error("Error listing or deleting files:", e);
269
- throw new Error("Bundle Not Found");
297
+ node: {
298
+ async delete(storageUri) {
299
+ const { bucket: bucketName, key } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "gs");
300
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
301
+ try {
302
+ const [files] = await bucket.getFiles({ prefix: key });
303
+ await Promise.all(files.map((file) => file.delete()));
304
+ } catch (e) {
305
+ console.error("Error listing or deleting files:", e);
306
+ throw new Error("Bundle Not Found");
307
+ }
308
+ },
309
+ async upload(key, filePath) {
310
+ try {
311
+ const fileContent = await fs_promises.default.readFile(filePath);
312
+ const contentType = (0, _hot_updater_plugin_core.getContentType)(filePath);
313
+ const storageKey = getStorageKey(key, path.default.basename(filePath));
314
+ await bucket.file(storageKey).save(fileContent, { metadata: {
315
+ contentType,
316
+ cacheControl: "public, max-age=31536000, immutable"
317
+ } });
318
+ return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
319
+ } catch (error) {
320
+ console.error("Error uploading bundle:", error);
321
+ if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
322
+ throw error;
323
+ }
324
+ },
325
+ async downloadFile(storageUri, filePath) {
326
+ const { bucket: bucketName, key } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "gs");
327
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
328
+ await fs_promises.default.mkdir(path.default.dirname(filePath), { recursive: true });
329
+ await bucket.file(key).download({ destination: filePath });
270
330
  }
271
331
  },
272
- async upload(key, filePath) {
273
- try {
274
- const fileContent = await fs_promises.default.readFile(filePath);
275
- const contentType = (0, _hot_updater_plugin_core.getContentType)(filePath);
276
- const storageKey = getStorageKey(key, path.default.basename(filePath));
277
- await bucket.file(storageKey).save(fileContent, { metadata: {
278
- contentType,
279
- cacheControl: "public, max-age=31536000, immutable"
280
- } });
281
- return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
282
- } catch (error) {
283
- console.error("Error uploading bundle:", error);
284
- if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
285
- throw error;
332
+ runtime: {
333
+ async readText(storageUri) {
334
+ const { bucket: bucketName, key } = (0, _hot_updater_plugin_core.parseStorageUri)(storageUri, "gs");
335
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
336
+ try {
337
+ const [contents] = await bucket.file(key).download();
338
+ return contents.toString("utf8");
339
+ } catch (error) {
340
+ if (typeof error === "object" && error !== null && "code" in error && error.code === 404) return null;
341
+ throw error;
342
+ }
343
+ },
344
+ async getDownloadUrl(storageUri) {
345
+ const u = new URL(storageUri);
346
+ if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
347
+ const key = u.pathname.slice(1);
348
+ if (!key) throw new Error("Invalid Firebase storage URI: missing key");
349
+ const [signedUrl] = await bucket.file(key).getSignedUrl({
350
+ action: "read",
351
+ expires: Date.now() + 3600 * 1e3
352
+ });
353
+ if (!signedUrl) throw new Error("Failed to generate download URL");
354
+ return { fileUrl: signedUrl };
286
355
  }
287
- },
288
- async getDownloadUrl(storageUri) {
289
- const u = new URL(storageUri);
290
- if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
291
- const key = u.pathname.slice(1);
292
- if (!key) throw new Error("Invalid Firebase storage URI: missing key");
293
- const [signedUrl] = await bucket.file(key).getSignedUrl({
294
- action: "read",
295
- expires: Date.now() + 3600 * 1e3
296
- });
297
- if (!signedUrl) throw new Error("Failed to generate download URL");
298
- return { fileUrl: signedUrl };
299
356
  }
300
357
  };
301
358
  }
package/dist/index.d.cts CHANGED
@@ -12,6 +12,6 @@ interface FirebaseStorageConfig extends admin.AppOptions {
12
12
  */
13
13
  basePath?: string;
14
14
  }
15
- declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.StoragePlugin<unknown>;
15
+ declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.UniversalStoragePlugin<unknown>;
16
16
  //#endregion
17
17
  export { FirebaseStorageConfig, firebaseDatabase, firebaseStorage };
package/dist/index.d.mts CHANGED
@@ -12,6 +12,6 @@ interface FirebaseStorageConfig extends admin.AppOptions {
12
12
  */
13
13
  basePath?: string;
14
14
  }
15
- declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.StoragePlugin<unknown>;
15
+ declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: _$_hot_updater_plugin_core0.StoragePluginHooks) => () => _$_hot_updater_plugin_core0.UniversalStoragePlugin<unknown>;
16
16
  //#endregion
17
17
  export { FirebaseStorageConfig, firebaseDatabase, firebaseStorage };
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
- import { DEFAULT_ROLLOUT_COHORT_COUNT } from "@hot-updater/core";
2
- import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createStorageKeyBuilder, createStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
1
+ import { DEFAULT_ROLLOUT_COHORT_COUNT, getAssetBaseStorageUri, getBundlePatches, getManifestFileHash, getManifestStorageUri, getPatchBaseBundleId, getPatchBaseFileHash, getPatchFileHash, getPatchStorageUri, stripBundleArtifactMetadata } from "@hot-updater/core";
2
+ import { calculatePagination, createDatabasePlugin, createDatabasePluginGetUpdateInfo, createStorageKeyBuilder, createUniversalStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
3
3
  import admin from "firebase-admin";
4
4
  import fs from "fs/promises";
5
5
  import path from "path";
@@ -50,22 +50,42 @@ const chunkValues = (values, size) => {
50
50
  for (let index = 0; index < values.length; index += size) chunks.push(values.slice(index, index + size));
51
51
  return chunks;
52
52
  };
53
- const convertToBundle = (firestoreData) => ({
54
- channel: firestoreData.channel,
55
- enabled: Boolean(firestoreData.enabled),
56
- shouldForceUpdate: Boolean(firestoreData.should_force_update),
57
- fileHash: firestoreData.file_hash,
58
- gitCommitHash: firestoreData.git_commit_hash,
59
- id: firestoreData.id,
60
- message: firestoreData.message,
61
- platform: firestoreData.platform,
62
- targetAppVersion: firestoreData.target_app_version,
63
- storageUri: firestoreData.storage_uri,
64
- fingerprintHash: firestoreData.fingerprint_hash,
65
- metadata: firestoreData?.metadata ?? {},
66
- rolloutCohortCount: firestoreData.rollout_cohort_count ?? DEFAULT_ROLLOUT_COHORT_COUNT,
67
- targetCohorts: firestoreData.target_cohorts ?? null
68
- });
53
+ const convertToBundle = (firestoreData) => {
54
+ const rawMetadata = firestoreData.metadata;
55
+ const storedPatches = firestoreData.patches;
56
+ const patches = storedPatches && Array.isArray(storedPatches) ? storedPatches : getBundlePatches({
57
+ metadata: rawMetadata,
58
+ patchBaseBundleId: firestoreData.patch_base_bundle_id ?? null,
59
+ patchBaseFileHash: firestoreData.patch_base_file_hash ?? null,
60
+ patchFileHash: firestoreData.patch_file_hash ?? null,
61
+ patchStorageUri: firestoreData.patch_storage_uri ?? null
62
+ });
63
+ const primaryPatch = patches[0] ?? null;
64
+ return {
65
+ channel: firestoreData.channel,
66
+ enabled: Boolean(firestoreData.enabled),
67
+ shouldForceUpdate: Boolean(firestoreData.should_force_update),
68
+ fileHash: firestoreData.file_hash,
69
+ gitCommitHash: firestoreData.git_commit_hash,
70
+ id: firestoreData.id,
71
+ message: firestoreData.message,
72
+ platform: firestoreData.platform,
73
+ targetAppVersion: firestoreData.target_app_version,
74
+ storageUri: firestoreData.storage_uri,
75
+ fingerprintHash: firestoreData.fingerprint_hash,
76
+ metadata: stripBundleArtifactMetadata(rawMetadata),
77
+ manifestStorageUri: firestoreData.manifest_storage_uri ?? null,
78
+ manifestFileHash: firestoreData.manifest_file_hash ?? null,
79
+ assetBaseStorageUri: firestoreData.asset_base_storage_uri ?? null,
80
+ patches,
81
+ patchBaseBundleId: primaryPatch?.baseBundleId ?? firestoreData.patch_base_bundle_id ?? null,
82
+ patchBaseFileHash: primaryPatch?.baseFileHash ?? firestoreData.patch_base_file_hash ?? null,
83
+ patchFileHash: primaryPatch?.patchFileHash ?? firestoreData.patch_file_hash ?? null,
84
+ patchStorageUri: primaryPatch?.patchStorageUri ?? firestoreData.patch_storage_uri ?? null,
85
+ rolloutCohortCount: firestoreData.rollout_cohort_count ?? DEFAULT_ROLLOUT_COHORT_COUNT,
86
+ targetCohorts: firestoreData.target_cohorts ?? null
87
+ };
88
+ };
69
89
  const firebaseDatabase = createDatabasePlugin({
70
90
  name: "firebaseDatabase",
71
91
  factory: (config) => {
@@ -157,7 +177,15 @@ const firebaseDatabase = createDatabasePlugin({
157
177
  target_app_version: data.targetAppVersion,
158
178
  storage_uri: data.storageUri,
159
179
  fingerprint_hash: data.fingerprintHash,
160
- metadata: data.metadata ?? {},
180
+ metadata: stripBundleArtifactMetadata(data.metadata) ?? {},
181
+ manifest_storage_uri: getManifestStorageUri(data),
182
+ manifest_file_hash: getManifestFileHash(data),
183
+ asset_base_storage_uri: getAssetBaseStorageUri(data),
184
+ patches: data.patches ?? null,
185
+ patch_base_bundle_id: getPatchBaseBundleId(data),
186
+ patch_base_file_hash: getPatchBaseFileHash(data),
187
+ patch_file_hash: getPatchFileHash(data),
188
+ patch_storage_uri: getPatchStorageUri(data),
161
189
  rollout_cohort_count: data.rolloutCohortCount ?? DEFAULT_ROLLOUT_COHORT_COUNT,
162
190
  target_cohorts: data.targetCohorts ?? null
163
191
  };
@@ -193,7 +221,15 @@ const firebaseDatabase = createDatabasePlugin({
193
221
  target_app_version: data.targetAppVersion || null,
194
222
  storage_uri: data.storageUri,
195
223
  fingerprint_hash: data.fingerprintHash,
196
- metadata: data.metadata ?? {},
224
+ metadata: stripBundleArtifactMetadata(data.metadata) ?? {},
225
+ manifest_storage_uri: getManifestStorageUri(data),
226
+ manifest_file_hash: getManifestFileHash(data),
227
+ asset_base_storage_uri: getAssetBaseStorageUri(data),
228
+ patches: data.patches ?? null,
229
+ patch_base_bundle_id: getPatchBaseBundleId(data),
230
+ patch_base_file_hash: getPatchBaseFileHash(data),
231
+ patch_file_hash: getPatchFileHash(data),
232
+ patch_storage_uri: getPatchStorageUri(data),
197
233
  rollout_cohort_count: data.rolloutCohortCount ?? DEFAULT_ROLLOUT_COHORT_COUNT,
198
234
  target_cohorts: data.targetCohorts ?? null
199
235
  }, { merge: true });
@@ -219,7 +255,7 @@ const firebaseDatabase = createDatabasePlugin({
219
255
  });
220
256
  //#endregion
221
257
  //#region src/firebaseStorage.ts
222
- const firebaseStorage = createStoragePlugin({
258
+ const firebaseStorage = createUniversalStoragePlugin({
223
259
  name: "firebaseStorage",
224
260
  supportedProtocol: "gs",
225
261
  factory: (config) => {
@@ -232,44 +268,65 @@ const firebaseStorage = createStoragePlugin({
232
268
  const bucket = app.storage().bucket(config.storageBucket);
233
269
  const getStorageKey = createStorageKeyBuilder(config.basePath);
234
270
  return {
235
- async delete(storageUri) {
236
- const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
237
- if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
238
- try {
239
- const [files] = await bucket.getFiles({ prefix: key });
240
- await Promise.all(files.map((file) => file.delete()));
241
- } catch (e) {
242
- console.error("Error listing or deleting files:", e);
243
- throw new Error("Bundle Not Found");
271
+ node: {
272
+ async delete(storageUri) {
273
+ const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
274
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
275
+ try {
276
+ const [files] = await bucket.getFiles({ prefix: key });
277
+ await Promise.all(files.map((file) => file.delete()));
278
+ } catch (e) {
279
+ console.error("Error listing or deleting files:", e);
280
+ throw new Error("Bundle Not Found");
281
+ }
282
+ },
283
+ async upload(key, filePath) {
284
+ try {
285
+ const fileContent = await fs.readFile(filePath);
286
+ const contentType = getContentType(filePath);
287
+ const storageKey = getStorageKey(key, path.basename(filePath));
288
+ await bucket.file(storageKey).save(fileContent, { metadata: {
289
+ contentType,
290
+ cacheControl: "public, max-age=31536000, immutable"
291
+ } });
292
+ return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
293
+ } catch (error) {
294
+ console.error("Error uploading bundle:", error);
295
+ if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
296
+ throw error;
297
+ }
298
+ },
299
+ async downloadFile(storageUri, filePath) {
300
+ const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
301
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
302
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
303
+ await bucket.file(key).download({ destination: filePath });
244
304
  }
245
305
  },
246
- async upload(key, filePath) {
247
- try {
248
- const fileContent = await fs.readFile(filePath);
249
- const contentType = getContentType(filePath);
250
- const storageKey = getStorageKey(key, path.basename(filePath));
251
- await bucket.file(storageKey).save(fileContent, { metadata: {
252
- contentType,
253
- cacheControl: "public, max-age=31536000, immutable"
254
- } });
255
- return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
256
- } catch (error) {
257
- console.error("Error uploading bundle:", error);
258
- if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
259
- throw error;
306
+ runtime: {
307
+ async readText(storageUri) {
308
+ const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
309
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
310
+ try {
311
+ const [contents] = await bucket.file(key).download();
312
+ return contents.toString("utf8");
313
+ } catch (error) {
314
+ if (typeof error === "object" && error !== null && "code" in error && error.code === 404) return null;
315
+ throw error;
316
+ }
317
+ },
318
+ async getDownloadUrl(storageUri) {
319
+ const u = new URL(storageUri);
320
+ if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
321
+ const key = u.pathname.slice(1);
322
+ if (!key) throw new Error("Invalid Firebase storage URI: missing key");
323
+ const [signedUrl] = await bucket.file(key).getSignedUrl({
324
+ action: "read",
325
+ expires: Date.now() + 3600 * 1e3
326
+ });
327
+ if (!signedUrl) throw new Error("Failed to generate download URL");
328
+ return { fileUrl: signedUrl };
260
329
  }
261
- },
262
- async getDownloadUrl(storageUri) {
263
- const u = new URL(storageUri);
264
- if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
265
- const key = u.pathname.slice(1);
266
- if (!key) throw new Error("Invalid Firebase storage URI: missing key");
267
- const [signedUrl] = await bucket.file(key).getSignedUrl({
268
- action: "read",
269
- expires: Date.now() + 3600 * 1e3
270
- });
271
- if (!signedUrl) throw new Error("Failed to generate download URL");
272
- return { fileUrl: signedUrl };
273
330
  }
274
331
  };
275
332
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/firebase",
3
3
  "type": "module",
4
- "version": "0.30.12",
4
+ "version": "0.31.1",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "types": "dist/index.d.cts",
@@ -36,10 +36,10 @@
36
36
  "dependencies": {
37
37
  "firebase": "^11.3.1",
38
38
  "hono": "4.12.9",
39
- "@hot-updater/core": "0.30.12",
40
- "@hot-updater/cli-tools": "0.30.12",
41
- "@hot-updater/plugin-core": "0.30.12",
42
- "@hot-updater/server": "0.30.12"
39
+ "@hot-updater/plugin-core": "0.31.1",
40
+ "@hot-updater/server": "0.31.1",
41
+ "@hot-updater/cli-tools": "0.31.1",
42
+ "@hot-updater/core": "0.31.1"
43
43
  },
44
44
  "publishConfig": {
45
45
  "access": "public"
@@ -54,9 +54,9 @@
54
54
  "firebase-tools": "^13.32.0",
55
55
  "fkill": "^9.0.0",
56
56
  "mime": "^4.0.4",
57
- "@hot-updater/js": "0.30.12",
58
- "@hot-updater/test-utils": "0.30.12",
59
- "@hot-updater/mock": "0.30.12"
57
+ "@hot-updater/js": "0.31.1",
58
+ "@hot-updater/mock": "0.31.1",
59
+ "@hot-updater/test-utils": "0.31.1"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "firebase-admin": "*",