0

I have a cloud function that generates a set of resized images for every image uploaded. This is triggered with the onFinalize() hook.

Cloud Function to resize an uploaded image:

export const onImageUpload = functions
  .runWith({
    timeoutSeconds: 120,
    memory: '1GB'
  })
  .storage
  .object()
  .onFinalize(async object => {
    const bucket = admin.storage().bucket(object.bucket)
    const filePath = object.name
    const fileName = filePath.split('/').pop()
    const bucketDir = dirname(filePath)
    const workingDir = join(tmpdir(), 'resizes')
    const tmpFilePath = join(workingDir, fileName)

    if (fileName.includes('resize@') || !object.contentType.includes('image')) {
      return false
    }

    await fs.ensureDir(workingDir)

    await bucket.file(filePath).download({
      destination: tmpFilePath
    })

    const sizes = [
      500,
      1000
    ]

    const uploadPromises = sizes.map(async size => {
      const resizeName = `resize@${size}_${fileName}`
      const resizePath = join(workingDir, resizeName)

      await sharp(tmpFilePath)
        .resize(size, null)
        .toFile(resizePath)

      return bucket.upload(resizePath, {
        destination: join(bucketDir, resizeName)
      })
    })

    // I need to now update my Firestore database with the public URL.
    // ...but how do I get that here?

    await Promise.all(uploadPromises)
    return fs.remove(workingDir)
  })

That's all well and good and it works, but I also need to somehow retrieve the public URL for each of these images, in order to write the values into my Firestore.

I can do this on the frontend using getDownloadURL(), but I'm not sure how to do it from within a Cloud Function from the newly generated images.

As I see it, this needs to happen on the backend anyway, as my frontend has no way of knowing when the images have been processed.

Only works on the client:

const storageRef = firebase.storage().ref()
const url = await storageRef.child(`images/${image.name}`).getDownloadURL()

Any ideas?

Answer (with caveats):

This question was technically answered correctly by @sergio below, but I just wanted to point out some additional things that need doing before it can work.

  1. It appears that the 'expires' parameter of getSignedUrl() has to be a number according to TypeScript. So, to make it work I had to pass a future date represented as an epoch (milliseconds) like 3589660800000.

  2. I needed to pass credentials to admin.initializeApp() in order to use this method. You need to generate a service account key in your Firebase admin. See here: https://firebase.google.com/docs/admin/setup?authuser=1

Hope this helps someone else out too.

Michael Giovanni Pumo
  • 14,338
  • 18
  • 91
  • 140

1 Answers1

4

I believe the promises returned from bucket upload contain a reference to the File, which then you can use to obtain a signed URL.

Something like (not tested):

const data = await bucket.upload(resizePath, { destination: join(bucketDir, resizeName) });
const file = data[0];
const signedUrlData = await file.getSignedUrl({ action: 'read', expires: '03-17-2025'});
const url = signedUrlData[0];
Sergio
  • 491
  • 4
  • 11
  • Okay, this worked...but a few notes: 1. It appears 'expires' has to be a number according to TypeScript, so I had to pass a future date represented as an epoch (milliseconds) like 3589660800000. 2. I needed to pass credentials to admin.initializeApp() in order to use this method. I will add these notes to my question and mark this as the answer. Thank you! – Michael Giovanni Pumo Nov 28 '18 at 14:46