I'm trying to build the functionality that will automatically trigger an email through nodemailer when a specific file is uploaded to Firebase Storage. For flow - a user completes a form, the data is aggregated and a PDF is automatically generated, the PDF is then added to Cloud Storage.
The Storage path is "UserFiles/{uID}/"(in here lives the user's file)". When a specific file is finalized in Storage (called "Resume.pdf"), I'd like to send all of the files in that uID folder. Is this possible with cloud functions? I've built functionality to manually trigger this if a user clicks a button, but I'd like the email to automatically be sent when the upload is complete.
Here is the manual send (works fine):
const getDocumentURLs = () => {
firebase
.storage()
.ref("Tenant Resumes/" + firebase.auth().currentUser.uid)
.listAll()
.then((res) => {
res.items.forEach((result) => {
result.getDownloadURL().then((docURL) => {
setDocumentData((newURLs) => [...newURLs, docURL]);
console.log(docURL);
});
});
});
};
const sendMailFunction = async () => {
console.log(documentData);
const sendMailOverHTTP = firebase
.functions()
.httpsCallable("sendMailOverHTTP");
sendMailOverHTTP({
replyTo: userInfo.email,
name: userInfo.firstName + " " + userInfo.lastName,
documentOne: documentData[0] ? documentData[0] : "",
documentTwo: documentData[1] ? documentData[1] : "",
documentThree: documentData[2] ? documentData[2] : "",
documentFour: documentData[3] ? documentData[3] : "",
documentFive: documentData[4] ? documentData[4] : "",
documentSix: documentData[5] ? documentData[5] : "",
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.log(error);
});
};
How would I use this same methodology with a cloud function?
I'm trying:
exports.sendAutomatedResume = functions.storage.bucket("Resumes/{uID}/Resume.pdf")
.object()
.onFinalize(async (object) => {
But it doesn't seem to be working. Any thoughts?
Cloud Storage triggers aren't able to pre-filter based on the path of the uploaded file, but you can create a generic onFinalize function that returns early if the path doesn't match. For example:
exports.sendAutomatedResume = functions.storage.bucket().object()
.onFinalize(async (object) => {
const [folder, uid, filename, ...rest] = object.name.split("/");
if (folder !== "Resumes" || filename !== "Resume.pdf" || rest.length > 0) {
// doesn't match, so return early
return;
}
// does match, do stuff here
});
Related
So I am working on a upload function for multiple images in an array. After a lot of struggling I have finally got my upload function to work and the images are showing up in the Firebase Database. However I have yet to find out a working way to make sure my upload function completes before continuing.
Below is the part were I am calling the upload function and try to store the response in uploadurl, the uploadurl variable is later used in the dispatch function to store the url with other data.
try {
uploadurl = await uploadImages()
address = await getAddress(selectedLocation)
console.log(uploadurl)
if (!uploadurl.lenght) {
Alert.alert('Upload error', 'Something went wrong uploading the photo, plase try again', [
{ text: 'Okay' }
]);
setIsLoading(true);
return;
}
dispatch(
So the image upload function is below. This works to the point that the images are uploaded, however the .then call to get the DownloadURL is not started correctly and the .then images also is not working.
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
let imagesArray = [];
try {
Promise.all(photos)
.then(photoarray => {
console.log('all responses are resolved succesfully')
for (let photo of photoarray) {
let file = photo.data;
const path = "Img_" + uuid.v4();
const ref = firebase
.storage()
.ref(`/${uid}/${path}`);
var metadata = {
contentType: 'image/jpeg',
};
ref.putString(file, 'base64', metadata).then(() => {
ref
.getDownloadURL()
.then(images => {
imagesArray.push({
uri: images
});
console.log("Out-imgArray", imagesArray);
})
})
};
return imagesArray
})
} catch (e) {
console.error(e);
}
};
So I want to return the imagesArray, AFTER, all the photos are uploaded. So the imagesArray is then set as uploadURL in the first function? After all images URL are set in imagesArray and passed to uploadURL, only then my dispatch function to upload the rest of the data should continue. How can I make sure this is happening as expected?
I have changed this so many times now because I keep getting send to different ways of doing this that I am completely at a loss how to continue now :(
Most of your uploadImages() code was correct, however in many places you didn't return the promise from each asynchronous action.
Quick sidestep: Handling many promises
When working with lots of asynchronous tasks based on an array, it is advised to map() the array to an array of Promises rather than use a for loop. This allows you to build an array of promises that can be fed to Promise.all() without the need to initialise and push to another array.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry();
});
Promise.all(arrayOfPromises)
.then((resultsOfPromises) => {
console.log('All promises resolved successfully');
})
.catch((err) => {
// an error in one of the promises occurred
console.error(err);
})
The above snippet will fail if any of the contained promises fail. To silently ignore individual errors or defer them to handle later, you just add a catch() inside the mapped array step.
let arrayOfPromises = someArray.map((entry) => {
// do something with 'entry'
return somePromiseRelatedToEntry()
.catch(err => ({hasError: true, error: err})); // silently ignore errors for processing later
});
Updated uploadImages() code
Updating your code with these changes, gives the following result:
uploadImages = () => {
const provider = firebase.database().ref(`providers/${uid}`);
// CHANGED: removed 'let imagesArray = [];', no longer needed
return Promise.all(photos) // CHANGED: return the promise chain
.then(photoarray => {
console.log('all responses are resolved successfully');
// take each photo, upload it and then return it's download URL
return Promise.all(photoarray.map((photo) => { // CHANGED: used Promise.all(someArray.map(...)) idiom
let file = photo.data;
const path = "Img_" + uuid.v4();
const storageRef = firebase // CHANGED: renamed 'ref' to 'storageRef'
.storage()
.ref(`/${uid}/${path}`);
let metadata = {
contentType: 'image/jpeg',
};
// upload current photo and get it's download URL
return storageRef.putString(file, 'base64', metadata) // CHANGED: return the promise chain
.then(() => {
console.log(`${path} was uploaded successfully.`);
return storageRef.getDownloadURL() // CHANGED: return the promise chain
.then(fileUrl => ({uri: fileUrl}));
});
}));
})
.then((imagesArray) => { // These lines can
console.log("Out-imgArray: ", imagesArray) // safely be removed.
return imagesArray; // They are just
}) // for logging.
.catch((err) => {
console.error(err);
});
};
So I am very new to the whole coding scene and am trying to learn how to code using react native. Right now, I'm trying to figure out how to upload images using firebase (functions)and google cloud storage.
Below is the backend code that enables me to upload one image per submission to firebase.
I was wondering is it possible to modify this code so that it can upload multiple images per submission? If so, how would I go about doing it?
exports.storeImage = functions.https.onRequest((request, response) => {
return cors(request, response, () => {
const body = JSON.parse(request.body);
fs.writeFileSync("/tmp/uploaded-image.jpg", body.image, "base64", err => {
console.log(err);
return response.status(500).json({ error: err });
});
const bucket = gcs.bucket("myapp.appspot.com");
const uuid = UUID();
return bucket.upload(
"/tmp/uploaded-image.jpg",
{
uploadType: "media",
destination: "/places/" + uuid + ".jpg",
metadata: {
metadata: {
contentType: "image/jpeg",
firebaseStorageDownloadTokens: uuid
}
}
},
(err, file) => {
if (!err) {
return response.status(201).json({
imageUrl:
"https://firebasestorage.googleapis.com/v0/b/" +
bucket.name +
"/o/" +
encodeURIComponent(file.name) +
"?alt=media&token=" +
uuid,
imagePath: "/places/" + uuid + ".jpg"
});
} else {
console.log(err);
return response.status(500).json({ error: err });
}
}
);
})
.catch(error => {
console.log("Token is invalid!");
response.status(403).json({error: "Unauthorized"});
});
});
});
I don't have a React Native environment easily available, but I believe you can do it from the client with code like this:
await firebase.storage().ref('test/test.jpg').putFile('/path/to/test.jpg');
let downloadUrl = await firebase.storage().ref('test/test.jpg').getDownloadURL();
console.log('downloadUrl :', downloadUrl); // do whatever you need with it
To upload another image you just call the code twice, you can even do it in concurrently if you want.
When you use Firebase you should do most of the operations directly from the client, so you just need backend (including cloud functions) code if you need to do some heavy processing, use the admin SDK, integrate with third party apps, or stuff like that. For simple database or storage operations the client will suit you much better.
Also, you don't need to compose the download URL yourself, getDownloadUrl() does that for you. And if you access storage from the client it automatically integrates with Firebase Auth so you can protect your data.
I'm trying to make an app in Node to access my google calendar, so I followed the steps at https://developers.google.com/calendar/quickstart/nodejs but I'm getting Error: Error: No access, refresh token or API key is set..
Yes I have created the credentials.
Yes I have downloaded the json, renamed to client_secret.json and added to the application folder.
Here is the code:
const fs = require('fs');
const readline = require('readline');
const {google} = require('googleapis');
const OAuth2Client = google.auth.OAuth2;
const SCOPES = ['https://www.googleapis.com/auth/calendar.readonly'];
const TOKEN_PATH = './client_secret.json';
try {
const content = fs.readFileSync('client_secret.json');
authorize(JSON.parse(content), listEvents);
} catch (err) {
return console.log('Error loading client secret file:', err);
}
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed;
let token = {};
const oAuth2Client = new OAuth2Client(client_id, client_secret, redirect_uris[0]);
// Check if we have previously stored a token.
try {
token = fs.readFileSync(TOKEN_PATH);
} catch (err) {
return getAccessToken(oAuth2Client, callback);
}
oAuth2Client.setCredentials(JSON.parse(token));
callback(oAuth2Client);
}
function getAccessToken(oAuth2Client, callback) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return callback(err);
oAuth2Client.setCredentials(token);
// Store the token to disk for later program executions
try {
fs.writeFileSync(TOKEN_PATH, JSON.stringify(token));
console.log('Token stored to', TOKEN_PATH);
} catch (err) {
console.error(err);
}
callback(oAuth2Client);
});
});
}
function listEvents(auth) {
const calendar = google.calendar({version: 'v3', auth});
calendar.events.list({
calendarId: 'primary',
timeMin: (new Date()).toISOString(),
maxResults: 10,
singleEvents: true,
orderBy: 'startTime', }, (err, {data}) => {
if (err) return console.log('The API returned an error: ' + err);
const events = data.items;
if (events.length) {
console.log('Upcoming 10 events:');
events.map((event, i) => {
const start = event.start.dateTime || event.start.date;
console.log(`${start} - ${event.summary}`);
});
} else {
console.log('No upcoming events found.');
}
});
}
Any ideas?
Can you confirm as following points again?
The files of const TOKEN_PATH = './client_secret.json'; and const content = fs.readFileSync('client_secret.json'); are the same.
Please modify from const TOKEN_PATH = './client_secret.json'; to const TOKEN_PATH = './credentials.json';, and run again.
By this, client_secret.json that you downloaded has already might be overwritten. So please also confirm this.
When an error occurs even if above modification was done, please confirm the version of googleapis. Because it has been reported that googleapis with v25.0.0 - v30.0.0. has some bugs for some APIs.
If you think a bug for the error, please modify the version of googleapis to v24.0.0. The error may be removed.
References :
How do I update my google sheet in v4?
Create a gmail filter with Gmail API nodejs, Error: Filter doesn't have any criteria
Insufficient Permission when trying to create a folder on Google Drive via API(v3)
Youtube Data API V3 - Error fetching video with google.youtube.videos.list()
Google drive API - Cannot read property 'OAuth2' of undefined
How to run a Google App Script using Google API Service Library (Node.js)
If these points were not useful for your situation, I'm sorry.
Hello my firebase cloud function gets called multiple times when I don't put in check for previous.exists().
I get multiple push notifications.
if (!event.data.exists()){
return;
}
if (event.data.previous.exists()){
return;
}
But when I check for it i don't get push notification.
Here is the not working code:
What should I change?
exports.sendShoppingListInvitationNotification = functions.database.ref('/invites/{id}/').onWrite(event => {
//get the snapshot of the written data
const snapshot = event.data;
if (!event.data.exists()){
return;
}
if (event.data.previous.exists()){
return;
}
//get snapshot values
console.log(snapshot.key);
const receiptToken = snapshot.child('receiptFcmToken').val();
const senderName = snapshot.child('senderNickname').val();
const inviteMessage = snapshot.child('inviteMessage').val();
const senderImage = snapshot.child('senderProfileImageURL').val();
//create Notification
const payload = {
notification: {
title: `Invitation from ${senderName}`,
body: `${inviteMessage}`,
icon: `${senderImage}`,
badge: '1',
sound: 'default',
}
};
//send a notification to firends token
return admin.messaging().sendToDevice(receiptToken, payload).then(response => {
console.log("Successfully sent message:", response);
}).catch((err) => {
console.log(err);
});
});
I don't get error message on cloud console.
This is the firebase structure:
Seems like it shouldn’t be called multiple times unless you’re doing multiple writes to that location. Try using .onCreate instead of .onWriteif you only want to send a notification on the first write to the path. Then you won’t need that check for previous data. See the documentation here which outlines the different database triggers.
I have two image paths in my component state
I try to upload one of the images inside of a function but get an error:
Firebase Storage: Invalid argument in 'put' at index 0: Expected Blob or file
and my function
submitImages = () => {
// Upload images to Firebase storage
let user = firebaseAuth.currentUser;
let imagesRef = storageRef.child('productImages/' + user.uid);
imagesRef.put(this.state.imageFront).then(snapshot => {
console.log('Uploaded ' + this.state.imageFront);
});
}
What should I be doing instead to get these images up to Firebase. Thanks!
What the error says is that you need to use a blob. You can use react-native-fetch-blob: https://github.com/wkh237/react-native-fetch-blob
Check out this example: https://github.com/dailydrip/react-native-firebase-storage/blob/master/src/App.js#L43-L69
I am posting my code since this was a bit frustrating for me:
To upload images to firebase.storage you need to upload the images as Blobs. If you don't know what Blobs are, don't worry: BLOB stands for Binary Large OBject.
Step 1.
npm install --save react-native-fetch-blob
Step 2.
// copy and paste this code where you will handle the file upload
import RNFetchBlob from 'react-native-fetch-blob'
const Blob = RNFetchBlob.polyfill.Blob;
const fs = RNFetchBlob.fs;
window.XMLHttpRequest = RNFetchBlob.polyfill.XMLHttpRequest;
window.Blob = Blob;
Step 3.
// The uploadImage function that you are going to use:
function uploadImage(uri, mime = 'image/jpeg', name) {
return new Promise((resolve, reject) => {
let imgUri = uri; let uploadBlob = null;
const uploadUri = Platform.OS === 'ios' ? imgUri.replace('file://', '') : imgUri;
const { currentUser } = firebase.auth();
const imageRef = firebase.storage().ref(`/jobs/${currentUser.uid}`)
fs.readFile(uploadUri, 'base64')
.then(data => {
return Blob.build(data, { type: `${mime};BASE64` });
})
.then(blob => {
uploadBlob = blob;
return imageRef.put(blob, { contentType: mime, name: name });
})
.then(() => {
uploadBlob.close()
return imageRef.getDownloadURL();
})
.then(url => {
resolve(url);
})
.catch(error => {
reject(error)
})
})
}
So how do you call this function?
Pass the URI of the image as the first argument. In my case img1, img2, img3 where variables that pointed to the URIs of the images, that I wanted to upload which were on my phone. They looked something like '/Phone/Pics/imageToUpload.jpeg', etc.
As the second argument you can pass 'image/jpeg' and the last argument is the name that you want to give the image. Chose the name that you like.
But what if I have several images and want to upload them and want to handle the upload correctly. What if one upload succeeds and the other does not?
Do this then:
let imgPromises = [];
imgPromises.push(uploadImage(img1, 'image/jpeg', 'imageOne'));
imgPromises.push(uploadImage(img2, 'image/jpeg', 'imageTwo'));
imgPromises.push(uploadImage(img3, 'image/jpeg', 'imageOne'));
Promise.all(imgPromises).then(urls => {
// ALL IMAGES SUCCEEDED and you will get an array of URIS that you can save to your database for later use!
}).catch(error => {
// One OR many images failed the upload. Give feedback to someone.
})
You can use react-native-firebase to upload image to storge https://rnfirebase.io/
const storage = firebase.storage();
const sessionId = new Date().getTime();
const imageRef = storage.ref('images').child(`${sessionId}`);
return imageRef.putFile(uri);
So far this is the best method I found to upload a file/image to a Firebase Storage with React Native. This method does not use any third party libraries except for the Expo SDK.
Get the File URI of the image to upload. To do this we will need to use Expo ImagePicker. The best place to include this code block is on to a button with an onPress handler.
ImagePicker.launchImageLibraryAsync({
mediaTypes: "Images"
}).then((result)=>{
if (!result.cancelled) {
// User picked an image
const {height, width, type, uri} = result;
return uriToBlob(uri); // will follow later
}
})
Generate a BLOB from the image URI. There are a lot of third party libraries to help do this. But if you don't want to install a library, then you can use XMLHttpRequest. The React Native docs recommends we use the Fetch API, but right now we can't use it because it will throw an error that we can only fetch https:// urls, but our URI is a file://. There is a way to get pass this, but using XMLHttpRequest will make things a lot simpler.
uriToBlob = (uri) => {
return new Promise((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.onload = function() {
// return the blob
resolve(xhr.response);
};
xhr.onerror = function() {
// something went wrong
reject(new Error('uriToBlob failed'));
};
// this helps us get a blob
xhr.responseType = 'blob';
xhr.open('GET', uri, true);
xhr.send(null);
});
}
We have our BLOB, let's upload it to Firebase. This part is pretty straightforward as explained in the Firebase Docs.
uploadToFirebase = (blob) => {
return new Promise((resolve, reject)=>{
var storageRef = firebase.storage().ref();
storageRef.child('uploads/photo.jpg').put(blob, {
contentType: 'image/jpeg'
}).then((snapshot)=>{
blob.close(); // let's free up the blob
resolve(snapshot);
}).catch((error)=>{
reject(error);
});
});
}
That's it, you can now upload a file to Firebase Storage. The key part to this is getting a File URI and converting it to a BLOB. You can read more about this method here.
For some time I used the Firebase JS SDK with React Native. Using this library, as referred in this thread you need to use a library like rn-fetch-blob (react-native-fetch-blob is not maintained anymore) in order to provide a blob to Firebase Storage put() method.
Recently I started using React Native Firebase. As they say in their website "Using the native Firebase SDKs with React Native Firebase allows you to consume device SDKs which don't exist on the Firebase JS SDK".
Using React-Native-Firebase you don't need any extra library to upload images to Firebase Storage, and your code gets much cleaner:
export const uploadImage = (path, mime = 'application/octet-stream') => {
return new Promise((resolve, reject) => {
const imageRef = firebase.storage().ref('images').child('filename.jpg');
return imageRef.put(path, { contentType: mime })
.then(() => {
return imageRef.getDownloadURL();
})
.then(url => {
resolve(url);
})
.catch(error => {
reject(error);
console.log('Error uploading image: ', error);
});
});
};
if you don’t mind using cloudinary, I show how to upload and then get the uploaded url to save to firebase
https://medium.com/#ifeoluwaking24/how-to-upload-an-image-in-expo-react-native-to-firebase-using-cloudinary-24aac981c87
Also you can try it snack but make sure you add your cloud_name and upload_preset
https://snack.expo.io/#ifeking/upload-to-cloudinary