How to call refFromURL in Firebase Cloud Function - firebase

I'm storing references to files in Firebase Cloud Storage using URLs. In firebase client code, you can call firebase.storage().refFromURL(photo.image) to get the actual storage reference and do handy things like call delete with it. How do I accomplish the same thing in a cloud function (specifically a realtime database trigger)? I want to be able to clean up images after deleting the object that references them.

Following Bob Snider's answer, this is a little function (typescript) to extract file full path from URL.
export const getFileFromURL = (fileURL: string): Promise<any> => {
const fSlashes = fileURL.split('/');
const fQuery = fSlashes[fSlashes.length - 1].split('?');
const segments = fQuery[0].split('%2F');
const fileName = segments.join('/');
return fileName;
}

In a cloud function, to delete a file from storage you need the file's bucket name and file name (which includes the path). Those can be obtained on the client side from the storage reference. For example, a JS Storage Reference has properties bucket and fullPath. The string representation of a storage reference has format: gs://example-12345.appspot.com/path/to/file, where the bucket is example-12345.appspot.com and the file "name" is path/to/file.
In the example cloud function shown below, the client is expected to provide the bucket and filename as children of the trigger location. You could also write the URL string to the trigger location and then split it into bucket and filename components in the cloud function.
This code is based on the example in the Cloud Storage guide.
const functions = require('firebase-functions');
const gcs = require('#google-cloud/storage')();
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.deleteFile = functions.database.ref('/test').onWrite(event => {
const bucket = event.data.child('bucket').val();
const filename = event.data.child('filename').val();
console.log('bucket=', bucket, 'filename=', filename);
return gcs.bucket(bucket).file(filename).delete().then(() => {
console.log(`gs://${bucket}/${filename} deleted.`);
}).catch((err) => {
console.error('ERROR:', err);
});
});

Here is a one-liner.
const refFromURL = (URL) => decodeURIComponent(URL.split('/').pop().split('?')[0])

I've wrote code sample which I using instead refFromURL method from web-firebase in my functions project based on Bob Snyder answer.
function refFromUrl(gsLink) {
var fileEntryTemp = gsLink.file.replace("gs://", "")
var bucketName = fileEntryTemp.substring(0, fileEntryTemp.indexOf("/"));
var filename = gsLink.file.match("gs://" + bucketName + "/" + "(.*)")[1];
var gsReference = admin.storage().bucket().file(filename);
return gsReference;
}
Here is an example how I get a download link based on this ref:
var gsReference = refFromUrl(fileEntry);
gsReference.getSignedUrl({
action: 'read',
expires: '03-09-2491'
}).then(function (url) {
console.log(url);
response.send(url);
}).catch(function (error) {
});
Hope this will save time for somebody

For complicated actions on your database from cloud functions you could use Admin SDK https://firebase.google.com/docs/database/admin/startFor the usage of Cloud Storage in Cloud Function check this out https://firebase.google.com/docs/functions/gcp-storage-eventsCloud Functions may not provide the same capability as client since Cloud Functions is beta for now and people are still working on it.

Related

Firebase Storage with Google Actions

