1

I've already read all other answers related to this. But it doesn't work for me or I just don't understand something. Please help.

I'm sending the 'image/png' base64 string from the client. It is something like this: "iVBORw0KGgoAAAANSUhEUgAAAgAAAA......."

On Cloud Functions I have the method:

exports.uploadImage = functions.https.onCall((data, context) => {  
  var bytes = data.imageData;
  uploadFile(bytes);
})

function _base64ToArrayBuffer(binary_string) {
  var len = binary_string.length;
  var bytes = new Uint8Array(len);
  for (var i = 0; i < len; i++) {
      bytes[i] = binary_string.charCodeAt(i);
  }
  return new Uint8Array(bytes);
}

async function uploadFile(data) {
    const imageBuffer = _base64ToArrayBuffer(data)
    const fielName = 'img/' + UTIL.getRandomString(20) + '.png'
    const fileUpload = bucket.file(fielName)

    const uploadStream = fileUpload.createWriteStream({
      metadata: {
          contentType: 'image/png'
      }
  });

  uploadStream.on('error', (err) => {
      console.log(err);
      return;
  });

  uploadStream.on('finish', () => {
      console.log('Upload success');
      bucket.file(fielName).makePublic();
  });

  uploadStream.end(imageBuffer);
}

As a result I have some strange file saved on Firebase Storage that is 35Kb (the source image is 14kb) The the image of this file not showing and there is no the link to the image created. enter image description here

What can be wrong?

Yura Buyaroff
  • 1,718
  • 3
  • 18
  • 31

3 Answers3

1

I'm thinking that there might be something going wrong in the ToArrayBuffer you are using.

Can you attempt to use the base64EncodedImageString buffer and see if that works for you? I've seen this other SO thread of people attempting to pipe an image to GCS with a Cloud Function as well and the main differences I'm seeing with your code is the buffer.

SO Thread

Hope this helps.

EDIT:

As you mentioned that the item is still not public, try adding in the metadata of the createWriteStream

public = true

If you have any doubts of exactly what I mean you can refer to the previous SO thread I linked, it is present there as well.

Let us know.

Stefan G.
  • 890
  • 5
  • 10
  • Thank you. This method of getting bufferStream make file size 29.69 KB and it is still not a picture. My base64 string seems correct. I've checked it with https://www.base64-image.de/ for example. So it is still not working – Yura Buyaroff Dec 17 '19 at 15:11
  • Thanks again. But it doesn't do exactly what I nee. It doesn't create access token. As you can see on the screenshot. That mean I don't have public url that I can use for the image. I believe it should be done via using some functions os the Storage sdk but not just metadata. – Yura Buyaroff Dec 24 '19 at 20:37
1

In your logic you have:

function _base64ToArrayBuffer(binary_string) {
  var len = binary_string.length;
  var bytes = new Uint8Array(len);
  for (var i = 0; i < len; i++) {
      bytes[i] = binary_string.charCodeAt(i);
  }
  return new Uint8Array(bytes);
}

It is my belief that your intent is to decode a Base64 string to its binary data representation. I am not sure where you got that code fragment nor what it actually does ... but I can assure you that it has nothing to do with base64 decoding.

Node.js has some powerful libraries that will perform Base64 decoding on your behalf. For example the Buffer class.

let buff = new Buffer(data, 'base64');

will decode a Base64 String in data to a buffer of bytes.

And excellent article to read is Encoding and Decoding Base64 Strings in Node.js.

Kolban
  • 13,794
  • 3
  • 38
  • 60
  • Thank you. You're right about base64 but images are still not public. – Yura Buyaroff Dec 17 '19 at 16:45
  • @YuraBuyaroff - I'm not following I'm afraid. In the original post we discussed that the images weren't good and I think we find that the puzzle is that they weren't being decoded from base64 properly. So where are we in the puzzle now? – Kolban Dec 17 '19 at 19:31
  • @YuraBuyaroff I think you now need to pass in the metadata the public value. I will edit my answer so you can try it out and let me know. – Stefan G. Dec 17 '19 at 23:16
0

Upload the image and using sharp crop it, optimize it and convert it to png. Here you can find more information about the library.

The package uuid is used to get a uuid to use as a token and then get the download image. You could also use this to give each image a unique name.

Note that the userId is also being added to the metadata of the image, this is just an example, if you wish you can remove it. You can add more metadata if you want.

Note that the useFirestoreEmulator variable is also used, to indicate if you are using the firestore emulator and thus obtain the download url from your local or google servers.

import * as admin from "firebase-admin";
import * as functions from "firebase-functions";
import { log } from "firebase-functions/logger";

import { v4 as uuidv4 } from "uuid";
import * as sharp from "sharp";

export const useFirestoreEmulator = true;
export const hostFirestore = "https://firebasestorage.googleapis.com";
export const hostFirestoreLocal = "http://192.168.3.19:9199";

export class ImageUtil {
  static async uploadFile(userId: string, base64: string, width: number) {
    const bucket = admin.storage().bucket();

    const buffer = Buffer.from(base64, "base64");

    const bufferSharp = await sharp(buffer)
      .png({
        quality: 50,
        effort: 1,
        compressionLevel: 9,
      })
      .resize({
        width: width,
        fit: sharp.fit.outside,
        withoutEnlargement: true,
      })
      .toBuffer();

    const nombre = "IMAGE_NAME.png";
    const token = uuidv4();

    const fileName = `img/${nombre}.png`;
    const fileUpload = bucket.file(fileName);

    const uploadStream = fileUpload.createWriteStream();

    uploadStream.on("error", async (err) => {
      log("Error uploading image", err);

      throw new functions.https.HttpsError("unknown", "Error uploading image");
    });

    uploadStream.on("finish", async () => {
      await fileUpload.setMetadata({
        contentType: "image/png",
        metadata: {
          metadata: {
            firebaseStorageDownloadTokens: token,
            userId: userId,
          },
        },
      });

      log("Upload success");
    });

    uploadStream.end(bufferSharp);

    return {
      fileName,
      url: this.getDownloadUrl(fileName, bucket.name, token),
    };
  }

  static async getDownloadUrl(
    nameFile: string,
    bucketName: string,
    token: string,
  ) {
    return (
      (useFirestoreEmulator ? hostFirestoreLocal : hostFirestore) +
      "/v0/b/" +
      bucketName +
      "/o/" +
      encodeURIComponent(nameFile) +
      "?alt=media&token=" +
      token
    );
  }
}

export const uploadFile = functions.https.onCall((data, context) => {
  const bytes = data.imageData;

  const userId = context.auth?.uid;

  if (!userId) {
    throw new functions.https.HttpsError(
      "unauthenticated",
      "You must be authenticated",
    );
  }

  return ImageUtil.uploadFile(userId, bytes, 500);
});
edalvb
  • 581
  • 6
  • 7