How can I generate download URL after file is uploaded to the Firebase Storage with Google Cloud Functions? I'd need some alternative to getDownloadURL to be available also for Cloud Functions. Please provide example to run within
functions.storage.object().onFinalize(object => ... event
If I construct URL manually this way:
const url = "https://firebasestorage.googleapis.com/v0/b/" + bucketName + "/o/" + filename + "?alt=media&token=" + object.metadata.firebaseStorageDownloadTokens;
the token seems invalid occasionaly (getting 403 error) and different to URL obtained from console. Any idea why?
On Cloud Function you can use file.getSignedUrl(config)
We have some samples using it, for instance:
https://github.com/firebase/functions-samples/blob/Node-8/generate-thumbnail/functions/index.js#L85-L95
You need to make sure, as explained in the readme, to:
my SignedUrl is expiring in 2 weeks with following code
instead of " Service Account Token Creator" I had "owner" to my App Engine default service account which I just changed. is this the reason it was expiring ? or maybe I need to use service key with initializeApp ? or something else ?
````
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp();
const {Storage} = require('@google-cloud/storage');
const projectId = 'blahblah-65d71'; // I starred it inten
// Instantiates a client
const gcs = new Storage({
projectId: projectId,
});
const path = require('path');
const sharp = require('sharp');
const THUMB_MAX_WIDTH = 300;
const THUMB_MAX_HEIGHT = 300;
exports.thumbnailGen = functions.storage.object().onFinalize(async (object) => {
const fileBucket = object.bucket; // The Storage bucket that contains the file.
const filePath = object.name; // File path in the bucket.
console.log('object',object)
const contentType = object.contentType; // File content type.
const fileDir = path.dirname(filePath); //iadd
const lastDir = fileDir.split("/"); //iadd
// Exit if this is triggered on a file that is not an image.
if (!contentType.startsWith('image/')) {
console.log('This is not an image.');
return null;
}
// Get the file name.
const fileName = path.basename(filePath);
// Exit if the image is already a thumbnail.
if (fileName.startsWith('thumb_')) {
console.log('Already a Thumbnail.');
return null;
}
/* iadd */
const filenameWithoutExt = fileName.split('.').shift();
let exp = /^(.+)_([0-9]+)$/;
let thekey = filenameWithoutExt.replace(exp, '$1');
const bucket = gcs.bucket(fileBucket); // Download file from bucket.
const metadata = {
contentType: contentType,
};
console.log('metadata',metadata)
// We add a 'thumb_' prefix to thumbnails file name. That's where we'll upload the thumbnail.
const thumbFileName = thumb_${fileName};
const thumbFilePath = path.join(fileDir, thumbFileName);
// Create write stream for uploading thumbnail
const thumbnailUploadStream = bucket.file(thumbFilePath).createWriteStream({metadata});
let theimageindex = filenameWithoutExt.replace(exp, '$2');
/* iadd */
// Create Sharp pipeline for resizing the image and use pipe to read from bucket read stream
const pipeline = sharp();
pipeline
.resize(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT)
.jpeg({ quality: 100 })
.png({ quality: 100 })
.max()
.pipe(thumbnailUploadStream);
bucket.file(filePath).createReadStream().pipe(pipeline);
const config = {action: 'read', expires: '03-01-2025', contentType }
const streamAsPromise = new Promise((resolve, reject) =>
thumbnailUploadStream.on('finish', resolve).on('error', reject));
let thumbFileUrl
let thumbFile
let updatedatabasepath = {};
return streamAsPromise
.then(() => {
console.log('Thumbnail created successfully');
thumbFile = bucket.file(thumbFilePath);
return thumbFile.getSignedUrl(config)
})
.then((results)=>{
thumbFileUrl = results[0];
console.log('Got Signed URLs.',thumbFileUrl);
updatedatabasepath['/'+fileDir+'/'+thekey+'/'+'/thumbnails/'+filenameWithoutExt+'/thumbnailURL'] = thumbFileUrl;
updatedatabasepath['/'+fileDir+'/'+thekey+'/'+'/thumbnails/'+filenameWithoutExt+'/thumbnailcontentType'] = contentType;
return admin.database().ref().update(updatedatabasepath)
})
.then(() => {
//console.log('All done successfully');
return null
})
}); //exports.thumbnailGen
````
On Cloud Function you can use
file.getSignedUrl(config)We have some samples using it, for instance:
https://github.com/firebase/functions-samples/blob/Node-8/generate-thumbnail/functions/index.js#L85-L95You need to make sure, as explained in the readme, to:
- Go to your project's Cloud Console > IAM & admin > IAM, Find the App Engine default service account and add the Service Account Token Creator role to that member. This will allow your app to create signed public URLs to the images.
Expires next day
Most helpful comment
my SignedUrl is expiring in 2 weeks with following code
instead of " Service Account Token Creator" I had "owner" to my App Engine default service account which I just changed. is this the reason it was expiring ? or maybe I need to use service key with initializeApp ? or something else ?
````
const functions = require('firebase-functions');
const admin = require("firebase-admin");
admin.initializeApp();
const {Storage} = require('@google-cloud/storage');
const projectId = 'blahblah-65d71'; // I starred it inten
// Instantiates a client
const gcs = new Storage({
projectId: projectId,
});
const path = require('path');
const sharp = require('sharp');
const THUMB_MAX_WIDTH = 300;
const THUMB_MAX_HEIGHT = 300;
exports.thumbnailGen = functions.storage.object().onFinalize(async (object) => {
const fileBucket = object.bucket; // The Storage bucket that contains the file.
const filePath = object.name; // File path in the bucket.
console.log('object',object)
const contentType = object.contentType; // File content type.
const fileDir = path.dirname(filePath); //iadd
const lastDir = fileDir.split("/"); //iadd
// Exit if this is triggered on a file that is not an image.
if (!contentType.startsWith('image/')) {
console.log('This is not an image.');
return null;
}
// Get the file name.
const fileName = path.basename(filePath);
// Exit if the image is already a thumbnail.
if (fileName.startsWith('thumb_')) {
console.log('Already a Thumbnail.');
return null;
}
/* iadd */
const filenameWithoutExt = fileName.split('.').shift();
let exp = /^(.+)_([0-9]+)$/;
let thekey = filenameWithoutExt.replace(exp, '$1');
const bucket = gcs.bucket(fileBucket); // Download file from bucket.
const metadata = {
contentType: contentType,
};
console.log('metadata',metadata)
// We add a 'thumb_' prefix to thumbnails file name. That's where we'll upload the thumbnail.
const thumbFileName =
thumb_${fileName};const thumbFilePath = path.join(fileDir, thumbFileName);
// Create write stream for uploading thumbnail
const thumbnailUploadStream = bucket.file(thumbFilePath).createWriteStream({metadata});
let theimageindex = filenameWithoutExt.replace(exp, '$2');
/* iadd */
// Create Sharp pipeline for resizing the image and use pipe to read from bucket read stream
const pipeline = sharp();
pipeline
.resize(THUMB_MAX_WIDTH, THUMB_MAX_HEIGHT)
.jpeg({ quality: 100 })
.png({ quality: 100 })
.max()
.pipe(thumbnailUploadStream);
bucket.file(filePath).createReadStream().pipe(pipeline);
const config = {action: 'read', expires: '03-01-2025', contentType }
const streamAsPromise = new Promise((resolve, reject) =>
thumbnailUploadStream.on('finish', resolve).on('error', reject));
}); //exports.thumbnailGen
````