@edgedev/firebase 2.1.48 → 2.1.49

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/edgeFirebase.ts CHANGED
@@ -56,12 +56,11 @@ import {
56
56
  } from "firebase/auth";
57
57
 
58
58
 
59
- import { getStorage, ref, uploadBytesResumable, getDownloadURL, connectStorageEmulator, listAll, deleteObject} from "firebase/storage";
59
+ import { getStorage, ref, uploadBytesResumable, getDownloadURL, connectStorageEmulator, listAll, deleteObject, getMetadata} from "firebase/storage";
60
60
 
61
61
  import { getFunctions, httpsCallable, connectFunctionsEmulator } from "firebase/functions";
62
62
 
63
63
  import { getAnalytics, logEvent } from "firebase/analytics";
64
- import { get } from "http";
65
64
 
66
65
  interface FirestoreQuery {
67
66
  field: string;
@@ -206,7 +205,6 @@ interface Meta {
206
205
  [key: string]: unknown;
207
206
  }
208
207
 
209
-
210
208
  export const EdgeFirebase = class {
211
209
  constructor(
212
210
  firebaseConfig: firebaseConfig = {
@@ -1235,6 +1233,7 @@ export const EdgeFirebase = class {
1235
1233
  registrationCode: "",
1236
1234
  registrationMeta: {},
1237
1235
  ruleHelpers: {},
1236
+ currentUploadProgress: 0,
1238
1237
  });
1239
1238
 
1240
1239
  public getDocData = async (
@@ -2095,8 +2094,13 @@ export const EdgeFirebase = class {
2095
2094
 
2096
2095
 
2097
2096
  // File functions
2098
- public uploadFile = async (filePath: string, file: Blob, isPublic: boolean): Promise<actionResponse> => {
2099
-
2097
+ public uploadFile = async (orgId: string, file: Blob, filePath: string = "", isPublic: boolean = false, toR2: boolean = false): Promise<actionResponse> => {
2098
+ // Finish toCF function hook.
2099
+ this.state.currentUploadProgress = 0;
2100
+ let collectionPath = "organizations/" + orgId;
2101
+ if (filePath) {
2102
+ collectionPath = "organizations/" + orgId + "/" + filePath;
2103
+ }
2100
2104
  // Validate if file is provided
2101
2105
  if (!file) {
2102
2106
  return this.sendResponse({
@@ -2107,10 +2111,11 @@ export const EdgeFirebase = class {
2107
2111
  }
2108
2112
 
2109
2113
  // Check if the user has write permission to the filePath
2110
- let hasWritePermission = await this.permissionCheck("write", filePath);
2114
+ let hasWritePermission = await this.permissionCheck("write", collectionPath);
2111
2115
  if (isPublic) {
2112
2116
  hasWritePermission = true;
2113
2117
  filePath = "public";
2118
+ collectionPath = "public";
2114
2119
  }
2115
2120
  if (!hasWritePermission) {
2116
2121
  return this.sendResponse({
@@ -2121,17 +2126,44 @@ export const EdgeFirebase = class {
2121
2126
  }
2122
2127
 
2123
2128
  try {
2124
- let randomId = '';
2129
+ const fileDoc = {
2130
+ orgId,
2131
+ uploadCompleted: false,
2132
+ uploadCompletedToR2: false,
2133
+ fileName: file.name,
2134
+ name: file.name,
2135
+ fileSize: file.size,
2136
+ fileType: file.type,
2137
+ directory: filePath,
2138
+ filePath: '',
2139
+ r2filePath: '',
2140
+ r2URL: '',
2141
+ isPublic: isPublic,
2142
+ toR2: toR2,
2143
+ };
2144
+
2145
+ const result: any = await this.runFunction("edgeFirebase-addUpdateFileDoc", fileDoc);
2146
+ const fileDocId = (result.data as { docId: string }).docId;
2147
+ let tempFilePath = `${collectionPath.replaceAll('/', '-')}` + '/' + fileDocId + '-' + file.name;
2125
2148
  if (isPublic) {
2126
- randomId = Math.random().toString(36).substring(2, 6) + '-';
2149
+ tempFilePath = `${collectionPath.replaceAll('/', '-')}` + '/' + orgId + '-' + fileDocId + '-' + file.name;
2127
2150
  }
2128
- const tempFilePath = `${filePath.replaceAll('/', '-')}` + '/' + randomId + file.name;
2151
+ await this.runFunction("edgeFirebase-addUpdateFileDoc", {orgId, docId: fileDocId, filePath: tempFilePath });
2129
2152
  const storage = getStorage();
2130
2153
  const fileRef = ref(storage, tempFilePath);
2131
2154
 
2132
2155
  const metadata = {
2133
- contentType: file.type,
2134
2156
  cacheControl: isPublic ? 'public, max-age=31536000' : undefined,
2157
+ customMetadata: {
2158
+ orgId,
2159
+ uid: this.user.uid,
2160
+ contentType: file.type,
2161
+ fileName: file.name,
2162
+ fileDocId: fileDocId,
2163
+ filePath: tempFilePath,
2164
+ toR2: toR2 ? 'true' : '',
2165
+ isPublic: isPublic ? 'true' : ''
2166
+ }
2135
2167
  };
2136
2168
 
2137
2169
  // Resumable upload
@@ -2143,14 +2175,14 @@ export const EdgeFirebase = class {
2143
2175
  (snapshot) => {
2144
2176
  // Progress, pause, and resume events
2145
2177
  const progress = (snapshot.bytesTransferred / snapshot.totalBytes) * 100;
2146
- console.log('Upload is ' + progress + '% done');
2178
+ this.state.currentUploadProgress = progress;
2147
2179
  },
2148
2180
  (error) => {
2149
2181
  // Handle unsuccessful uploads
2150
2182
  reject(this.sendResponse({
2151
2183
  success: false,
2152
2184
  message: "An error occurred during file upload.",
2153
- meta: { error: error.message }
2185
+ meta: { error: error.message, fileDocId}
2154
2186
  }));
2155
2187
  },
2156
2188
  () => {
@@ -2158,13 +2190,16 @@ export const EdgeFirebase = class {
2158
2190
  resolve(this.sendResponse({
2159
2191
  success: true,
2160
2192
  message: "File uploaded successfully.",
2161
- meta: { file: tempFilePath }
2193
+ meta: { file: tempFilePath, fileDocId }
2162
2194
  }));
2163
2195
  }
2164
2196
  );
2165
2197
  });
2166
-
2167
- return await uploadPromise;
2198
+
2199
+
2200
+ const uploadStatus = await uploadPromise;
2201
+ await this.runFunction("edgeFirebase-addUpdateFileDoc", {orgId, docId: fileDocId, uploadStatus, uploadCompleted: true });
2202
+ return uploadStatus;
2168
2203
 
2169
2204
  } catch (error) {
2170
2205
  return this.sendResponse({
@@ -2191,11 +2226,11 @@ export const EdgeFirebase = class {
2191
2226
  try {
2192
2227
  const fileRef = ref(this.storage, filePath);
2193
2228
  await deleteObject(fileRef);
2194
- return this.sendResponse({
2195
- success: true,
2196
- message: "File deleted successfully.",
2197
- meta: {}
2198
- });
2229
+ this.sendResponse({
2230
+ success: true,
2231
+ message: "File deleted successfully.",
2232
+ meta: {}
2233
+ });
2199
2234
  } catch (error) {
2200
2235
  return this.sendResponse({
2201
2236
  success: false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@edgedev/firebase",
3
- "version": "2.1.48",
3
+ "version": "2.1.49",
4
4
  "description": "Vue 3 / Nuxt 3 Plugin or Nuxt 3 plugin for firebase authentication and firestore.",
5
5
  "main": "index.ts",
6
6
  "scripts": {
package/src/.env.dev CHANGED
@@ -4,4 +4,13 @@ STRIPE_RETURN_URL=
4
4
  OPENAI_API_KEY=
5
5
  TWILIO_SID=
6
6
  TWILIO_AUTH_TOKEN=
7
- TWILIO_SYSTEM_NUMBER=
7
+ TWILIO_SYSTEM_NUMBER=
8
+ FIREBASE_STORAGE_BUCKET=
9
+ FIREBASE_STORAGE_BUCKET_REGION=
10
+ FIREBASE_STORAGE_BUCKET=
11
+ FIREBASE_STORAGE_BUCKET_REGION=
12
+ CLOUDFLARE_R2_PUBLIC_URL=
13
+ CLOUDFLARE_R2_ENDPOINT=
14
+ CLOUDFLARE_R2_ACCESS_KEY_ID=
15
+ CLOUDFLARE_R2_SECRET_ACCESS_KEY=
16
+ CLOUDFLARE_R2_BUCKET_NAME=
package/src/.env.prod CHANGED
@@ -4,4 +4,13 @@ STRIPE_RETURN_URL=
4
4
  OPENAI_API_KEY=
5
5
  TWILIO_SID=
6
6
  TWILIO_AUTH_TOKEN=
7
- TWILIO_SYSTEM_NUMBER=
7
+ TWILIO_SYSTEM_NUMBER=
8
+ FIREBASE_STORAGE_BUCKET=
9
+ FIREBASE_STORAGE_BUCKET_REGION=
10
+ FIREBASE_STORAGE_BUCKET=
11
+ FIREBASE_STORAGE_BUCKET_REGION=
12
+ CLOUDFLARE_R2_PUBLIC_URL=
13
+ CLOUDFLARE_R2_ENDPOINT=
14
+ CLOUDFLARE_R2_ACCESS_KEY_ID=
15
+ CLOUDFLARE_R2_SECRET_ACCESS_KEY=
16
+ CLOUDFLARE_R2_BUCKET_NAME=
package/src/config.js CHANGED
@@ -11,6 +11,7 @@ const { onMessagePublished } = require('firebase-functions/v2/pubsub')
11
11
  const { onCall, HttpsError, onRequest } = require('firebase-functions/v2/https')
12
12
  const { onSchedule } = require('firebase-functions/v2/scheduler')
13
13
  const { Storage } = require('@google-cloud/storage')
14
+ const { onObjectFinalized, onObjectDeleted } = require('firebase-functions/v2/storage')
14
15
  const {
15
16
  onDocumentWritten,
16
17
  onDocumentCreated,
@@ -81,4 +82,6 @@ module.exports = {
81
82
  db,
82
83
  Storage,
83
84
  permissionCheck,
85
+ onObjectFinalized,
86
+ onObjectDeleted,
84
87
  }
@@ -1,6 +1,5 @@
1
- /* eslint-disable @typescript-eslint/no-var-requires */
2
- /* eslint-disable no-undef */
3
- const { onCall, HttpsError, logger, getFirestore, functions, admin, twilio, db, onSchedule, onDocumentUpdated, pubsub, Storage, permissionCheck } = require('./config.js')
1
+ const AWS = require('aws-sdk')
2
+ const { onCall, HttpsError, logger, getFirestore, functions, admin, twilio, db, onSchedule, onDocumentUpdated, pubsub, Storage, permissionCheck, onObjectFinalized, onObjectDeleted, onDocumentDeleted } = require('./config.js')
4
3
 
5
4
  const authToken = process.env.TWILIO_AUTH_TOKEN
6
5
  const accountSid = process.env.TWILIO_SID
@@ -13,6 +12,153 @@ function formatPhoneNumber(phone) {
13
12
  return `+1${numericPhone}`
14
13
  }
15
14
 
15
+ exports.uploadDocumentDeleted = onDocumentDeleted(
16
+ { document: 'organizations/{orgId}/files/{docId}', timeoutSeconds: 180 },
17
+ async (event) => {
18
+ const fileData = event.data.data()
19
+ const filePath = fileData.filePath
20
+ // Check if the file exists in the bucket
21
+ const bucket = admin.storage().bucket()
22
+ const [exists] = await bucket.file(filePath).exists()
23
+ if (exists) {
24
+ // Delete the file if it exists
25
+ await bucket.file(filePath).delete()
26
+ console.log(`File deleted: ${filePath}`)
27
+ }
28
+ else {
29
+ console.log(`File not found: ${filePath}`)
30
+ }
31
+ },
32
+ )
33
+
34
+ exports.addUpdateFileDoc = onCall(async (request) => {
35
+ const data = request.data
36
+ const auth = request.auth
37
+ let docId = data?.docId
38
+ if (data.uid === auth.uid) {
39
+ console.log(data)
40
+ const orgId = data.orgId
41
+ if (docId) {
42
+ const docRef = db.collection(`organizations/${orgId}/files`).doc(docId)
43
+ await docRef.set(data, { merge: true })
44
+ }
45
+ else {
46
+ const docRef = db.collection(`organizations/${orgId}/files`).doc()
47
+ await docRef.set(data)
48
+ docId = docRef.id
49
+ }
50
+ }
51
+ console.log(docId)
52
+ return { docId }
53
+ })
54
+
55
+ const deleteR2File = async (filePath) => {
56
+ const r2 = new AWS.S3({
57
+ endpoint: process.env.CLOUDFLARE_R2_ENDPOINT, // e.g., "https://<account-id>.r2.cloudflarestorage.com"
58
+ accessKeyId: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID,
59
+ secretAccessKey: process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY,
60
+ region: 'auto', // Cloudflare R2 uses "auto" for region
61
+ })
62
+ const params = {
63
+ Bucket: 'files',
64
+ Key: filePath,
65
+ }
66
+ try {
67
+ await r2.deleteObject(params).promise()
68
+ console.log(`File deleted from Cloudflare R2: ${filePath}`)
69
+ }
70
+ catch (error) {
71
+ console.error('Error deleting file from Cloudflare R2:', error)
72
+ }
73
+ }
74
+
75
+ exports.fileDeleted = onObjectDeleted(async (event) => {
76
+ const docId = event.data.metadata?.fileDocId
77
+ const toR2 = event.data.metadata?.toR2
78
+ if (toR2) {
79
+ await deleteR2File(event.data.metadata?.r2FilePath)
80
+ }
81
+ if (docId) {
82
+ const orgId = event.data.metadata?.orgId
83
+ const docRef = db.collection(`organizations/${orgId}/files`).doc(docId)
84
+ const docSnapshot = await docRef.get()
85
+ if (docSnapshot.exists) {
86
+ console.log('Deleting file document:', docId)
87
+ await docRef.delete()
88
+ }
89
+ else {
90
+ console.log('File document not found:', docId)
91
+ }
92
+ }
93
+ })
94
+
95
+ exports.toR2 = onObjectFinalized(
96
+ {
97
+ bucket: process.env.FIREBASE_STORAGE_BUCKET,
98
+ region: process.env.FIREBASE_STORAGE_BUCKET_REGION,
99
+ memory: '2GiB',
100
+ cpu: 2,
101
+ timeoutSeconds: 540,
102
+ }, async (event) => {
103
+ const toR2 = event.data.metadata?.toR2
104
+ if (toR2) {
105
+ const r2 = new AWS.S3({
106
+ endpoint: process.env.CLOUDFLARE_R2_ENDPOINT, // e.g., "https://<account-id>.r2.cloudflarestorage.com"
107
+ accessKeyId: process.env.CLOUDFLARE_R2_ACCESS_KEY_ID,
108
+ secretAccessKey: process.env.CLOUDFLARE_R2_SECRET_ACCESS_KEY,
109
+ region: 'auto', // Cloudflare R2 uses "auto" for region
110
+ })
111
+ const fileBucket = event.data.bucket // Storage bucket containing the file.
112
+ const filePath = event.data.name // File path in the bucket.
113
+ const r2FilePath = `${process.env.FIREBASE_STORAGE_BUCKET}/${event.data.metadata?.filePath}`
114
+ const r2URL = `${process.env.CLOUDFLARE_R2_PUBLIC_URL}/${r2FilePath}`
115
+ const fileName = event.data.metadata?.fileName // File name.
116
+ const contentType = event.data.contentType // File content type.
117
+
118
+ // Download file into memory from bucket.
119
+ const bucket = admin.storage().bucket(fileBucket)
120
+ const downloadResponse = await bucket.file(filePath).download()
121
+ const file = downloadResponse[0]
122
+
123
+ // Upload the file to Cloudflare R2.
124
+ const params = {
125
+ Bucket: 'files',
126
+ Key: r2FilePath,
127
+ Body: file,
128
+ ContentType: contentType,
129
+ }
130
+ try {
131
+ await r2.upload(params).promise()
132
+ const fileDocId = event.data.metadata?.fileDocId
133
+ const orgId = event.data.metadata?.orgId
134
+ const docRef = db.collection(`organizations/${orgId}/files`).doc(fileDocId)
135
+ await docRef.set({ r2FilePath, r2URL, uploadCompletedToR2: true }, { merge: true })
136
+ const fileRef = bucket.file(filePath)
137
+
138
+ const blankBuffer = Buffer.from('')
139
+ await fileRef.save(blankBuffer, {
140
+ contentType: 'application/octet-stream',
141
+ })
142
+
143
+ // Step 2: Update metadata with additional fields
144
+ const updatedMetadata = {
145
+ metadata: {
146
+ ...event.data.metadata,
147
+ r2FilePath,
148
+ r2URL,
149
+ uploadCompletedToR2: 'true', // Add custom metadata after file save
150
+ },
151
+ }
152
+
153
+ await fileRef.setMetadata(updatedMetadata)
154
+ console.log(`File uploaded to Cloudflare R2: ${fileName}`)
155
+ }
156
+ catch (error) {
157
+ console.error('Error uploading file to Cloudflare R2:', error)
158
+ }
159
+ }
160
+ })
161
+
16
162
  exports.topicQueue = onSchedule({ schedule: 'every 1 minutes', timeoutSeconds: 180 }, async (event) => {
17
163
  const queuedTopicsRef = db.collection('topic-queue')
18
164
  const snapshot = await queuedTopicsRef.get()
package/src/package.json CHANGED
@@ -15,6 +15,7 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@google-cloud/pubsub": "^4.9.0",
18
+ "aws-sdk": "^2.1692.0",
18
19
  "crypto": "^1.0.1",
19
20
  "dotenv": "^16.3.1",
20
21
  "exceljs": "^4.4.0",
@@ -28,6 +29,6 @@
28
29
  "twilio": "^4.18.0"
29
30
  },
30
31
  "devDependencies": {
31
- "firebase-functions-test": "^0.2.0"
32
+ "firebase-functions-test": "^3.4.0"
32
33
  }
33
34
  }
@@ -73,4 +73,26 @@ if [ ! -f "$project_root/functions/package.json" ]; then
73
73
  cd "$project_root/functions"
74
74
  npm install --no-audit --silent
75
75
  cd "$project_root"
76
- fi
76
+ fi
77
+
78
+ # Upgrade specific npm packages in the functions directory
79
+ cd "$project_root/functions"
80
+
81
+ # List of packages to upgrade
82
+ npm install --save \
83
+ "@google-cloud/pubsub@^4.9.0" \
84
+ "aws-sdk@^2.1692.0" \
85
+ "crypto@^1.0.1" \
86
+ "dotenv@^16.3.1" \
87
+ "exceljs@^4.4.0" \
88
+ "firebase-admin@^13.0.2" \
89
+ "firebase-functions@^6.2.0" \
90
+ "form-data@^4.0.0" \
91
+ "formidable-serverless@^1.1.1" \
92
+ "moment-timezone@^0.5.43" \
93
+ "openai@^4.11.1" \
94
+ "stripe@^13.8.0" \
95
+ "twilio@^4.18.0"
96
+
97
+ # Return to the project root
98
+ cd "$project_root"