@hot-updater/firebase 0.21.10 → 0.21.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
@@ -45,194 +45,192 @@ const convertToBundle = (firestoreData) => ({
45
45
  fingerprintHash: firestoreData.fingerprint_hash,
46
46
  metadata: firestoreData?.metadata ?? {}
47
47
  });
48
- const firebaseDatabase = (config, hooks) => {
49
- let bundles = [];
50
- return (0, __hot_updater_plugin_core.createDatabasePlugin)("firebaseDatabase", {
51
- getContext: () => {
52
- let app;
53
- try {
54
- app = firebase_admin.app();
55
- } catch {
56
- app = firebase_admin.initializeApp(config);
57
- }
58
- const db = firebase_admin.firestore(app);
59
- return {
60
- db,
61
- bundlesCollection: db.collection("bundles")
62
- };
63
- },
64
- async getBundleById(context, bundleId) {
65
- const found = bundles.find((b) => b.id === bundleId);
66
- if (found) return found;
67
- const bundleSnap = await context.bundlesCollection.doc(bundleId).get();
68
- if (!bundleSnap.exists) return null;
69
- return convertToBundle(bundleSnap.data());
70
- },
71
- async getBundles(context, options) {
72
- const { where, limit, offset } = options;
73
- let query = context.bundlesCollection;
74
- if (where?.channel) query = query.where("channel", "==", where.channel);
75
- if (where?.platform) query = query.where("platform", "==", where.platform);
76
- query = query.orderBy("id", "desc");
77
- const total = (await query.get()).size;
78
- if (offset > 0) query = query.offset(offset);
79
- if (limit) query = query.limit(limit);
80
- bundles = (await query.get()).docs.map((doc) => convertToBundle(doc.data()));
81
- return {
82
- data: bundles,
83
- pagination: (0, __hot_updater_plugin_core.calculatePagination)(total, {
84
- limit,
85
- offset
86
- })
87
- };
88
- },
89
- async getChannels(context) {
90
- const querySnapshot = await context.db.collection("channels").get();
91
- if (querySnapshot.empty) return [];
92
- const channels = /* @__PURE__ */ new Set();
93
- for (const doc of querySnapshot.docs) {
94
- const data = doc.data();
95
- if (data.name) channels.add(data.name);
96
- }
97
- return Array.from(channels);
98
- },
99
- async commitBundle(context, { changedSets }) {
100
- if (changedSets.length === 0) return;
101
- let isTargetAppVersionChanged = false;
102
- const deletedBundleIds = /* @__PURE__ */ new Set();
103
- await context.db.runTransaction(async (transaction) => {
104
- const bundlesSnapshot = await transaction.get(context.bundlesCollection);
105
- const targetVersionsSnapshot = await transaction.get(context.db.collection("target_app_versions"));
106
- const channelsSnapshot = await transaction.get(context.db.collection("channels"));
107
- const bundlesMap = {};
108
- for (const doc of bundlesSnapshot.docs) bundlesMap[doc.id] = doc.data();
109
- for (const { operation, data } of changedSets) {
110
- if (data.targetAppVersion) isTargetAppVersionChanged = true;
111
- if (operation === "insert" || operation === "update") {
112
- bundlesMap[data.id] = {
113
- id: data.id,
114
- channel: data.channel,
115
- enabled: data.enabled,
116
- should_force_update: data.shouldForceUpdate,
117
- file_hash: data.fileHash,
118
- git_commit_hash: data.gitCommitHash || null,
119
- message: data.message || null,
120
- platform: data.platform,
121
- target_app_version: data.targetAppVersion,
122
- storage_uri: data.storageUri,
123
- fingerprint_hash: data.fingerprintHash,
124
- metadata: data.metadata ?? {}
125
- };
126
- const channelRef = context.db.collection("channels").doc(data.channel);
127
- transaction.set(channelRef, { name: data.channel }, { merge: true });
128
- } else if (operation === "delete") {
129
- if (!bundlesMap[data.id]) throw new Error(`Bundle with id ${data.id} not found`);
130
- delete bundlesMap[data.id];
131
- deletedBundleIds.add(data.id);
132
- isTargetAppVersionChanged = true;
133
- }
48
+ const firebaseDatabase = (0, __hot_updater_plugin_core.createDatabasePlugin)({
49
+ name: "firebaseDatabase",
50
+ factory: (config) => {
51
+ let bundles = [];
52
+ let app;
53
+ try {
54
+ app = firebase_admin.app();
55
+ } catch {
56
+ app = firebase_admin.initializeApp(config);
57
+ }
58
+ const db = firebase_admin.firestore(app);
59
+ const bundlesCollection = db.collection("bundles");
60
+ return {
61
+ async getBundleById(bundleId) {
62
+ const found = bundles.find((b) => b.id === bundleId);
63
+ if (found) return found;
64
+ const bundleSnap = await bundlesCollection.doc(bundleId).get();
65
+ if (!bundleSnap.exists) return null;
66
+ return convertToBundle(bundleSnap.data());
67
+ },
68
+ async getBundles(options) {
69
+ const { where, limit, offset } = options;
70
+ let query = bundlesCollection;
71
+ if (where?.channel) query = query.where("channel", "==", where.channel);
72
+ if (where?.platform) query = query.where("platform", "==", where.platform);
73
+ query = query.orderBy("id", "desc");
74
+ const total = (await query.get()).size;
75
+ if (offset > 0) query = query.offset(offset);
76
+ if (limit) query = query.limit(limit);
77
+ bundles = (await query.get()).docs.map((doc) => convertToBundle(doc.data()));
78
+ return {
79
+ data: bundles,
80
+ pagination: (0, __hot_updater_plugin_core.calculatePagination)(total, {
81
+ limit,
82
+ offset
83
+ })
84
+ };
85
+ },
86
+ async getChannels() {
87
+ const querySnapshot = await db.collection("channels").get();
88
+ if (querySnapshot.empty) return [];
89
+ const channels = /* @__PURE__ */ new Set();
90
+ for (const doc of querySnapshot.docs) {
91
+ const data = doc.data();
92
+ if (data.name) channels.add(data.name);
134
93
  }
135
- const requiredTargetVersionKeys = /* @__PURE__ */ new Set();
136
- const requiredChannels = /* @__PURE__ */ new Set();
137
- for (const bundle of Object.values(bundlesMap)) {
138
- if (bundle.target_app_version) {
139
- const key = `${bundle.platform}_${bundle.channel}_${bundle.target_app_version}`;
140
- requiredTargetVersionKeys.add(key);
94
+ return Array.from(channels);
95
+ },
96
+ async commitBundle({ changedSets }) {
97
+ if (changedSets.length === 0) return;
98
+ let isTargetAppVersionChanged = false;
99
+ const deletedBundleIds = /* @__PURE__ */ new Set();
100
+ await db.runTransaction(async (transaction) => {
101
+ const bundlesSnapshot = await transaction.get(bundlesCollection);
102
+ const targetVersionsSnapshot = await transaction.get(db.collection("target_app_versions"));
103
+ const channelsSnapshot = await transaction.get(db.collection("channels"));
104
+ const bundlesMap = {};
105
+ for (const doc of bundlesSnapshot.docs) bundlesMap[doc.id] = doc.data();
106
+ for (const { operation, data } of changedSets) {
107
+ if (data.targetAppVersion) isTargetAppVersionChanged = true;
108
+ if (operation === "insert" || operation === "update") {
109
+ bundlesMap[data.id] = {
110
+ id: data.id,
111
+ channel: data.channel,
112
+ enabled: data.enabled,
113
+ should_force_update: data.shouldForceUpdate,
114
+ file_hash: data.fileHash,
115
+ git_commit_hash: data.gitCommitHash || null,
116
+ message: data.message || null,
117
+ platform: data.platform,
118
+ target_app_version: data.targetAppVersion,
119
+ storage_uri: data.storageUri,
120
+ fingerprint_hash: data.fingerprintHash,
121
+ metadata: data.metadata ?? {}
122
+ };
123
+ const channelRef = db.collection("channels").doc(data.channel);
124
+ transaction.set(channelRef, { name: data.channel }, { merge: true });
125
+ } else if (operation === "delete") {
126
+ if (!bundlesMap[data.id]) throw new Error(`Bundle with id ${data.id} not found`);
127
+ delete bundlesMap[data.id];
128
+ deletedBundleIds.add(data.id);
129
+ isTargetAppVersionChanged = true;
130
+ }
141
131
  }
142
- requiredChannels.add(bundle.channel);
143
- }
144
- for (const { operation, data } of changedSets) {
145
- const bundleRef = context.bundlesCollection.doc(data.id);
146
- if (operation === "insert" || operation === "update") {
147
- transaction.set(bundleRef, {
148
- id: data.id,
149
- channel: data.channel,
150
- enabled: data.enabled,
151
- should_force_update: data.shouldForceUpdate,
152
- file_hash: data.fileHash,
153
- git_commit_hash: data.gitCommitHash || null,
154
- message: data.message || null,
155
- platform: data.platform,
156
- target_app_version: data.targetAppVersion || null,
157
- storage_uri: data.storageUri,
158
- fingerprint_hash: data.fingerprintHash,
159
- metadata: data.metadata ?? {}
160
- }, { merge: true });
161
- if (data.targetAppVersion) {
162
- const versionDocId = `${data.platform}_${data.channel}_${data.targetAppVersion}`;
163
- const targetAppVersionsRef = context.db.collection("target_app_versions").doc(versionDocId);
164
- transaction.set(targetAppVersionsRef, {
132
+ const requiredTargetVersionKeys = /* @__PURE__ */ new Set();
133
+ const requiredChannels = /* @__PURE__ */ new Set();
134
+ for (const bundle of Object.values(bundlesMap)) {
135
+ if (bundle.target_app_version) {
136
+ const key = `${bundle.platform}_${bundle.channel}_${bundle.target_app_version}`;
137
+ requiredTargetVersionKeys.add(key);
138
+ }
139
+ requiredChannels.add(bundle.channel);
140
+ }
141
+ for (const { operation, data } of changedSets) {
142
+ const bundleRef = bundlesCollection.doc(data.id);
143
+ if (operation === "insert" || operation === "update") {
144
+ transaction.set(bundleRef, {
145
+ id: data.id,
165
146
  channel: data.channel,
147
+ enabled: data.enabled,
148
+ should_force_update: data.shouldForceUpdate,
149
+ file_hash: data.fileHash,
150
+ git_commit_hash: data.gitCommitHash || null,
151
+ message: data.message || null,
166
152
  platform: data.platform,
167
- target_app_version: data.targetAppVersion
153
+ target_app_version: data.targetAppVersion || null,
154
+ storage_uri: data.storageUri,
155
+ fingerprint_hash: data.fingerprintHash,
156
+ metadata: data.metadata ?? {}
168
157
  }, { merge: true });
169
- }
170
- } else if (operation === "delete") transaction.delete(bundleRef);
171
- }
172
- if (isTargetAppVersionChanged) {
173
- for (const targetDoc of targetVersionsSnapshot.docs) if (!requiredTargetVersionKeys.has(targetDoc.id)) transaction.delete(targetDoc.ref);
174
- }
175
- for (const channelDoc of channelsSnapshot.docs) if (!requiredChannels.has(channelDoc.id)) transaction.delete(channelDoc.ref);
176
- });
177
- for (const bundleId of deletedBundleIds) bundles = bundles.filter((b) => b.id !== bundleId);
178
- hooks?.onDatabaseUpdated?.();
179
- }
180
- }, hooks);
181
- };
158
+ if (data.targetAppVersion) {
159
+ const versionDocId = `${data.platform}_${data.channel}_${data.targetAppVersion}`;
160
+ const targetAppVersionsRef = db.collection("target_app_versions").doc(versionDocId);
161
+ transaction.set(targetAppVersionsRef, {
162
+ channel: data.channel,
163
+ platform: data.platform,
164
+ target_app_version: data.targetAppVersion
165
+ }, { merge: true });
166
+ }
167
+ } else if (operation === "delete") transaction.delete(bundleRef);
168
+ }
169
+ if (isTargetAppVersionChanged) {
170
+ for (const targetDoc of targetVersionsSnapshot.docs) if (!requiredTargetVersionKeys.has(targetDoc.id)) transaction.delete(targetDoc.ref);
171
+ }
172
+ for (const channelDoc of channelsSnapshot.docs) if (!requiredChannels.has(channelDoc.id)) transaction.delete(channelDoc.ref);
173
+ });
174
+ for (const bundleId of deletedBundleIds) bundles = bundles.filter((b) => b.id !== bundleId);
175
+ }
176
+ };
177
+ }
178
+ });
182
179
 
183
180
  //#endregion
184
181
  //#region src/firebaseStorage.ts
185
- const firebaseStorage = (config, hooks) => (_) => {
186
- let app;
187
- try {
188
- app = firebase_admin.app();
189
- } catch {
190
- app = firebase_admin.initializeApp(config);
191
- }
192
- const bucket = app.storage().bucket(config.storageBucket);
193
- const getStorageKey = (0, __hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
194
- return {
195
- name: "firebaseStorage",
196
- supportedProtocol: "gs",
197
- async delete(storageUri) {
198
- const { bucket: bucketName, key } = (0, __hot_updater_plugin_core.parseStorageUri)(storageUri, "gs");
199
- if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
200
- try {
201
- const [files] = await bucket.getFiles({ prefix: key });
202
- await Promise.all(files.map((file) => file.delete()));
203
- } catch (e) {
204
- console.error("Error listing or deleting files:", e);
205
- throw new Error("Bundle Not Found");
206
- }
207
- },
208
- async upload(key, filePath) {
209
- try {
210
- const fileContent = await fs_promises.default.readFile(filePath);
211
- const contentType = (0, __hot_updater_plugin_core.getContentType)(filePath);
212
- const storageKey = getStorageKey(key, path.default.basename(filePath));
213
- await bucket.file(storageKey).save(fileContent, { metadata: { contentType } });
214
- hooks?.onStorageUploaded?.();
215
- return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
216
- } catch (error) {
217
- console.error("Error uploading bundle:", error);
218
- if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
219
- throw error;
220
- }
221
- },
222
- async getDownloadUrl(storageUri) {
223
- const u = new URL(storageUri);
224
- if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
225
- const key = u.pathname.slice(1);
226
- if (!key) throw new Error("Invalid Firebase storage URI: missing key");
227
- const [signedUrl] = await bucket.file(key).getSignedUrl({
228
- action: "read",
229
- expires: Date.now() + 3600 * 1e3
230
- });
231
- if (!signedUrl) throw new Error("Failed to generate download URL");
232
- return { fileUrl: signedUrl };
182
+ const firebaseStorage = (0, __hot_updater_plugin_core.createStoragePlugin)({
183
+ name: "firebaseStorage",
184
+ supportedProtocol: "gs",
185
+ factory: (config) => {
186
+ let app;
187
+ try {
188
+ app = firebase_admin.app();
189
+ } catch {
190
+ app = firebase_admin.initializeApp(config);
233
191
  }
234
- };
235
- };
192
+ const bucket = app.storage().bucket(config.storageBucket);
193
+ const getStorageKey = (0, __hot_updater_plugin_core.createStorageKeyBuilder)(config.basePath);
194
+ return {
195
+ async delete(storageUri) {
196
+ const { bucket: bucketName, key } = (0, __hot_updater_plugin_core.parseStorageUri)(storageUri, "gs");
197
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
198
+ try {
199
+ const [files] = await bucket.getFiles({ prefix: key });
200
+ await Promise.all(files.map((file) => file.delete()));
201
+ } catch (e) {
202
+ console.error("Error listing or deleting files:", e);
203
+ throw new Error("Bundle Not Found");
204
+ }
205
+ },
206
+ async upload(key, filePath) {
207
+ try {
208
+ const fileContent = await fs_promises.default.readFile(filePath);
209
+ const contentType = (0, __hot_updater_plugin_core.getContentType)(filePath);
210
+ const storageKey = getStorageKey(key, path.default.basename(filePath));
211
+ await bucket.file(storageKey).save(fileContent, { metadata: { contentType } });
212
+ return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
213
+ } catch (error) {
214
+ console.error("Error uploading bundle:", error);
215
+ if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
216
+ throw error;
217
+ }
218
+ },
219
+ async getDownloadUrl(storageUri) {
220
+ const u = new URL(storageUri);
221
+ if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
222
+ const key = u.pathname.slice(1);
223
+ if (!key) throw new Error("Invalid Firebase storage URI: missing key");
224
+ const [signedUrl] = await bucket.file(key).getSignedUrl({
225
+ action: "read",
226
+ expires: Date.now() + 3600 * 1e3
227
+ });
228
+ if (!signedUrl) throw new Error("Failed to generate download URL");
229
+ return { fileUrl: signedUrl };
230
+ }
231
+ };
232
+ }
233
+ });
236
234
 
237
235
  //#endregion
238
236
  exports.firebaseDatabase = firebaseDatabase;
package/dist/index.d.cts CHANGED
@@ -1,9 +1,8 @@
1
- import * as _hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
- import { BasePluginArgs, DatabasePluginHooks, StoragePlugin, StoragePluginHooks } from "@hot-updater/plugin-core";
1
+ import * as _hot_updater_plugin_core1 from "@hot-updater/plugin-core";
3
2
  import * as admin from "firebase-admin";
4
3
 
5
4
  //#region src/firebaseDatabase.d.ts
6
- declare const firebaseDatabase: (config: admin.AppOptions, hooks?: DatabasePluginHooks) => (options: _hot_updater_plugin_core0.BasePluginArgs) => _hot_updater_plugin_core0.DatabasePlugin;
5
+ declare const firebaseDatabase: (config: admin.AppOptions, hooks?: _hot_updater_plugin_core1.DatabasePluginHooks) => (() => _hot_updater_plugin_core1.DatabasePlugin);
7
6
  //#endregion
8
7
  //#region src/firebaseStorage.d.ts
9
8
  interface FirebaseStorageConfig extends admin.AppOptions {
@@ -13,6 +12,6 @@ interface FirebaseStorageConfig extends admin.AppOptions {
13
12
  */
14
13
  basePath?: string;
15
14
  }
16
- declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: StoragePluginHooks) => (_: BasePluginArgs) => StoragePlugin;
15
+ declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: _hot_updater_plugin_core1.StoragePluginHooks) => () => _hot_updater_plugin_core1.StoragePlugin;
17
16
  //#endregion
18
17
  export { FirebaseStorageConfig, firebaseDatabase, firebaseStorage };
package/dist/index.d.ts CHANGED
@@ -1,9 +1,8 @@
1
- import * as _hot_updater_plugin_core0 from "@hot-updater/plugin-core";
2
- import { BasePluginArgs, DatabasePluginHooks, StoragePlugin, StoragePluginHooks } from "@hot-updater/plugin-core";
1
+ import * as _hot_updater_plugin_core1 from "@hot-updater/plugin-core";
3
2
  import * as admin from "firebase-admin";
4
3
 
5
4
  //#region src/firebaseDatabase.d.ts
6
- declare const firebaseDatabase: (config: admin.AppOptions, hooks?: DatabasePluginHooks) => (options: _hot_updater_plugin_core0.BasePluginArgs) => _hot_updater_plugin_core0.DatabasePlugin;
5
+ declare const firebaseDatabase: (config: admin.AppOptions, hooks?: _hot_updater_plugin_core1.DatabasePluginHooks) => (() => _hot_updater_plugin_core1.DatabasePlugin);
7
6
  //#endregion
8
7
  //#region src/firebaseStorage.d.ts
9
8
  interface FirebaseStorageConfig extends admin.AppOptions {
@@ -13,6 +12,6 @@ interface FirebaseStorageConfig extends admin.AppOptions {
13
12
  */
14
13
  basePath?: string;
15
14
  }
16
- declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: StoragePluginHooks) => (_: BasePluginArgs) => StoragePlugin;
15
+ declare const firebaseStorage: (config: FirebaseStorageConfig, hooks?: _hot_updater_plugin_core1.StoragePluginHooks) => () => _hot_updater_plugin_core1.StoragePlugin;
17
16
  //#endregion
18
17
  export { FirebaseStorageConfig, firebaseDatabase, firebaseStorage };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { calculatePagination, createDatabasePlugin, createStorageKeyBuilder, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
1
+ import { calculatePagination, createDatabasePlugin, createStorageKeyBuilder, createStoragePlugin, getContentType, parseStorageUri } from "@hot-updater/plugin-core";
2
2
  import * as admin from "firebase-admin";
3
3
  import fs from "fs/promises";
4
4
  import path from "path";
@@ -18,194 +18,192 @@ const convertToBundle = (firestoreData) => ({
18
18
  fingerprintHash: firestoreData.fingerprint_hash,
19
19
  metadata: firestoreData?.metadata ?? {}
20
20
  });
21
- const firebaseDatabase = (config, hooks) => {
22
- let bundles = [];
23
- return createDatabasePlugin("firebaseDatabase", {
24
- getContext: () => {
25
- let app;
26
- try {
27
- app = admin.app();
28
- } catch {
29
- app = admin.initializeApp(config);
30
- }
31
- const db = admin.firestore(app);
32
- return {
33
- db,
34
- bundlesCollection: db.collection("bundles")
35
- };
36
- },
37
- async getBundleById(context, bundleId) {
38
- const found = bundles.find((b) => b.id === bundleId);
39
- if (found) return found;
40
- const bundleSnap = await context.bundlesCollection.doc(bundleId).get();
41
- if (!bundleSnap.exists) return null;
42
- return convertToBundle(bundleSnap.data());
43
- },
44
- async getBundles(context, options) {
45
- const { where, limit, offset } = options;
46
- let query = context.bundlesCollection;
47
- if (where?.channel) query = query.where("channel", "==", where.channel);
48
- if (where?.platform) query = query.where("platform", "==", where.platform);
49
- query = query.orderBy("id", "desc");
50
- const total = (await query.get()).size;
51
- if (offset > 0) query = query.offset(offset);
52
- if (limit) query = query.limit(limit);
53
- bundles = (await query.get()).docs.map((doc) => convertToBundle(doc.data()));
54
- return {
55
- data: bundles,
56
- pagination: calculatePagination(total, {
57
- limit,
58
- offset
59
- })
60
- };
61
- },
62
- async getChannels(context) {
63
- const querySnapshot = await context.db.collection("channels").get();
64
- if (querySnapshot.empty) return [];
65
- const channels = /* @__PURE__ */ new Set();
66
- for (const doc of querySnapshot.docs) {
67
- const data = doc.data();
68
- if (data.name) channels.add(data.name);
69
- }
70
- return Array.from(channels);
71
- },
72
- async commitBundle(context, { changedSets }) {
73
- if (changedSets.length === 0) return;
74
- let isTargetAppVersionChanged = false;
75
- const deletedBundleIds = /* @__PURE__ */ new Set();
76
- await context.db.runTransaction(async (transaction) => {
77
- const bundlesSnapshot = await transaction.get(context.bundlesCollection);
78
- const targetVersionsSnapshot = await transaction.get(context.db.collection("target_app_versions"));
79
- const channelsSnapshot = await transaction.get(context.db.collection("channels"));
80
- const bundlesMap = {};
81
- for (const doc of bundlesSnapshot.docs) bundlesMap[doc.id] = doc.data();
82
- for (const { operation, data } of changedSets) {
83
- if (data.targetAppVersion) isTargetAppVersionChanged = true;
84
- if (operation === "insert" || operation === "update") {
85
- bundlesMap[data.id] = {
86
- id: data.id,
87
- channel: data.channel,
88
- enabled: data.enabled,
89
- should_force_update: data.shouldForceUpdate,
90
- file_hash: data.fileHash,
91
- git_commit_hash: data.gitCommitHash || null,
92
- message: data.message || null,
93
- platform: data.platform,
94
- target_app_version: data.targetAppVersion,
95
- storage_uri: data.storageUri,
96
- fingerprint_hash: data.fingerprintHash,
97
- metadata: data.metadata ?? {}
98
- };
99
- const channelRef = context.db.collection("channels").doc(data.channel);
100
- transaction.set(channelRef, { name: data.channel }, { merge: true });
101
- } else if (operation === "delete") {
102
- if (!bundlesMap[data.id]) throw new Error(`Bundle with id ${data.id} not found`);
103
- delete bundlesMap[data.id];
104
- deletedBundleIds.add(data.id);
105
- isTargetAppVersionChanged = true;
106
- }
21
+ const firebaseDatabase = createDatabasePlugin({
22
+ name: "firebaseDatabase",
23
+ factory: (config) => {
24
+ let bundles = [];
25
+ let app;
26
+ try {
27
+ app = admin.app();
28
+ } catch {
29
+ app = admin.initializeApp(config);
30
+ }
31
+ const db = admin.firestore(app);
32
+ const bundlesCollection = db.collection("bundles");
33
+ return {
34
+ async getBundleById(bundleId) {
35
+ const found = bundles.find((b) => b.id === bundleId);
36
+ if (found) return found;
37
+ const bundleSnap = await bundlesCollection.doc(bundleId).get();
38
+ if (!bundleSnap.exists) return null;
39
+ return convertToBundle(bundleSnap.data());
40
+ },
41
+ async getBundles(options) {
42
+ const { where, limit, offset } = options;
43
+ let query = bundlesCollection;
44
+ if (where?.channel) query = query.where("channel", "==", where.channel);
45
+ if (where?.platform) query = query.where("platform", "==", where.platform);
46
+ query = query.orderBy("id", "desc");
47
+ const total = (await query.get()).size;
48
+ if (offset > 0) query = query.offset(offset);
49
+ if (limit) query = query.limit(limit);
50
+ bundles = (await query.get()).docs.map((doc) => convertToBundle(doc.data()));
51
+ return {
52
+ data: bundles,
53
+ pagination: calculatePagination(total, {
54
+ limit,
55
+ offset
56
+ })
57
+ };
58
+ },
59
+ async getChannels() {
60
+ const querySnapshot = await db.collection("channels").get();
61
+ if (querySnapshot.empty) return [];
62
+ const channels = /* @__PURE__ */ new Set();
63
+ for (const doc of querySnapshot.docs) {
64
+ const data = doc.data();
65
+ if (data.name) channels.add(data.name);
107
66
  }
108
- const requiredTargetVersionKeys = /* @__PURE__ */ new Set();
109
- const requiredChannels = /* @__PURE__ */ new Set();
110
- for (const bundle of Object.values(bundlesMap)) {
111
- if (bundle.target_app_version) {
112
- const key = `${bundle.platform}_${bundle.channel}_${bundle.target_app_version}`;
113
- requiredTargetVersionKeys.add(key);
67
+ return Array.from(channels);
68
+ },
69
+ async commitBundle({ changedSets }) {
70
+ if (changedSets.length === 0) return;
71
+ let isTargetAppVersionChanged = false;
72
+ const deletedBundleIds = /* @__PURE__ */ new Set();
73
+ await db.runTransaction(async (transaction) => {
74
+ const bundlesSnapshot = await transaction.get(bundlesCollection);
75
+ const targetVersionsSnapshot = await transaction.get(db.collection("target_app_versions"));
76
+ const channelsSnapshot = await transaction.get(db.collection("channels"));
77
+ const bundlesMap = {};
78
+ for (const doc of bundlesSnapshot.docs) bundlesMap[doc.id] = doc.data();
79
+ for (const { operation, data } of changedSets) {
80
+ if (data.targetAppVersion) isTargetAppVersionChanged = true;
81
+ if (operation === "insert" || operation === "update") {
82
+ bundlesMap[data.id] = {
83
+ id: data.id,
84
+ channel: data.channel,
85
+ enabled: data.enabled,
86
+ should_force_update: data.shouldForceUpdate,
87
+ file_hash: data.fileHash,
88
+ git_commit_hash: data.gitCommitHash || null,
89
+ message: data.message || null,
90
+ platform: data.platform,
91
+ target_app_version: data.targetAppVersion,
92
+ storage_uri: data.storageUri,
93
+ fingerprint_hash: data.fingerprintHash,
94
+ metadata: data.metadata ?? {}
95
+ };
96
+ const channelRef = db.collection("channels").doc(data.channel);
97
+ transaction.set(channelRef, { name: data.channel }, { merge: true });
98
+ } else if (operation === "delete") {
99
+ if (!bundlesMap[data.id]) throw new Error(`Bundle with id ${data.id} not found`);
100
+ delete bundlesMap[data.id];
101
+ deletedBundleIds.add(data.id);
102
+ isTargetAppVersionChanged = true;
103
+ }
114
104
  }
115
- requiredChannels.add(bundle.channel);
116
- }
117
- for (const { operation, data } of changedSets) {
118
- const bundleRef = context.bundlesCollection.doc(data.id);
119
- if (operation === "insert" || operation === "update") {
120
- transaction.set(bundleRef, {
121
- id: data.id,
122
- channel: data.channel,
123
- enabled: data.enabled,
124
- should_force_update: data.shouldForceUpdate,
125
- file_hash: data.fileHash,
126
- git_commit_hash: data.gitCommitHash || null,
127
- message: data.message || null,
128
- platform: data.platform,
129
- target_app_version: data.targetAppVersion || null,
130
- storage_uri: data.storageUri,
131
- fingerprint_hash: data.fingerprintHash,
132
- metadata: data.metadata ?? {}
133
- }, { merge: true });
134
- if (data.targetAppVersion) {
135
- const versionDocId = `${data.platform}_${data.channel}_${data.targetAppVersion}`;
136
- const targetAppVersionsRef = context.db.collection("target_app_versions").doc(versionDocId);
137
- transaction.set(targetAppVersionsRef, {
105
+ const requiredTargetVersionKeys = /* @__PURE__ */ new Set();
106
+ const requiredChannels = /* @__PURE__ */ new Set();
107
+ for (const bundle of Object.values(bundlesMap)) {
108
+ if (bundle.target_app_version) {
109
+ const key = `${bundle.platform}_${bundle.channel}_${bundle.target_app_version}`;
110
+ requiredTargetVersionKeys.add(key);
111
+ }
112
+ requiredChannels.add(bundle.channel);
113
+ }
114
+ for (const { operation, data } of changedSets) {
115
+ const bundleRef = bundlesCollection.doc(data.id);
116
+ if (operation === "insert" || operation === "update") {
117
+ transaction.set(bundleRef, {
118
+ id: data.id,
138
119
  channel: data.channel,
120
+ enabled: data.enabled,
121
+ should_force_update: data.shouldForceUpdate,
122
+ file_hash: data.fileHash,
123
+ git_commit_hash: data.gitCommitHash || null,
124
+ message: data.message || null,
139
125
  platform: data.platform,
140
- target_app_version: data.targetAppVersion
126
+ target_app_version: data.targetAppVersion || null,
127
+ storage_uri: data.storageUri,
128
+ fingerprint_hash: data.fingerprintHash,
129
+ metadata: data.metadata ?? {}
141
130
  }, { merge: true });
142
- }
143
- } else if (operation === "delete") transaction.delete(bundleRef);
144
- }
145
- if (isTargetAppVersionChanged) {
146
- for (const targetDoc of targetVersionsSnapshot.docs) if (!requiredTargetVersionKeys.has(targetDoc.id)) transaction.delete(targetDoc.ref);
147
- }
148
- for (const channelDoc of channelsSnapshot.docs) if (!requiredChannels.has(channelDoc.id)) transaction.delete(channelDoc.ref);
149
- });
150
- for (const bundleId of deletedBundleIds) bundles = bundles.filter((b) => b.id !== bundleId);
151
- hooks?.onDatabaseUpdated?.();
152
- }
153
- }, hooks);
154
- };
131
+ if (data.targetAppVersion) {
132
+ const versionDocId = `${data.platform}_${data.channel}_${data.targetAppVersion}`;
133
+ const targetAppVersionsRef = db.collection("target_app_versions").doc(versionDocId);
134
+ transaction.set(targetAppVersionsRef, {
135
+ channel: data.channel,
136
+ platform: data.platform,
137
+ target_app_version: data.targetAppVersion
138
+ }, { merge: true });
139
+ }
140
+ } else if (operation === "delete") transaction.delete(bundleRef);
141
+ }
142
+ if (isTargetAppVersionChanged) {
143
+ for (const targetDoc of targetVersionsSnapshot.docs) if (!requiredTargetVersionKeys.has(targetDoc.id)) transaction.delete(targetDoc.ref);
144
+ }
145
+ for (const channelDoc of channelsSnapshot.docs) if (!requiredChannels.has(channelDoc.id)) transaction.delete(channelDoc.ref);
146
+ });
147
+ for (const bundleId of deletedBundleIds) bundles = bundles.filter((b) => b.id !== bundleId);
148
+ }
149
+ };
150
+ }
151
+ });
155
152
 
156
153
  //#endregion
157
154
  //#region src/firebaseStorage.ts
158
- const firebaseStorage = (config, hooks) => (_) => {
159
- let app;
160
- try {
161
- app = admin.app();
162
- } catch {
163
- app = admin.initializeApp(config);
164
- }
165
- const bucket = app.storage().bucket(config.storageBucket);
166
- const getStorageKey = createStorageKeyBuilder(config.basePath);
167
- return {
168
- name: "firebaseStorage",
169
- supportedProtocol: "gs",
170
- async delete(storageUri) {
171
- const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
172
- if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
173
- try {
174
- const [files] = await bucket.getFiles({ prefix: key });
175
- await Promise.all(files.map((file) => file.delete()));
176
- } catch (e) {
177
- console.error("Error listing or deleting files:", e);
178
- throw new Error("Bundle Not Found");
179
- }
180
- },
181
- async upload(key, filePath) {
182
- try {
183
- const fileContent = await fs.readFile(filePath);
184
- const contentType = getContentType(filePath);
185
- const storageKey = getStorageKey(key, path.basename(filePath));
186
- await bucket.file(storageKey).save(fileContent, { metadata: { contentType } });
187
- hooks?.onStorageUploaded?.();
188
- return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
189
- } catch (error) {
190
- console.error("Error uploading bundle:", error);
191
- if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
192
- throw error;
193
- }
194
- },
195
- async getDownloadUrl(storageUri) {
196
- const u = new URL(storageUri);
197
- if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
198
- const key = u.pathname.slice(1);
199
- if (!key) throw new Error("Invalid Firebase storage URI: missing key");
200
- const [signedUrl] = await bucket.file(key).getSignedUrl({
201
- action: "read",
202
- expires: Date.now() + 3600 * 1e3
203
- });
204
- if (!signedUrl) throw new Error("Failed to generate download URL");
205
- return { fileUrl: signedUrl };
155
+ const firebaseStorage = createStoragePlugin({
156
+ name: "firebaseStorage",
157
+ supportedProtocol: "gs",
158
+ factory: (config) => {
159
+ let app;
160
+ try {
161
+ app = admin.app();
162
+ } catch {
163
+ app = admin.initializeApp(config);
206
164
  }
207
- };
208
- };
165
+ const bucket = app.storage().bucket(config.storageBucket);
166
+ const getStorageKey = createStorageKeyBuilder(config.basePath);
167
+ return {
168
+ async delete(storageUri) {
169
+ const { bucket: bucketName, key } = parseStorageUri(storageUri, "gs");
170
+ if (bucketName !== config.storageBucket) throw new Error(`Bucket name mismatch: expected "${config.storageBucket}", but found "${bucketName}".`);
171
+ try {
172
+ const [files] = await bucket.getFiles({ prefix: key });
173
+ await Promise.all(files.map((file) => file.delete()));
174
+ } catch (e) {
175
+ console.error("Error listing or deleting files:", e);
176
+ throw new Error("Bundle Not Found");
177
+ }
178
+ },
179
+ async upload(key, filePath) {
180
+ try {
181
+ const fileContent = await fs.readFile(filePath);
182
+ const contentType = getContentType(filePath);
183
+ const storageKey = getStorageKey(key, path.basename(filePath));
184
+ await bucket.file(storageKey).save(fileContent, { metadata: { contentType } });
185
+ return { storageUri: `gs://${config.storageBucket}/${storageKey}` };
186
+ } catch (error) {
187
+ console.error("Error uploading bundle:", error);
188
+ if (error instanceof Error) throw new Error(`Failed to upload bundle: ${error.message}`);
189
+ throw error;
190
+ }
191
+ },
192
+ async getDownloadUrl(storageUri) {
193
+ const u = new URL(storageUri);
194
+ if (u.protocol.replace(":", "") !== "gs") throw new Error("Invalid Firebase storage URI protocol");
195
+ const key = u.pathname.slice(1);
196
+ if (!key) throw new Error("Invalid Firebase storage URI: missing key");
197
+ const [signedUrl] = await bucket.file(key).getSignedUrl({
198
+ action: "read",
199
+ expires: Date.now() + 3600 * 1e3
200
+ });
201
+ if (!signedUrl) throw new Error("Failed to generate download URL");
202
+ return { fileUrl: signedUrl };
203
+ }
204
+ };
205
+ }
206
+ });
209
207
 
210
208
  //#endregion
211
209
  export { firebaseDatabase, firebaseStorage };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@hot-updater/firebase",
3
3
  "type": "module",
4
- "version": "0.21.10",
4
+ "version": "0.21.12",
5
5
  "description": "React Native OTA solution for self-hosted",
6
6
  "main": "dist/index.cjs",
7
7
  "types": "dist/index.d.ts",
@@ -35,9 +35,9 @@
35
35
  ],
36
36
  "dependencies": {
37
37
  "firebase": "^11.3.1",
38
- "@hot-updater/cli-tools": "0.21.10",
39
- "@hot-updater/plugin-core": "0.21.10",
40
- "@hot-updater/core": "0.21.10"
38
+ "@hot-updater/cli-tools": "0.21.12",
39
+ "@hot-updater/core": "0.21.12",
40
+ "@hot-updater/plugin-core": "0.21.12"
41
41
  },
42
42
  "publishConfig": {
43
43
  "access": "public"
@@ -53,8 +53,8 @@
53
53
  "fkill": "^9.0.0",
54
54
  "hono": "^4.6.3",
55
55
  "mime": "^4.0.4",
56
- "@hot-updater/js": "0.21.10",
57
- "@hot-updater/test-utils": "0.21.10"
56
+ "@hot-updater/js": "0.21.12",
57
+ "@hot-updater/test-utils": "0.21.12"
58
58
  },
59
59
  "peerDependencies": {
60
60
  "firebase-admin": "*"