@edgedev/firebase 2.0.30 → 2.0.33
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 +71 -53
- package/package.json +1 -1
- package/src/.env.dev +2 -1
- package/src/.env.development +2 -1
- package/src/.env.prod +2 -1
- package/src/.env.production +2 -1
- package/src/config.js +4 -0
- package/src/edgeFirebase.js +117 -0
- package/src/postinstall.sh +23 -4
- package/src/storage.rules +19 -0
package/edgeFirebase.ts
CHANGED
|
@@ -48,19 +48,14 @@ import {
|
|
|
48
48
|
OAuthProvider,
|
|
49
49
|
browserPopupRedirectResolver,
|
|
50
50
|
signInWithPopup,
|
|
51
|
-
signInWithPhoneNumber,
|
|
52
51
|
sendSignInLinkToEmail,
|
|
53
|
-
updateEmail,
|
|
54
52
|
verifyBeforeUpdateEmail,
|
|
55
|
-
RecaptchaVerifier,
|
|
56
|
-
ConfirmationResult,
|
|
57
|
-
PhoneAuthProvider,
|
|
58
53
|
signInWithCustomToken,
|
|
59
54
|
updateProfile,
|
|
60
55
|
} from "firebase/auth";
|
|
61
56
|
|
|
62
|
-
import { getStorage, ref as storageRef, uploadBytes, listAll, getDownloadURL, deleteObject } from "firebase/storage";
|
|
63
57
|
|
|
58
|
+
import { getStorage, ref as storageRef, uploadBytes, getDownloadURL, connectStorageEmulator} from "firebase/storage";
|
|
64
59
|
|
|
65
60
|
import { getFunctions, httpsCallable, connectFunctionsEmulator } from "firebase/functions";
|
|
66
61
|
|
|
@@ -186,6 +181,7 @@ interface firebaseConfig {
|
|
|
186
181
|
measurementId?: string;
|
|
187
182
|
emulatorFirestore?: string;
|
|
188
183
|
emulatorFunctions?: string;
|
|
184
|
+
emulatorStorage?: string;
|
|
189
185
|
}
|
|
190
186
|
|
|
191
187
|
interface actionResponse {
|
|
@@ -211,7 +207,8 @@ export const EdgeFirebase = class {
|
|
|
211
207
|
measurementId: "",
|
|
212
208
|
emulatorAuth: "",
|
|
213
209
|
emulatorFirestore: "",
|
|
214
|
-
emulatorFunctions: ""
|
|
210
|
+
emulatorFunctions: "",
|
|
211
|
+
emulatorStorage: "",
|
|
215
212
|
},
|
|
216
213
|
isPersistant: false,
|
|
217
214
|
enablePopupRedirect: false,
|
|
@@ -239,17 +236,17 @@ export const EdgeFirebase = class {
|
|
|
239
236
|
|
|
240
237
|
if (this.firebaseConfig.measurementId) {
|
|
241
238
|
this.anaytics = getAnalytics(this.app);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
if (this.firebaseConfig.storageBucket) {
|
|
239
|
+
}
|
|
240
|
+
if (this.firebaseConfig.storageBucket) {
|
|
245
241
|
this.storage = getStorage(this.app);
|
|
246
242
|
}
|
|
247
|
-
|
|
248
243
|
this.functions = getFunctions(this.app);
|
|
249
244
|
if (this.firebaseConfig.emulatorFunctions) {
|
|
250
245
|
connectFunctionsEmulator(this.functions, "127.0.0.1", this.firebaseConfig.emulatorFunctions)
|
|
251
246
|
}
|
|
252
|
-
this.
|
|
247
|
+
if (this.firebaseConfig.emulatorStorage) {
|
|
248
|
+
connectStorageEmulator(this.storage, "127.0.0.1", this.firebaseConfig.emulatorStorage)
|
|
249
|
+
}
|
|
253
250
|
}
|
|
254
251
|
|
|
255
252
|
private firebaseConfig = null;
|
|
@@ -258,7 +255,6 @@ export const EdgeFirebase = class {
|
|
|
258
255
|
public auth = null;
|
|
259
256
|
public db = null;
|
|
260
257
|
public storage = null;
|
|
261
|
-
|
|
262
258
|
private anaytics = null;
|
|
263
259
|
|
|
264
260
|
private functions = null;
|
|
@@ -2127,48 +2123,70 @@ export const EdgeFirebase = class {
|
|
|
2127
2123
|
this.unsubscribe[collectionPath] = null;
|
|
2128
2124
|
}
|
|
2129
2125
|
};
|
|
2130
|
-
// Firestore Storage
|
|
2131
|
-
// Upload a file
|
|
2132
|
-
async uploadFileToStorage(filePath, file) {
|
|
2133
|
-
const canWrite = await this.permissionCheck("write", filePath);
|
|
2134
|
-
if (!canWrite) {
|
|
2135
|
-
return { success: false, message: "Permission denied" };
|
|
2136
|
-
}
|
|
2137
|
-
const storagePath = storageRef(this.storage, filePath);
|
|
2138
|
-
await uploadBytes(storagePath, file);
|
|
2139
|
-
return { success: true, message: "File uploaded successfully" };
|
|
2140
|
-
}
|
|
2141
2126
|
|
|
2142
|
-
// List all files in a directory
|
|
2143
|
-
async listFilesInStorage(directoryPath) {
|
|
2144
|
-
const canRead = await this.permissionCheck("read", directoryPath);
|
|
2145
|
-
if (!canRead) {
|
|
2146
|
-
return { success: false, message: "Permission denied" };
|
|
2147
|
-
}
|
|
2148
|
-
const dirRef = storageRef(this.storage, directoryPath);
|
|
2149
|
-
const fileList = await listAll(dirRef);
|
|
2150
|
-
return { success: true, files: fileList.items.map(item => item.fullPath) };
|
|
2151
|
-
}
|
|
2152
2127
|
|
|
2153
|
-
//
|
|
2154
|
-
async
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2128
|
+
// File functions
|
|
2129
|
+
public uploadFileToStorage = async (filePath: string, file: Blob): Promise<actionResponse> => {
|
|
2130
|
+
try {
|
|
2131
|
+
// Validate if file is provided
|
|
2132
|
+
if (!file) {
|
|
2133
|
+
return this.sendResponse({
|
|
2134
|
+
success: false,
|
|
2135
|
+
message: "No file provided for upload.",
|
|
2136
|
+
meta: {}
|
|
2137
|
+
});
|
|
2138
|
+
}
|
|
2163
2139
|
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2140
|
+
// Check if the user has write permission to the filePath
|
|
2141
|
+
const hasWritePermission = await this.permissionCheck("write", filePath);
|
|
2142
|
+
if (!hasWritePermission) {
|
|
2143
|
+
return this.sendResponse({
|
|
2144
|
+
success: false,
|
|
2145
|
+
message: "You do not have permission to upload files to this path.",
|
|
2146
|
+
meta: {}
|
|
2147
|
+
});
|
|
2148
|
+
}
|
|
2149
|
+
|
|
2150
|
+
// Initialize Firebase Storage
|
|
2151
|
+
|
|
2152
|
+
// Define a temporary path for the file upload
|
|
2153
|
+
// You might want to include some unique identifier in the temporary path
|
|
2154
|
+
const tempFilePath = `temp/${this.user.uid}/${filePath.replaceAll('/', '-|-')}`;
|
|
2155
|
+
|
|
2156
|
+
// Create a reference to the temporary file location
|
|
2157
|
+
const fileRef = storageRef(this.storage, tempFilePath);
|
|
2158
|
+
|
|
2159
|
+
// Upload the file to the temporary location
|
|
2160
|
+
await uploadBytes(fileRef, file);
|
|
2161
|
+
|
|
2162
|
+
// Prepare data for the callable function
|
|
2163
|
+
const data = {
|
|
2164
|
+
uid: this.user.uid,
|
|
2165
|
+
filePath: filePath, // The final desired path for the file
|
|
2166
|
+
};
|
|
2167
|
+
|
|
2168
|
+
// Call the Firebase Function to handle the file move and any additional processing
|
|
2169
|
+
const callable = httpsCallable(this.functions, 'edgeFirebase-uploadFile');
|
|
2170
|
+
const functionResult = await callable(data);
|
|
2171
|
+
|
|
2172
|
+
const resultData = functionResult.data as { success: boolean; message: string; finalDownloadURL?: string };
|
|
2173
|
+
|
|
2174
|
+
if (!resultData.success) {
|
|
2175
|
+
throw new Error(resultData.message);
|
|
2176
|
+
}
|
|
2177
|
+
// Return success response
|
|
2178
|
+
return this.sendResponse({
|
|
2179
|
+
success: true,
|
|
2180
|
+
message: "File processed successfully.",
|
|
2181
|
+
meta: {}
|
|
2182
|
+
});
|
|
2183
|
+
} catch (error) {
|
|
2184
|
+
console.error(error);
|
|
2185
|
+
return this.sendResponse({
|
|
2186
|
+
success: false,
|
|
2187
|
+
message: "An error occurred during file processing.",
|
|
2188
|
+
meta: {}
|
|
2189
|
+
});
|
|
2169
2190
|
}
|
|
2170
|
-
|
|
2171
|
-
await deleteObject(fileRef);
|
|
2172
|
-
return { success: true, message: "File deleted successfully" };
|
|
2173
|
-
}
|
|
2191
|
+
};
|
|
2174
2192
|
};
|
package/package.json
CHANGED
package/src/.env.dev
CHANGED
package/src/.env.development
CHANGED
package/src/.env.prod
CHANGED
package/src/.env.production
CHANGED
package/src/config.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
+
/* eslint-disable no-undef */
|
|
1
3
|
const functions = require('firebase-functions')
|
|
2
4
|
const { PubSub } = require('@google-cloud/pubsub')
|
|
3
5
|
const admin = require('firebase-admin')
|
|
@@ -10,6 +12,7 @@ const { onMessagePublished } = require('firebase-functions/v2/pubsub')
|
|
|
10
12
|
|
|
11
13
|
const { onCall, HttpsError, onRequest } = require('firebase-functions/v2/https')
|
|
12
14
|
const { onSchedule } = require('firebase-functions/v2/scheduler')
|
|
15
|
+
const { Storage } = require('@google-cloud/storage')
|
|
13
16
|
const {
|
|
14
17
|
onDocumentWritten,
|
|
15
18
|
onDocumentCreated,
|
|
@@ -42,5 +45,6 @@ module.exports = {
|
|
|
42
45
|
admin,
|
|
43
46
|
twilio,
|
|
44
47
|
db,
|
|
48
|
+
Storage,
|
|
45
49
|
}
|
|
46
50
|
|
package/src/edgeFirebase.js
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-var-requires */
|
|
2
|
+
/* eslint-disable no-undef */
|
|
1
3
|
const { onCall, HttpsError, logger, getFirestore, functions, admin, twilio, db, onSchedule, onDocumentUpdated, pubsub } = require('./config.js')
|
|
2
4
|
|
|
3
5
|
const authToken = process.env.TWILIO_AUTH_TOKEN
|
|
4
6
|
const accountSid = process.env.TWILIO_SID
|
|
5
7
|
const systemNumber = process.env.TWILIO_SYSTEM_NUMBER
|
|
6
8
|
|
|
9
|
+
|
|
7
10
|
function formatPhoneNumber(phone) {
|
|
8
11
|
// Remove non-numeric characters from the phone number
|
|
9
12
|
const numericPhone = phone.replace(/\D/g, '')
|
|
@@ -11,6 +14,120 @@ function formatPhoneNumber(phone) {
|
|
|
11
14
|
return `+1${numericPhone}`
|
|
12
15
|
}
|
|
13
16
|
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
// File functions:
|
|
20
|
+
|
|
21
|
+
//TODO: NEED TO WRITE WRAPPERS FOR THESE IN THE edgeFirebase.js file... UPLOAD has to do alot more... upload needs to acutall upload the file to firestorage... get the path, then pass it to the uploadFile function...
|
|
22
|
+
//TODO: the uploadFile funntion needs to delete the file if they don't have permission to write to the path...
|
|
23
|
+
|
|
24
|
+
const bucketName = process.env.BUCKET_NAME
|
|
25
|
+
const storage = new Storage();
|
|
26
|
+
const bucket = storage.bucket(bucketName)
|
|
27
|
+
|
|
28
|
+
exports.uploadFile = onCall(async (request) => {
|
|
29
|
+
const auth = request.auth
|
|
30
|
+
if (data.uid !== auth.uid) {
|
|
31
|
+
throw new functions.https.HttpsError('permission-denied', 'You do not have permission to upload files for this user.');
|
|
32
|
+
}
|
|
33
|
+
const tempFilesPath = `temp/${auth.uid}/`;
|
|
34
|
+
const [files] = await bucket.getFiles({ prefix: tempFilesPath });
|
|
35
|
+
for (const file of files) {
|
|
36
|
+
const originalFilePath = file.name.replace(/-\|-/g, '/');
|
|
37
|
+
const hasWritePermission = await permissionCheck(auth.uid, "write", originalFilePath);
|
|
38
|
+
if (hasWritePermission) {
|
|
39
|
+
// Move file to the new path
|
|
40
|
+
await bucket.file(file.name).move(originalFilePath);
|
|
41
|
+
} else {
|
|
42
|
+
// Delete the file if no write permission
|
|
43
|
+
await bucket.file(file.name).delete();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
exports.downloadFile = onCall(async (request) => {
|
|
49
|
+
const data = request.data;
|
|
50
|
+
const auth = request.auth
|
|
51
|
+
if (data.uid !== auth.uid) {
|
|
52
|
+
throw new functions.https.HttpsError('permission-denied', 'You do not have permission to upload files for this user.');
|
|
53
|
+
}
|
|
54
|
+
// Permission check for downloading the specified file
|
|
55
|
+
const canRead = await permissionCheck(auth.uid, "read", data.filePath);
|
|
56
|
+
if (!canRead) {
|
|
57
|
+
throw new HttpsError('permission-denied', 'You do not have permission to download this file.');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const options = {
|
|
61
|
+
version: 'v4',
|
|
62
|
+
action: 'read',
|
|
63
|
+
expires: Date.now() + 5 * 60 * 1000, // 5 minutes
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
const [url] = await bucket.file(data.filePath).getSignedUrl(options);
|
|
68
|
+
return { success: true, url };
|
|
69
|
+
} catch (error) {
|
|
70
|
+
logger.error(error);
|
|
71
|
+
throw new HttpsError('internal', 'Unable to generate download URL.');
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
exports.listFiles = onCall(async (request) => {
|
|
76
|
+
// Validate user authentication
|
|
77
|
+
const data = request.data
|
|
78
|
+
const auth = request.auth
|
|
79
|
+
if (data.uid !== auth.uid) {
|
|
80
|
+
throw new functions.https.HttpsError('permission-denied', 'You do not have permission to upload files for this user.');
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Permission check for reading the specified directory
|
|
84
|
+
const canRead = await permissionCheck(auth.uid, "read", data.directoryPath);
|
|
85
|
+
if (!canRead) {
|
|
86
|
+
throw new HttpsError('permission-denied', 'You do not have permission to list files in this directory.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const [files] = await bucket.getFiles({ prefix: data.directoryPath });
|
|
91
|
+
const fileList = files.map(file => file.name);
|
|
92
|
+
return { success: true, fileList };
|
|
93
|
+
} catch (error) {
|
|
94
|
+
logger.error(error);
|
|
95
|
+
throw new HttpsError('internal', 'Unable to list files.');
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
exports.deleteFile = onCall(async (request) => {
|
|
101
|
+
// Validate user authentication
|
|
102
|
+
const data = request.data
|
|
103
|
+
const auth = request.auth
|
|
104
|
+
if (data.uid !== auth.uid) {
|
|
105
|
+
throw new functions.https.HttpsError('permission-denied', 'You do not have permission to upload files for this user.');
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Extract filePath from the request data
|
|
109
|
+
const filePath = data.filePath;
|
|
110
|
+
|
|
111
|
+
// Perform permission check for deleting the specified file
|
|
112
|
+
const canDelete = await permissionCheck(auth.uid, "delete", filePath);
|
|
113
|
+
if (!canDelete) {
|
|
114
|
+
throw new functions.https.HttpsError('permission-denied', 'You do not have permission to delete this file.');
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
// Specify your bucket name
|
|
119
|
+
await storage.bucket(bucketName).file(filePath).delete();
|
|
120
|
+
|
|
121
|
+
return { success: true, message: "File successfully deleted." };
|
|
122
|
+
} catch (error) {
|
|
123
|
+
console.error("Error deleting file:", error);
|
|
124
|
+
throw new functions.https.HttpsError('internal', 'Failed to delete file.');
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
//end file functions
|
|
129
|
+
|
|
130
|
+
|
|
14
131
|
exports.topicQueue = onSchedule({ schedule: 'every 1 minutes', timeoutSeconds: 180 }, async (event) => {
|
|
15
132
|
const queuedTopicsRef = db.collection('topic-queue')
|
|
16
133
|
const snapshot = await queuedTopicsRef.get()
|
package/src/postinstall.sh
CHANGED
|
@@ -22,6 +22,25 @@ awk '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/' ./src/fi
|
|
|
22
22
|
sed -e '1s/^/\/\/ #EDGE FIREBASE RULES START\n/' -e '$s/$/\n\/\/ #EDGE FIREBASE RULES END/' \
|
|
23
23
|
>> "$project_root/firestore.rules";
|
|
24
24
|
|
|
25
|
+
|
|
26
|
+
# Check if the destination file exists
|
|
27
|
+
if [ ! -f "$project_root/storage.rules" ]; then
|
|
28
|
+
# If the file does not exist, create it and add the rules_version line
|
|
29
|
+
echo "rules_version = '2';" > "$project_root/storage.rules";
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
# Ensure there's a newline at the end of the file
|
|
33
|
+
[ "$(tail -c1 $project_root/storage.rules)" != "" ] && echo "" >> "$project_root/storage.rules"
|
|
34
|
+
|
|
35
|
+
# Remove the existing block of rules between the markers
|
|
36
|
+
sed -i.backup '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/d' "$project_root/storage.rules"
|
|
37
|
+
|
|
38
|
+
# Extract the code block from the source file and append it to the destination file
|
|
39
|
+
awk '/\/\/ #EDGE FIREBASE RULES START/,/\/\/ #EDGE FIREBASE RULES END/' ./src/storage.rules | \
|
|
40
|
+
sed '1d;$d' | \
|
|
41
|
+
sed -e '1s/^/\/\/ #EDGE FIREBASE RULES START\n/' -e '$s/$/\n\/\/ #EDGE FIREBASE RULES END/' \
|
|
42
|
+
>> "$project_root/storage.rules";
|
|
43
|
+
|
|
25
44
|
if [ ! -d "$project_root/functions" ]; then
|
|
26
45
|
mkdir "$project_root/functions"
|
|
27
46
|
fi
|
|
@@ -41,12 +60,12 @@ if [ ! -f "$project_root/functions/.env.prod" ]; then
|
|
|
41
60
|
cp ./src/.env.prod "$project_root/functions/.env.prod"
|
|
42
61
|
fi
|
|
43
62
|
|
|
44
|
-
if [ ! -f "$project_root/.env.
|
|
45
|
-
cp ./src/.env.development "$project_root/.env.
|
|
63
|
+
if [ ! -f "$project_root/.env.dev" ]; then
|
|
64
|
+
cp ./src/.env.development "$project_root/.env.dev"
|
|
46
65
|
fi
|
|
47
66
|
|
|
48
|
-
if [ ! -f "$project_root/.env
|
|
49
|
-
cp ./src/.env.production "$project_root/.env
|
|
67
|
+
if [ ! -f "$project_root/.env" ]; then
|
|
68
|
+
cp ./src/.env.production "$project_root/.env"
|
|
50
69
|
fi
|
|
51
70
|
|
|
52
71
|
if [ ! -f "$project_root/functions/package.json" ]; then
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
rules_version = '2';
|
|
2
|
+
// #EDGE FIREBASE RULES START
|
|
3
|
+
service firebase.storage {
|
|
4
|
+
match /b/{bucket}/o {
|
|
5
|
+
// Deny read access to all paths
|
|
6
|
+
match /{allPaths=**} {
|
|
7
|
+
allow read: if false;
|
|
8
|
+
}
|
|
9
|
+
// Allow write access only to paths matching /temp/{userId}/{anyPath=**}
|
|
10
|
+
// {userId} is a placeholder for the actual user ID
|
|
11
|
+
// {anyPath=**} matches any file path under /temp/{userId}/
|
|
12
|
+
match /temp/{userId}/{anyPath=**} {
|
|
13
|
+
allow write: if request.auth != null && request.auth.uid == userId;
|
|
14
|
+
// Keep read access denied
|
|
15
|
+
allow read: if false;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
// #EDGE FIREBASE RULES END
|