Expo and uploading image Blobs?

Blob support for network requests is still in progress (see https://github.com/facebook/react-native/pull/11573). One of the open-source RN maintainers at Facebook says:

We’re still planning on landing this. I can’t give a specific ETA - this is a pretty large PRs and integrating it with our codebase (including ensuring it passes all tests, which are not exposed here) has taken longer than expected.

Once that lands, it will be 1-2 months before it makes its way to a final RN release and we include it in Expo.

In this small tutorial, I wrote a cloud function that you can use to upload images to Firebase with Expo: Uploading Images to Firebase with Expo | by William Candillon | Medium. I hope this helps.

3 Likes

Awesome! Thank you for sharing this, William. :+1:t4:

I tried this solution today and there is error in firebase multipart requests. Anyone tried? I am really looking to solutions for this one for long time.

I updated the solution at Uploading Images to Firebase with Expo | by William Candillon | Medium to provide the necessary middlewares required in order to process the multipart request. It should work nicely for you.

Thanks for quick reply. Have you tried this? I’ve tried this already like below but this solution won’t work for me. File is always undefined.
Error message: 2017-12-09T14:16:44.513Z E app: TypeError: Cannot read property ‘originalname’ of undefined

const admin = require('firebase-admin');
const functions = require('firebase-functions');
const express = require('express');
const multer = require('multer');
const upload = multer();
const bodyParser = require('body-parser');
const getRawBody = require('raw-body');
const contentType = require('content-type');
const Busboy = require('busboy');

admin.initializeApp(functions.config().firebase);

const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
  extended: true
}));

app.use((req, res, next) => {
  if (req.rawBody === undefined && req.method === "POST" && req.headers["content-type"].startsWith("multipart/form-data")) {
    getRawBody(req, {
      length: req.headers["content-length"],
      limit: "10mb",
      encoding: contentType.parse(req).parameters.charset
    }, function(err, string) {
      if (err) return next(err);
      req.rawBody = string;
      next();
    });
  } else {
    next();
  }
});

app.use((req, res, next) => {
  if (req.method === "POST" && req.headers["content-type"].startsWith("multipart/form-data")) {
    const busboy = new Busboy({
      headers: req.headers
    });
    let fileBuffer = new Buffer("");
    req.files = {
      file: []
    };

    busboy.on("file", (fieldname, file, filename, encoding, mimetype) => {
      file.on("data", (data) => {
        fileBuffer = Buffer.concat([fileBuffer, data]);
      });

      file.on("end", () => {
        const file_object = {
          fieldname,
          originalname: filename,
          encoding,
          mimetype,
          buffer: fileBuffer
        };

        req.files.file.push(file_object);
        next();
      });
    });

    busboy.end(req.rawBody);
  } else {
    next();
  }
});

app.post('/picture', upload.single('picture'), function(req, response, next) {
  console.log('picture');
  console.log(req.file);
  uploadImageToStorage(req.file)
    .then((uri) => {
      response.status(200).json(uri);
      next();
    })
    .catch((error) => {
      console.error(error);
      response.status(500).json({ error });
      next();
    });
});

exports.app = functions.https.onRequest(app);

const uploadImageToStorage = (file) => {
  return new Promise((resolve, reject) => {
    const fileUpload = admin.storage().bucket().file(file.originalname);
    const blobStream = fileUpload.createWriteStream({
      metadata: {
        contentType: 'image/jpg'
      }
    });

    blobStream.on('error', (error) => reject(error));

    blobStream.on('finish', () => {
      // The public URL can be used to directly access the file via HTTP.
      resolve({
        [file.originalname]:
        `https://storage.googleapis.com/<BUCKET NAME>.appspot.com/${file.originalname}`
      });
    });

    blobStream.end(file.buffer);
  });
};

Sorry I forgot to update index.js. You can remove multer.

api.post("/picture", function (req, response, next) {
    uploadImageToStorage(req.files.file[0])

I’m updating the blog post as well

1 Like

Thanks!!! Finally it is working!!! By the way do you have any idea to fix below problem?

[eslint] 'new Buffer()' was deprecated since v6. Use 'Buffer.alloc()' or 'Buffer.from()' (use 'https://www.npmjs.com/package/safe-buffer' for '<4.5.0') instead. (node/no-deprecated-api)

Hi. I am stucked for 6 hours now…

I’ve successfully uploaded file but can never get the download URL. Above download url example did not worked for me so I’ve been googling to solve the problem. I’ve found a lot out there but couldn’t solve my problem. Have you tried your download url to load in your react-native app?

my last related stackoverflow link is below.

I get the download URL client side, it works pretty well. Could the issue
be that you are trying to get the download url server side?

Nah I just can’t load image with url provided below.
https://storage.googleapis.com/.appspot.com/${file.originalname}

This is how I do it client side:

            const body = new FormData();
            const name = `${id}.jpg`;
            // $FlowFixMe
            body.append("picture", {
                uri: picture.uri,
                name,
                type: "image/jpg"
            });
            await fetch(`${Firebase.endpoint}/picture`, {
                method: "POST",
                body,
                headers: {
                    Accept: "application/json",
                    "Content-Type": "multipart/form-data"
                }
            });
            const url = await Firebase.storage.ref(name).getDownloadURL();
1 Like

AH… This can be another solution. Should have known this earlier. I’ve figreued out the other way like below. Google’s doc is so frustrating… So hard to find the working example.

const uploadImageToStorage = (file) => {
  return new Promise((resolve, reject) => {
    let uuid = UUID();
    const imgPath = decodeURIComponent(file.originalname).replace('%2E', /\./g);
    const blob = gcsBucket.file(imgPath);
    const bucket = blob.createWriteStream({
      metadata: {
        contentType: 'image/png',
        metadata: {
          firebaseStorageDownloadTokens: uuid,
        }
      }
    });

    bucket.on('error', (error) => reject(error));

    bucket.on('finish', () => {
      console.log('finished');
      blob.getSignedUrl({
        action: 'read',
        expires: '03-09-2491'
      }).then((signedUrls) => {
        console.log('signedUrls');
        console.log(signedUrls[0]);
        resolve({ url: signedUrls[0] });
      });
    });

    bucket.end(file.buffer);
  });
};
TypeError: handler is not a function
    at cloudFunction (/user_code/node_modules/firebase-functions/lib/providers/https.js:26:41)
    at /var/tmp/worker/worker.js:671:7
    at /var/tmp/worker/worker.js:655:9
    at _combinedTickCallback (internal/process/next_tick.js:73:7)
    at process._tickDomainCallback (internal/process/next_tick.js:128:9)

did anybody else get this error? I followed the medium article but it didn’t work? Did I setup the cloud function wrong? I didn’t get any errors when I uploaded it?

I solved the issue as I had an index.js file, you have to use module.exports = api.post etc. if you want to separate the logic for multiple google cloud functions

hello, I want to try this on my project, is there any working example of this code?

1 Like

im getting an error it says can currently only create a blob from another blob?
please help me.

depends on how you have your google cloud functions registered. Is there a separated index file for

exports.api = functions.https.onRequest(api);

or are they in the same file? if they are separated then express needs to identify the api as a function handler and a module.exports = api.post… is needed

Hello i’m also stuck like you guys , i’have seen @wcandillon cloud functions but didnt understand how to do it , can some one share his code for cloud functions and how to implement it in our RN project, thanks alot.

1 Like

I tried using @wcandillon’s code, but that gave me the error “await is a reserved word”, then after that when I fixed that error it just said unexpected token. What am I doing wrong?