@hot-updater/firebase 0.30.12 → 0.31.0
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/firebase/functions/index.cjs +258 -122
- package/dist/iac/index.cjs +0 -1
- package/dist/iac/index.mjs +0 -1
- package/dist/index.cjs +111 -54
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.mjs +113 -56
- package/package.json +8 -8
|
@@ -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
|
|
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
|
|
4663
|
+
const createRuntimeStoragePlugin = (options) => {
|
|
4586
4664
|
return (config, hooks) => {
|
|
4587
|
-
return () => {
|
|
4588
|
-
|
|
4589
|
-
|
|
4590
|
-
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
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
|
-
|
|
4693
|
-
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
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 =
|
|
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
|
-
|
|
4874
|
-
|
|
4875
|
-
|
|
4876
|
-
|
|
4877
|
-
|
|
4878
|
-
|
|
4879
|
-
|
|
4880
|
-
|
|
4881
|
-
|
|
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
|
-
|
|
4885
|
-
|
|
4886
|
-
const
|
|
4887
|
-
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
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
|
|
4921
|
-
|
|
4922
|
-
|
|
4923
|
-
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
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
|
-
|
|
4929
|
-
|
|
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
|
-
|
|
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
|
|
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({
|
package/dist/iac/index.cjs
CHANGED
package/dist/iac/index.mjs
CHANGED
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
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
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.
|
|
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
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
const
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
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 =
|
|
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
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
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.
|
|
4
|
+
"version": "0.31.0",
|
|
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/
|
|
40
|
-
"@hot-updater/
|
|
41
|
-
"@hot-updater/
|
|
42
|
-
"@hot-updater/
|
|
39
|
+
"@hot-updater/cli-tools": "0.31.0",
|
|
40
|
+
"@hot-updater/server": "0.31.0",
|
|
41
|
+
"@hot-updater/core": "0.31.0",
|
|
42
|
+
"@hot-updater/plugin-core": "0.31.0"
|
|
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.
|
|
58
|
-
"@hot-updater/
|
|
59
|
-
"@hot-updater/
|
|
57
|
+
"@hot-updater/js": "0.31.0",
|
|
58
|
+
"@hot-updater/mock": "0.31.0",
|
|
59
|
+
"@hot-updater/test-utils": "0.31.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"firebase-admin": "*",
|