3

I am trying to retrieve the download url for new files uploaded so I can write that to my db. I've followed both this answer and the official docs but I am getting errors in my function.

These are my imports:

import * as functions from "firebase-functions";
import * as admin from "firebase-admin";
import { FieldValue } from "@google-cloud/firestore";
import { _namespaceWithOptions } from "firebase-functions/lib/providers/firestore";
admin.initializeApp(functions.config().firebase);
const db = admin.firestore();
const defaultStorage = admin.storage();

This is currently my desired cloud function:

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

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

    return file.getSignedUrl(options)
    .then(results => {
      const url = results[0];

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

But when passing options into getSignedUrl I get this error:

Argument of type '{ action: string; expires: string; }' is not assignable to parameter of type 'GetSignedUrlConfig'

Also I get an error on results saying:

Parameter 'results' implicitly has an 'any' type

I can't see what I am doing different form the examples I've used as reference.

Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
Tsabary
  • 3,119
  • 2
  • 24
  • 66
  • Could you edit the question to show exactly how you are importing or using the modules here? Especially Cloud Storage, or the Firebase Admin SDK. You might also have a problem with the TypeScript configuration for your project. You shouldn't have to be specific about the types of things - they should all be understood by TypeScript. – Doug Stevenson Jan 11 '20 at 17:01
  • @DougStevenson I've edited my question and added my imports. But - I actually ended up solving the issue and I am not sure why it makes any difference (my solution). Would appreciate your input if you understand why. – Tsabary Jan 11 '20 at 17:22

2 Answers2

4

This is because you are using TypeScript and the example you are referring to is using JavaScript. You should import the type GetSignedUrlConfig as follows:

import { GetSignedUrlConfig } from '@google-cloud/storage';

and do

const options: GetSignedUrlConfig = {
  action: 'read',
  expires: '03-17-2025'
};
Renaud Tarnec
  • 79,263
  • 10
  • 95
  • 121
  • Are you certain about this? TypeScript is perfectly capable of inferring the type of object based on its "shape". The shape of the options object looks just fine to me and matches what's in the documentation. https://developer.android.com/guide/components/services#CreatingAService – Doug Stevenson Jan 11 '20 at 17:03
  • @DougStevenson I've tested it and without explicitly specifying the type of the `options` object, I get the same errors than the OP. Maybe my TypeScript set-up for Cloud Functions is incomplete, but I followed the doc so I assume it is ok. PS: is the link in your comment the correct one? – Renaud Tarnec Jan 11 '20 at 17:06
  • My projects have never had a problem inferring the type at all. It just works if you pass the bare object. TypeScript understands what's expected of the config and checks the keys and values to make sure they comply with the TypeScript binding provided by the cloud SDK. There might be something wrong with your project TS config. – Doug Stevenson Jan 11 '20 at 17:10
  • @RenaudTarnec thank you for your answer. Before getting to see it I was able to solve it with a very simple solution. Honestly though, I am not sure why the change I've made actually makes any difference. Would appreciate clarification if you do. – Tsabary Jan 11 '20 at 17:19
  • The problem has to do with the TypeScript type intersection required by the `action` property. It can't marry up plain "string" with a string intersection required by the cloud SDK, so it complains. Either solution here works to satisfy the type system. – Doug Stevenson Jan 11 '20 at 17:41
  • @DougStevenson FYI, just tried with a brand new Firebase project and clean Cloud Functions set-up with TypeScript (in particular following a [video](https://youtu.be/DYfP-UIKxH0) you do know :-)) and still having the same problem: types are not inferred automatically. – Renaud Tarnec Jan 11 '20 at 17:53
  • 1
    I explained why it's happening in this particular case. – Doug Stevenson Jan 11 '20 at 17:54
1

So, what solved my problem ended up placing the config object directly into the method as follows:

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

    return file.getSignedUrl({
        action: 'read',
        expires: '03-17-2025'
      })
    .then(results => {
      const url = results[0];

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

I am not sure why it makes a difference and would enjoy clarification form anyone that does, but it works just with this small change.

Tsabary
  • 3,119
  • 2
  • 24
  • 66
  • 3
    The problem is actually because the `action` property is typed as an intersection of `'read' | 'write' | 'delete' | 'resumable'`, to check if you passed a correct action value. This works if you pass the object inline like this, because it's able to infer the type based on what the cloud SDK expects. But when you create a plain object and assign it to a variable as you did originally, this type information is lost, and TypeScript only understands that you gave it a string value (not one of the specific values). So, you can do it like you are now, or declare the type at the time of assignment – Doug Stevenson Jan 11 '20 at 17:39