I am having some issues connecting my firebase storage with my google action. I need to be able to "download" the json files inside in order to be able to read and pick out what a user may need given data that they provide when they call the action.
Below is the code that I currently have, complied from the different APIs and other stackoverflow questions I have found.
const functions = require('firebase-functions');
const admin = require('firebase-admin');
const Firestore = require('#google-cloud/firestore');
const firestore = new Firestore();
var storage = require('#google-cloud/storage');
const gcs = storage({projectId: 'aur-healthcare-group'});
const bucket = gcs.bucket('gs://aur-healthcare-group');
admin.storage().bucket().file('aur-healthcare-group/aur_members.json').download(function(errr, contents){
if(!err){
var jsObjext = JSON.parse(contents.toString('utf8'));
}
});
The current error I am receiving is "code":3,"message":"Function failed on loading user code. This is likely due to a bug in the user code. Error message: Error: please examine your function logs to see the error cause. When I check the logs I only get the above mentioned message again.
I believe that I am not accessing my firebase storage correctly and have trouble finding a good resource on how to access this correctly. Would somebody be able to give me an example of how to access the storage correctly so I will be able to apply it to my project?
Since you're running in Firebase Functions, you shouldn't need to require the #google-cloud/storage dependency directly. Rather, you can get the correctly authenticated storage component via admin.storage()
Following that, you shouldn't download the file to your function, as you would be better off reading directly into memory via a readStream.
With regards to your existing code error, it may be because you're checking if (!err) when the callback variable is errr.
I've done this in the past and here's a code snippet of how I achieved it. It's written in Typescript specifically, but I think you should be able to port it to JS if you're using that directly.
import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin'
import { Bucket } from '#google-cloud/storage';
admin.initializeApp()
const db = admin.firestore()
const bucket = admin.storage().bucket('project-id.appspot.com') // Use your project-id here.
const readFile = async (bucket: Bucket, fileName: string) => {
const stream = bucket.file(fileName).createReadStream();
return new Promise((resolve, reject) => {
let buffer = '';
stream.on('data', function(d: string) {
buffer += d;
}).on('end', function() {
resolve(buffer)
});
})
}
app.handle('my-intent-handler', async (conv) => {
const contents = await readArticle(bucket, 'filename.txt')
conv.add(`Your content is ${contents}`)
})
exports.fulfillment = functions.https.onRequest(app)

Generating a PDF when a document is created in Firebase Cloud Firestore

