7

I am writing a firebase cloud function that records the download link of a recentally uploaded file to real-time database:

exports.recordImage = functions.storage.object().onFinalize((object) => {

});

"object" gives me access to two variables "selfLink" and "mediaLink" but both of them when entered in a browser they return the following:

Anonymous caller does not have storage.objects.get access to ... {filename}

So, they are not public links. How can I get the public download link within this trigger function?

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
Osama
  • 101
  • 1
  • 6

2 Answers2

11

You have to use the asynchronous getSignedUrl() method, see the doc of the Cloud Storage Node.js library: https://cloud.google.com/nodejs/docs/reference/storage/2.0.x/File#getSignedUrl.

So the following code should do the trick:

.....
const defaultStorage = admin.storage();
.....

exports.recordImage = functions.storage.object().onFinalize(object => {    
  const bucket = defaultStorage.bucket();
  const file = bucket.file(object.name);

  const options = {
    action: 'read',
    expires: '03-17-2025'
  };

  // Get a signed URL for the file
  return file
    .getSignedUrl(options)
    .then(results => {
      const url = results[0];

      console.log(`The signed url for ${filename} is ${url}.`);
      return true;
    })

});

Note that, in order to use the getSignedUrl() method, you need to initialize the Admin SDK with the credentials for a dedicated service account, see this SO Question & Answer firebase function get download url after successfully save image to firebase cloud storage.

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • @Osama Hi, did you have time to try the proposed answer? – Renaud Tarnec Nov 10 '18 at 09:52
  • Hi, yes I tried it and it failed. After long investigation, I found out a little mistake in the code you provided: `const file = bucket.file(object.bucket + '/' + object.name);` it should be: `const file = bucket.file(object.name);` So, just the object name. Can you please edit it, so others can benifit? Thanks. – Osama Nov 10 '18 at 10:21
  • @RenaudTarnec do you know how long this signedUrl is valid ? After I uploaded the image to firebase, I want to save the public direct url in another db (mongo) to have a direct reference, but I couln't find any information on how long this url might be valid, or if it is available for ever. Currently using the mediaLink. – turbopasi Dec 20 '18 at 12:37
  • 1
    @PascalLamers you set the expire date in the options object, have a look at the code in the answer. – Renaud Tarnec Dec 20 '18 at 12:54
  • Gotcha thanks, so setting it to something like `expires: '01-01-2500'` might do the trick then if i want the url to be valid forever. – turbopasi Dec 20 '18 at 12:57
  • 1
    @PascalLamers Yes indeed (unless you got tye secret of immortality ;-)) – Renaud Tarnec Dec 20 '18 at 18:27
  • Is this different from getDownloadUrl? The url seems a lot bigger with token and everything.. What is the expiration of getDownloadUrl then? – Daniel Vilela Aug 27 '20 at 07:37
9

*use this function:

function mediaLinkToDownloadableUrl(object) {
        var firstPartUrl = object.mediaLink.split("?")[0] // 'https://storage.googleapis.com/download/storage/v1/b/abcbucket.appspot.com/o/songs%2Fsong1.mp3.mp3'
        var secondPartUrl = object.mediaLink.split("?")[1] // 'generation=123445678912345&alt=media'

        firstPartUrl = firstPartUrl.replace("https://storage.googleapis.com/download/storage", "https://firebasestorage.googleapis.com")
        firstPartUrl = firstPartUrl.replace("v1", "v0")

        firstPartUrl += "?" + secondPartUrl.split("&")[1]; // 'alt=media'
        firstPartUrl += "&token=" + object.metadata.firebaseStorageDownloadTokens

        return firstPartUrl
    }

this is how your code might look like:

export const onAddSong = functions.storage.object().onFinalize((object) => {

    console.log("object: ", object);

    var url = mediaLinkToDownloadableUrl(object);

    //do anything with url, like send via email or save it in your database in playlist table 
    //in my case I'm saving it in mongodb database
    
    return new playlistModel({
        name: storyName,
        mp3Url: url,
        ownerEmail: ownerEmail
    })
    .save() // I'm doing nothing on save complete
    .catch(e => {
        console.log(e) // log if error occur in database write
    })

})

*I have tested this method on mp3 files, I'm sure it will work on all type of files but incase if it doesnt work for you simply go to firebase storage dashboard open any file and copy download url, and try to generate the same url in your code, and edit this answer too if possible

Inzamam Malik
  • 3,238
  • 3
  • 29
  • 61
  • 1
    Great answer. You saved my day. Though I believe download url should be available straight from object properties (especially if it can be constructed) – Vinayakaram Nagarajan Oct 10 '19 at 09:21