4

I am trying to write an image Firebase Storage via a Cloud Function (for more suitable write access).

My current attempt is to read the file object on the client, send it (the data) to an http firebase function, and then save it to storage from there. After saving the file successfully, I try using the download url as an img src value, but the file does not display. I also see an error in the Storage console (Error loading preview) when attempting to view the file.

If I save the data in Storage as base64, I can copy the contents of the file into the img src attribute, and it displays fine. However, I'd like to simply use the download URL as I could do if I just uploaded the image via the client SDK or directly via the console.

In the client, I'm simply using FileReader to read the uploaded file for sending. I've tried all the ways of reading it (readAsText,readAsBinaryString, readAsDataURL, readAsArrayBuffer), but none seem to solve the issue.

Here is how I am uploading the file via the Firebase Function:

import * as functions from 'firebase-functions';
import * as admin from 'firebase-admin';
import * as path from 'path';
import * as os from 'os';
import * as fs from 'fs-extra';

export default functions.https.onCall(async(req, context) => {
  const filename = req.filename;
  const bucket = admin.storage().bucket(environment.bucket)
  const temp_filename = filename;
  const temp_filepath = path.join(os.tmpdir(), temp_filename);
  await fs.outputFile(temp_filepath, req.data, {});

  // Upload.
  await bucket.upload(temp_filepath, {destination: 'logos'})
    .then((val) => {})
    .catch((err) => {});
});

This uploads the file successfully, however, the Download URL does not work when used as the img src attribute.

One thing I have noticed is that when using the client SDK to send a file (via AngularFireStorage), the payload is the raw png contents. E.g. a snippet of the file:

PNG


IHDRÈÈ­X®¤IDATx^í]  
Eµ¾·{&1,!dù»*yVQ@PTEDPA>ÊâC\P"ÈÄ"
F}òIW÷üCL@BÉL÷}
....

However, reading the file as text does not yield this encoding. I have tried several other encodings.

Any help would be immensely appreciated.

Edit

Here is what I mean about using the download URL:

<img alt='logo' src='https://firebasestorage.googleapis.com/v0/b/y<project-name>/o/logos%2FAnM65PlBGluoIzdgN9F5%2Fuser.png?alt=media&token=<token>' />

The above src url is the one provided in the Firebase Storage console when clicking on the file. It is labeled as 'Download URL' (I believe this is the one retrieved by calling getDownloadUrl() via the sdk).

When using AngularFireStorage to put the file in storage, the Download URL will work. When I say it 'will work', I mean the image will display properly. When using FileReader to pass the data to an http cloud function to upload (as seen above), the image will not display. In other words, after uploading the file via the backend, the download url does in fact provide what was uploaded, it's just not in a format that an img tag can display.

One possible issue may be that I am not getting the encoding correct when using FileReader readAsText. Here is what I am doing with FileReader:

const reader = new FileReader();

reader.onloadend = () => {
  firebase.functions().httpsCallable('http_put_logo')(reader.result);
};

// Have tried various encodings here, as well as all reader methods.
reader.readAsText(file); 

Edit 2

All of the discussion on this question so far seems to be around correctly getting the download URL. I'm not sure if Firebase docs have this information, but the download URL is available in the Storage console. I'm simply copying and pasting that URL to for testing purposes at the moment.

The reason why I am doing this is because I plan to save these image URLs in the DB since they are going to be frequently used and publicly readable. So, I'm not going to use the getDownLoadURL() method to fetch these images, I'm simply just going to link to them directly in img tags.

Here is an image of my console to see what I mean (bottom right):

enter image description here

You just have to click it and copy it. You can then open it in a browser tab, download it, use it as a src value, etc.

Edit 3

Here is an image of what the request payload looks like when using the client sdk: enter image description here

Here is when I read the file as text and send to backend for upload: enter image description here

Notice there are differences in the payloads. That's why I'm uncertain if I'm properly reading the file or encoding it incorrectly.

