Functions-samples: Can not obtain correct download URL

Created on 10 Sep 2018  路  3Comments  路  Source: firebase/functions-samples

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?

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));

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

````

All 3 comments

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:

  • 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.

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-L95

You 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

Was this page helpful?
0 / 5 - 0 ratings

Related issues

Midhilaj picture Midhilaj  路  5Comments

blaforet picture blaforet  路  3Comments

Rovel picture Rovel  路  5Comments

aaronte picture aaronte  路  6Comments

ValentinTaleb picture ValentinTaleb  路  6Comments