1

I have a Firebase Functions that I am unable to query from my React.js web app but no problem through Postman.

When I try to make a request via the web app I get the following error in my Firebase Console:

Error: Can't set headers after they are sent.

at validateHeader (_http_outgoing.js:491:11)
at ServerResponse.setHeader (_http_outgoing.js:498:3)
at ServerResponse.header (/worker/node_modules/express/lib/response.js:767:10)
at ServerResponse.json (/worker/node_modules/express/lib/response.js:264:10)
at ServerResponse.send (/worker/node_modules/express/lib/response.js:158:21)
at exports.uploadImage.functions.https.onRequest (/srv/index.js:21:13)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:229:7)

And on the web app I get a 500 error response without any data returned.

This is how my request is made:

const data = {
    image: 'image url here',
    options: {
        tags: 'document,doctor',
    }
};
axios.post('*** FUNCTION URL HERE***/uploadImage', data, {
    headers: {
        'Content-Type': 'application/json',
    },
})

However if I make the request through Postman then I get a 200 response with the data I expect.

This is what my functions/index.js file look like:

const functions = require('firebase-functions');
const admin = require('firebase-admin');
const cloudinary = require('cloudinary');
const cors = require('cors')({origin: true});

admin.initializeApp();

exports.uploadImage = functions.https.onRequest(async (req, res) => {
    cors(req, res, () => {});
    cloudinary.config({
        cloud_name: 'config here',
        api_key: 'config here',
        api_secret: 'config here',
    });

    try { 
        const data = Object.apply({folder: 'myortho'}, req.body.options);
        const response = await cloudinary.uploader.upload(req.body.image, data);
        return res.send(response);
    }catch(error) {
        return res.send(500, {message: 'Error uploading image', error});
    }
});

I saw many questions relating to the error I am getting but many of those questions are referring to people's routing issues (which isn't a concern for me since this is a Cloud Function and not my own Node.js back-end server).

It is also an error I cannot understand since it is successful through Postman but not through my browser so I am truly confused.

FrenchMajesty
  • 1,101
  • 2
  • 14
  • 29

2 Answers2

2

When you use cors, you're supposed to put your code inside the callback function that you pass to it. You're not using that callback at all, and what's happening is that your code is trying to send two responses. It should be more like this:

exports.uploadImage = functions.https.onRequest(async (req, res) => {
    cors(req, res, () => {
        cloudinary.config({
            cloud_name: 'config here',
            api_key: 'config here',
            api_secret: 'config here',
        });

        try { 
            const data = Object.apply({folder: 'myortho'}, req.body.options);
            const response = await cloudinary.uploader.upload(req.body.image, data);
            return res.send(response);
        } catch(error) {
            return res.send(500, {message: 'Error uploading image', error});
        }
    });
});
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
0

To answer your question on why your request worked as-is in Postman, but not in the browser:

This is because Postman is a development tool and thus does not rely on CORS. On the other hand, browsers have more security concerns and rely on CORS.

Google Chrome's extension documentation explains more technically why this is (while it's written with extensions in mind, Postman also behaves in this manner):

Regular web pages can use the XMLHttpRequest object to send and receive data from remote servers, but they're limited by the same origin policy. Content scripts initiate requests on behalf of the web origin that the content script has been injected into and therefore content scripts are also subject to the same origin policy. (Content scripts have been subject to CORB since Chrome 73 and CORS since Chrome 83.) Extension origins aren't so limited - a script executing in an extension's background page or foreground tab can talk to remote servers outside of its origin, as long as the extension requests cross-origin permissions.

There is also another thread on Stack Overflow that discusses this further.

edude123
  • 3
  • 4