I'm developing an app that creates a PDF based on a web form.
I am currently attempting to use pdfmake to generate the PDFs based on a firestore document create trigger
import * as functions from 'firebase-functions';
const admin = require('firebase-admin);
admin.initializeApp();
const PdfPrinter = require('pdfmake');
const fs = require('fs');
export const createPDF = functions.firestore
.document('pdfs/{pdf}')
.onCreate(async (snap, context) => {
var pdfName = context.params.pdf;
var printer = new PdfPrinter();
var docDefinition = {
// Pdf Definitions
};
var options = {
// Pdf Options
};
var pdfDoc = printer.createPdfKitDocument(docDefinition, options);
pdfDoc.pipe(fs.createWriteStream('tempDoc.pdf'));
await pdfDoc.end();
// Upload to Firebase Storage
const bucket = admin.storage().bucket('myproject.appspot.com');
bucket.upload('tempDoc.pdf', {
destination: pdfName + '.pdf',
});
return fs.unlinkSync('document.pdf');
});
The trigger is called, however i get the error "Error: ENOENT: no such file or directory, stat 'document.pdf'"
I have tried it with the onCreate function being async and without.
Any help is greatly appreciated
It's not possible to write to any file location in Cloud Functions outside of /tmp. If your code needs to write a file, it should build paths off of os.tmpdir() as described in the documentation:
The only writeable part of the filesystem is the /tmp directory, which
you can use to store temporary files in a function instance. This is a
local disk mount point known as a "tmpfs" volume in which data written
to the volume is stored in memory. Note that it will consume memory
resources provisioned for the function.
The rest of the file system is read-only and accessible to the
function.

how to create refFromURL with admin privilege on cloud functions?

I want to have a reference to an image using its http URL when firestore update cloud function triggered so that i can take the url from change provide by onUpdate() function and use it to get a reference to the image on firebase storage and delete it.
In order to delete a file stored in Cloud Storage for Firebase from a Cloud Function you will need to create a File object based on:
The Bucket instance this file is attached to;
The name of the file,
and then call the delete() method
as detailed in the Node.js library documentation https://cloud.google.com/nodejs/docs/reference/storage/2.0.x/File.
Here is an example of code from the documentation:
const storage = new Storage();
const bucketName = 'Name of a bucket, e.g. my-bucket';
const filename = 'File to delete, e.g. file.txt';
// Deletes the file from the bucket
storage
.bucket(bucketName)
.file(filename)
.delete()
.then(() => {
console.log(`gs://${bucketName}/${filename} deleted.`);
})
.catch(err => {
console.error('ERROR:', err);
});
From your question, I understand that your app clients don't have the bucket and file names as such and only have a download URL (probably generated through getDownloadURL if it is a web app, or the similar method for other SDKs).
So the challenge is to derive the bucket and file names from a download URL.
If you look at the format of a download URL you will find that it is composed as follows:
https://firebasestorage.googleapis.com/v0/b/<your-project-id>.appspot.com/o/<your-bucket-name>%2F<your-file-name>?alt=media&token=<a-token-string>
So you just need to use a set of Javascript methods like indexOf(), substring() and/or slice() to extract the bucket and file names from the download URL.
Based on the above, your Cloud Function code could then look like:
const storage = new Storage();
.....
exports.deleteStorageFile = functions.firestore
.document('deletionRequests/{requestId}')
.onUpdate((change, context) => {
const newValue = change.after.data();
const downloadUrl = newValue.downloadUrl;
// extract the bucket and file names, for example through two dedicated Javascript functions
const fileBucket = getFileBucket(downloadUrl);
const fileName = getFileName(downloadUrl);
return storage
.bucket(fileBucket)
.file(fileName)
.delete()
});

How to store strings into firebase storage via cloud function

I did Google around and tried some code but didn't work, since every time I deploy cloud functions to firebase, it takes about 30 secs - 1 min, I think it's a complete waste of time if I continued to try code from the internet
So, I need to write a cloud function like this:
const admin = require('firebase-admin');
module.exports = function (request, response) {
const { message } = request.body;
// Now, store `message` into firebase storage
// path is: /messages/new_message, where `new_message`
// is NOT a folder, but the file that contains `message`
}
I do have a solution, but obviously, it's not a wise choice, I mean, I can always install firebase package, then call initializeApp(...), then firebase.storage().ref().... Is there another way to do this? Could you please write a little code to elaborate it?
You'll want to use the #google-cloud/storage module.
// Creates a GCS client,
const storage = new Storage();
module.exports = function (req, res) {
const { message } = req.body;
const bucket = storage .bucket('projectid.appspot.com');
const file = bucket.file('myFolder/myFilename');
// gcloud supports upload(file) not upload(bytes), so we need to stream.
const uploadStream = file.createWriteStream();
.on('error', (err) => {
res.send(err);
}).on('finish', () => {
res.send('ok');
}
uploadStream.write(data);
uploadStream.end();
}
See my parse-server GCS adapter for an example.

Can't download file using google cloud storage and Cloud Functions for Firebase

I am having the opposite as this issue:
issues deleting an image using Cloud Functions for Firebase and #google-cloud/storage
(for the record, I have tried all things suggested there).
Basically I have a known file path, then a cloud function triggered by a database event.
I can initialise a bucket, get a file as well as its name, but then when I try and download it I get API Error: not found.
Here is my code:
module.exports = (orgID, reportID) => {
const bucket = gcs.bucket("MY_PROJECT.appspot.com");
const filePath = `/safety_hotline/${orgID}/${reportID}`;
const file = bucket.file(filePath);
// the name is shown correctly in the console
console.log(file.name);
const tempLocalFile = path.join(os.tmpdir(), filePath);
const tempLocalDir = path.dirname(tempLocalFile);
return mkdirp(tempLocalDir)
.then(() => {
// Download file from bucket.
return file.download({ destination: tempLocalFile });
})
.then(() => {
console.log("file downloaded succesfully");
})
.catch(err => {
console.log(err);
});
}
You can see I get the console log of the file name, so I don't understand why I can't then download it?
Any advice would be amazing, thanks!
Edit: edited code a bit for clarity
I see you have this line:
const filePath = `/safety_hotline/${orgID}/${reportID}`;
I am guessing that you may have named your objects with the pattern safety_hotline/org/report, but as written above the first character of the object name would be a slash. That's also a legal object name but it's usually unintentional. Try removing the slash?
You Try This Follow functions-samples ?
i'am try this follow done Download file successful

Resources