0

I always have a hard time when I'm uploading images to my Firebase project using the firebase-admin package.

Here is what I get when I upload images as an admin user, using my app's admin section, which uses the regular firebase JS package.

This is the code I use:

const metadata = {
  cacheControl: "public,max-age=31536000,must-revalidate"
  // contentType: "image/jpeg" // THIS IS AUTO INFERRED BY FIREBASE
};


const storageRef = firebase.storage().ref(`${directory}/${fileName}`);
const uploadTask = storageRef.put(file,metadata);

uploadTask.on("state_changed",
  function progress() {...},
  function error() {...},
  function complete() {...}
);

And this is what I get on Firebase storage console: a nice preview with links and a download token. Even though the cacheControl metadata is not displayed, it was set, 'cause it's visible when I visit the image's URL on my browser.

enter image description here

Now I'm writing an admin script to upload some images that I have on my local machine to my Firebase storage.

This is the code:

async function uploadImage() {

  admin.storage().bucket().upload(
    `./temp/${imageLocation}`,       // THIS IS MY LOCAL PATH
    { 
      destination: imageLocation,    // THIS IS THE PATH FOR THE STORAGE
    }
  );
  console.log(`Uploaded: ${imageLocation}`);
}

Everything works fine, the file was indeed uploaded, but this is what I get on Firebase storage console:

enter image description here

QUESTION

How can I use the firebase-admin in a way that I get the same consistent result as when I'm uploading an image using the firebase JS package on the browser?

Can I use the regular firebase package in my NodeJs admin script? I think I would have to authenticate as an admin user before making the firebase.storage().ref().put() request, since all storage paths are protected with allow write: if request.auth.token.admin == true;.

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336
  • Lack of preview for files uploaded with a backend SDK is a known issue with the Firebase console. If you don't like this behavior, contact Firebase support directly, as there is no documented solution. https://support.google.com/firebase/contact/support – Doug Stevenson Sep 23 '20 at 15:51

2 Answers2

4

you need to add uuidv4 package:

const { uuid } = require('uuidv4')

bucket.upload('cat.png', {
    destination: 'cat.png',
    metadata: {
        metadata: {
            firebaseStorageDownloadTokens: uuid(),
        }
    },
})
Methkal Khalawi
  • 2,368
  • 1
  • 8
  • 13
  • Thans, Methkal. I've also written an answer, would you take a look at it? I've noticed that the `firebase` JS SDK also adds a "weird" `contentDisposition` header. What does it do? I'm also adding it just in case. Also, is it ok to use the `import { v4 as uuid } from "uuid";` instead of `uuidv4` ? – cbdeveloper Sep 23 '20 at 10:42
0

Here is how I'm dealing with this:

When you upload something to the storage using the firebase JS SDK, it automatically adds some metadata along with it.

AFAIK, it adds two pieces of metadata:

const metadataObject = {
  contentDisposition: `inline; filename*=utf-8''${fileName}`  // THIS IS "REGULAR METADATA"
  metadata: {
    firebaseStorageDownloadTokens: downloadToken  // THIS IS "CUSTOM METADATA"
  }
}

The downloadToken is just a unique key generated with uuid(). As we can see from the snippet below from the Cloud Console (not the firebase console, because you can't see the metadata from there).

enter image description here

So you need to mimic that behavior when you are uploading an image with the firebase-admin package.

Here is how you can do it:

import * as admin from "firebase-admin";
import { v4 as uuid } from "uuid";

// INITIALIZE THE ADMIN WITH YOUR SERVICE ACCOUNT CREDENTIALS

admin.initializeApp({...});

/* ########################### */ 
/* #### GENERATE METADATA #### */
/* ########################### */ 

function generateMetadataObject(fileName: string) {
  const downloadToken = uuid();
  const metadataObject = {
    contentType: `image/jpeg`,  // contentType IS OPTIONAL CAUSE IT WILL BE AUTO-INFERRED
    cacheControl: `public,max-age=31536000,must-revalidate`,  // YOU CAN ALSO USE IT TO ADD OTHER METADATA LIKE cacheControl
    contentDisposition: `inline; filename*=utf-8''${fileName}`,
    metadata: {
      firebaseStorageDownloadTokens: downloadToken
    }
  };
  return metadataObject;
}

/* ##################################################### */
/* #### UPLOAD LOCAL IMAGE TO STORAGE WITH METADATA #### */
/* ##################################################### */

async function uploadImage() {

  const imageLocation = "/some/local_folder/someImage.jpg";
  const fileName = "someImage.jpg";

  await admin.storage().bucket().upload(
    imageLocation,   // THIS IS THE LOCAL PATH OF THE FILE
    {
      destination: "some/bucket_folder/someImage.jpg",  // THIS IS THE BUCKET PATH
      metadata: generateMetadataObject(fileName)        // ADD METADATA OBJECT
    }
  );
  console.log("File with metadata was uploaded to bucket..."); 
}

Doing like this, you'll get the same behavior and preview on the Firebase console as if you had uploaded the file using the regular firebase JS SDK package.

cbdeveloper
  • 27,898
  • 37
  • 155
  • 336