skwny
  • 2,930
  • 4
  • 25
  • 45
  • *"the Download URL does not work"*. I don't see any code here that deals with a download URL. What exactly isn't working? – Doug Stevenson Sep 07 '19 at 15:19
  • @DougStevenson added some more context/code – skwny Sep 07 '19 at 17:42
  • You can't just invent a new download URL using some pattern. You actually have to use the client API to generate a new one. Or you will have to generate a signed URL on the backend. – Doug Stevenson Sep 07 '19 at 18:17
  • @DougStevenson The download URL I use is found in the one provided in the Firebase Storage console. If you're referring to the `<>` parts, I was just using those as placeholders for the link to keep my project name private. The download url works fine when using the client sdk, or uploading from the backend. It's just that when uploading without client SDK, the image will not show. The problem I'm facing is not the download url. It is how to read and upload the file correctly when not using the client. I.e. read it from client, send it to CF and upload in same format as client sdk would. – skwny Sep 07 '19 at 18:25
  • @DougStevenson also I should mention. I do not intend to access the download url this way, I'm just doing it this way now for testing purposes. – skwny Sep 07 '19 at 18:28
  • This method of generating a download URL will not work in any case. Use the provided APIs to generate URLs. – Doug Stevenson Sep 07 '19 at 19:51
  • @DougStevenson I don't think we are on the same page. I am not generating urls. I am simply copying and pasting the available download url (from console) into an img src attribute to test it out. I can use `getDownloadURL()` and add it that way as well, but it is the same result so it doesn't matter. Are you aware of the Download URL that is provided in the Storage console? It is the same URL that one gets from `getDownloadURL()`. In any case, it's not an issue. The issue is that I can't determine how to get the image file into storage from my backend in exactly the same way as the SDK would. – skwny Sep 07 '19 at 20:01
  • If you have a donwload url from the console that doesn't work, then contact Firebase support and [file a bug report](https://support.google.com/firebase/contact/support) with steps to reproduce. – Doug Stevenson Sep 07 '19 at 20:09
  • @DougStevenson This is not a bug. I'll try to be more specific. The Download URL does work, in all cases. It's that when I try to upload the image file via the backend, the image file is not in the correct format, or something (*** this is the crux of the problem ***), and so when I try to use the Download URL as an img src value, the image does not display. I feel this is because the image was not read and uploaded in the correct format/encoding/something. So again the issue I'm facing is not a broken download url, it's how to read (form the client) and upload the file from the backend. – skwny Sep 07 '19 at 20:21
  • Why are you choosing base 64 for storage? I don't think browsers are going to understand that. Also would you mind showing the full function that does the upload, not just a snippet? – Doug Stevenson Sep 07 '19 at 20:39
  • @DougStevenson Updated the function code. I've tried many formats of storing the file. For base64, I tried that at the time b/c [base64 can be used as a Data URL for image src attribute](https://stackoverflow.com/a/8499716/3683643). Though I would need to get the contents of the file and add it to the src attr to work properly (I think). But, I would happily store it in whatever format is required to retrieve the image file for the browser, though I think that is what I am missing. I am also uploading an image of the request payload when using the SDK, to hopefully provide clues. – skwny Sep 07 '19 at 22:23
  • Yeah, I think the only way base64 works is if it's embedded in the URL itself, not if the contents of the URL is base64. – Doug Stevenson Sep 07 '19 at 22:38

1 Answers1

-1

What part of your code is taking care of getting the URL? I recently used a similar approach to uploading images to Firebase Storage using cloud functions. What worked best for me was to execute a different function to get the URL after the upload is complete. Something like this:

const bucket = admin.storage().bucket(environment.bucket)
  const temp_filename = filename;
  const temp_filepath = path.join(os.tmpdir(), temp_filename);
  await fs.outputFile(temp_filepath, req.data, {});

  // Upload.
  await bucket.upload(temp_filepath, {destination: 'images'})
    .then((val) => {retrieveUrl(temp_filename)})
    .catch((err) => {});

retrieveUrl = (imageName) => {
    const storage = firebase.storage();
    storage.ref(`/images/${imageName}.jpg`).getDownloadURL()
      .then( url => {
        /*Save the url to a variable or attach it directly to the src of your image, depending on the structure of your project*/
      })
      .catch(err => console.log(err));
  }

Keep in mind that you need to install firebase in your project in order to call firebase.storage.

Edgar Chávez
  • 119
  • 1
  • 4
  • Thanks for the answer but the issue I am having is not the download url. The download url works fine, it's just that when I try to upload an image file from the backend, the format is not correct (or something). When I use the download url in an `img` tag, it does not display. – skwny Sep 07 '19 at 19:09
  • Is there an instance of this where the url does work when used in the image src? What do you mean when you say that the url works fine? I experienced similar issues with my app when grabbing the url from the storage console. The storage.ref method worked perfectly for me. – Edgar Chávez Sep 07 '19 at 19:16
  • The url works when I use the client sdk to perform the upload (via AngularFireStorage). By 'works' I mean, I upload the image via the sdk, go to the console and copy the download url, paste it into the img `src` attribute, and voila the image displays. It will not work, however, if the image is uploaded the way I'm trying to do it (read file in client, pass to backend, and upload there). – skwny Sep 07 '19 at 19:21
  • When you use cloud functions to upload the image, is the image successfully uploaded and can you open it from the firebase console the same way you do with the sdk method? Can you visualize it in the browser? Please share the code for your Cloud Function if possible. – Edgar Chávez Sep 07 '19 at 21:20
  • Yes it is successfully uploaded and I can open in from the console. The only issue is that I can not visualize it (it's an image and it's just blank) as I can when using the sdk. I think that when I try uploading via the client-server way, I am not using the correct encoding, or something. Updated the function code above. – skwny Sep 07 '19 at 22:26
  • How are you handling the image on the client side? Are you appending it to form data? If that is the case I suggest you use Busboy to parse the file media type correctly before upload. – Edgar Chávez Sep 08 '19 at 00:44
  • No, not using form data. I'm reading the file via `FileReader` (above), and sending that as part of the payload to the http cloud function via `firebase.functions().httpsCallable()` request. – skwny Sep 08 '19 at 01:19