Stripe Firebase Cloud Functions - res.send() is not a function - node.js

I'm new to server development and trying to get my cloud firebase function working. I'm getting a res.send() is not a function on my firebase Log when my stripe webhook fires and I'm not too sure why. I'm pretty sure I'm missing something. The only conclusion I can come up with is that it is because I'm not using App from const app = express(); but I'm seeing it used elsewhere.
Any and all help/direction is appreciated
I'm getting the following:
res.send() is not a function
res.end() is not a function
res.json() is not a function
Anything that is dealing with res seems to be an issue.
Here is my code:
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp(functions.config().firebase);
const Chatkit = require('#pusher/chatkit-server');
const stripeToken = require('stripe')(functions.config().stripe.token);
const stripeWebhooks = require('stripe')(functions.config().keys.webhooks);
const express = require('express');
const cors = require('cors');
const endpointSecret = functions.config().keys.signing;
const request = require('request-promise');
const app = express();
app.use(cors({ origin: true }));
exports.stripeCreateOathResponseToken = functions.https.onRequest(cors((req, res) => {
const endpointSecret = "whsec_XXXXXXXXXXXXXXXXXXX";
// Get the signature from the request header
let sig = req.headers["stripe-signature"];
let rawbody = req.rawBody;
// res.send("testing res.send()"); // doesnt work. cant use res.send() here
console.log("rawbody: " + rawbody);
console.log("request.body: " + req.body);
console.log("request.query.code: " + req.query.code);
console.log("request.query.body: " + req.query.body);
console.log("request.query.state: " + req.query.state);
// console.log("res.body: " + res.json({received: true}));
stripeWebhooks.webhooks.constructEvent(req.rawBody, sig, endpointSecret);
res.end("testing res.end()"); // doesnt work. cant use res.end() here
}));

That doesn't look like the correct way to use the cors module with Cloud Functions for Firebase. Try this instead:
exports.stripeCreateOathResponseToken =
functions.https.onRequest((req, res) => {
return cors(req, res, () => {
// put your function code here
});
});
Cribbed from an official sample.

Related

Store webhooks functions inside a master function

I have set up multiple webhook endpoints that follow the similar structure in Node.js: 1) stringify the JSON request and 2) do something about that stringified request in R.
This consequently leads to a lot of duplicate code, and I attempt to stay DRY by creating a function in which I specify the arguments that actually do change. Here's an example.
First, the top portion of the script:
require("dotenv").config();
const express = require("express");
const app = express();
const port = 3000;
app.use(express.json());
Then, the part I would like to rewrite into a master function (WH) in which everything capitalized below between < and > becomes the argument.
app.post(<ENDPOINT>, foobar);
function foobar(req, res) {
var spawn = require("child_process").spawn;
let body = JSON.stringify(req.body);
var R_shell = spawn("/usr/local/bin/Rscript", [<PATH_TO_SCRIPT>, body]);
res.end("Processing completed");
}
app.get(<ENDPOINT>, (req, res) => res.send(`<html><body><h1>All working!</h1></body></html>
`));
Hence, with two endpoints, I'd end up with:
require("dotenv").config();
const express = require("express");
const app = express();
const port = 3000;
app.use(express.json());
WH(endpoint="foo", script_path="baz")
WH(endpoint="lorem", script_path="dolor")
P.S. Sorry if this is poorly formulated question from a Node.js standpoint—it's my first time developing with Node.js.
If I understood your question correctly, what you can do is something like this:
Firstly, you need to create a function that returns a router with the specified routes (You could create this function in a different file to make the code cleaner).
const {Router} = require('express')
function WH(endpoint, scriptPath) {
const router = Router()
function fn(req, res) {
var spawn = require("child_process").spawn;
let body = JSON.stringify(req.body);
var R_shell = spawn("/usr/local/bin/Rscript", [scriptPath, body]);
res.end("Processing completed");
}
router.post(endpoint, fn);
router.get(endpoint, (req, res) => res.send(`<html><body><h1>All working!</h1></body></html>`));
return router
}
And finally you should use it like this:
require("dotenv").config();
const express = require("express");
const app = express();
const port = 3000;
app.use(express.json());
app.use(WH("/foo", "./baz"))
app.use(WH("/lorem", "./dolor"))

Cannot set headers after they are sent to the client?

Here I'm trying to GET data from server, But when I try to open the browser and get the data nothing appear, Its give me the same URL in the browser.
UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]:
// Application Dependencies
require('dotenv').config();
const express = require('express');
const cors = require('cors');
const superAgent = require('superagent');
// Application Setup
const PORT = process.env.PORT || 3000;
const app = express();
app.use(cors());
//KEYS
const WEATHER_API_KEY = process.env.WEATHER_API_KEY;
const GEOCODE_API_KEY = process.env.GEOCODE_API_KEY;
const PARK_KEY = process.env.PARK_KEY;
//Route Definitions
app.get('/location', locationHandler);
app.get('/weather', weatherHandler);
app.get('/parks', parksHandler);
app.get('*', errorHandler);
//Location Handler
async function locationHandler(req, res) {
try {
console.log(req.query);
let getCity = req.query.city;
let url = `https://us1.locationiq.com/v1/search.php?key=pk.9e079e96352c63d18cf387532fa6b9ad&q=seattle&format=json`;
const locationData = await superAgent.get(url);
const apiData = JSON.parse(locationData.text);
console.log(superAgent.get(url))
res.send(superAgent.get(url));
// let aaa = new City(getCity, apiData[0].display_name, apiData[0].lat, apiData[0].lon);
// console.log(aaa);
res.status(200).send(new City(getCity, apiData[0].display_name, apiData[0].lat, apiData[0].lon));
} catch (error) {
res.status(404).send('Something went wrong in LOCATION route')
}
}
The response object does not batch an entire response necessarily, and might start sending it as available. HTTP requires that headers are written before anything else. Once anything else is written, it becomes impossible to send headers, such as changing the status code.
You might refactor the code to send the status first:
res.status(200)
res.send(superAgent.get(url));
res.send(new City(getCity, apiData[0].display_name, apiData[0].lat, apiData[0].lon));
} catch (error) {
You cannot send a response more then once.
So remove this line: res.send(superAgent.get(url));

Retrieve particular values from the POST endpoint in Express

I have an API definition /task/{activityId}?status={status} ( method = POST)
Input -> activityId, status
Output -> status
In Express I have written my code like this for debugging purpose -
const express = require("express");
const app = express();
const cors = require("cors");
const pool = require("./db");
const axios = require('axios');
app.use(cors());
app.use(express.json());
app.post("/task/:activityId?status=:status", async (req, res) => {
try {
var activityId = req.params.activityId;
var status = req.params.status;
console.log(status);
console.log(activityId);
if (status == "COMPLETE")
const updateStatus = await pool.query("update public.\"TableOne\" set \"Status\"='COMPLETE' where \"ActivityId\"='" + activityId + "'");
}
catch (err) {
console.error(err.message);
}
})
app.listen(5000, () => {
console.log("server has started on port 5000");
})
I am not able to see the values in console of activity id and status passed when I am hitting the endpoint from postman with something like this -
[POST] http://hostname:port/task/A1?status=PENDING
What mistake am I making here?
In order to get values from parameter, proper way is like this
console.log(req.params.status);
But secondary parameter named status is stated as querystring parameter, So, you need to fetch like this,
console.log(req.query.status);
Also, you don’t need to mention status in the code, so, your code to fetch the param should be like this:
app.post("/task/:activityId", async (req, res) => {
As you can see, I didn’t mention the status parameter. Still I will get it.

ExpressJS application to get Instagram media works on locahost and not on Firebase Functions

I will explain the problems in more detail.
I'm trying to do a really simple little practice. Through a link, the ID of an Instagram post is passed as a parameter.
After this, '? __ a = 1' is added to the URL for subsequent data processing once the JSON object has been obtained.
On localhost this works fine and I get the JSON object. But once deployed as a function in Firebase, although it accepts the request without errors, it does not return a JSON object. Instead it returns HTML content, like this:
"<!DOCTYPE html>\n<html lang=\"en\" class=\"no-js not-logged-in client-root\">\n <head>\n <meta charset=\"utf-8\">\n <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge\">\n\n <title>\nLogin • Instagram\n</title>\n\n
....
Does anyone know what is causing this problem?
...
Localhost:
Deployed like a Firebase Function:
I briefly attach the code of my application.
index.js
const functions = require('firebase-functions');
const server = require("./src/server");
const d = functions
.runWith({
memory: "2GB",
timeoutSeconds: 120
})
.https
.onRequest(server);
module.exports = {d};
server/index.js
const express = require("express");
const bodyParser = require("body-parser");
const cors = require("cors");
const app = express();
app
.use(cors({origin: true}))
.use(bodyParser.json())
.use(bodyParser.urlencoded({extended: false}))
.use("/", require("./route"))
.get('*', (_, res) =>
res.status(404)
.json({success: false, data: "Endpoint not found"}));
module.exports = app;
server/route.js
const router = require("express").Router();
const controller = require("./controller");
router.get("/:url", controller.getImageVideo);
module.exports = router;
server/controller.js
'use strict'
const axios = require('axios');
async function getImageVideo(req, res) {
const URL_id = req.params.url;
const URL = 'https://www.instagram.com/p/' + URL_id + '?__a=1';
const metadata = await axios.get(URL);
const test = metadata.data;
return res
.json({
test
});
}
module.exports = {
getAllLanguages,
getImageVideo
}

Client side CORS error with cloud functions and Firebase

I am new to firebase and wanting to use their HTTP cloud functions. When I try to go to any endpoint (not using the emulator - eg https://us-central1-xxxxxxxx.cloudfunctions.net/app/api/admin/studio) I am getting CORS errors.
Here is an example of my code. I have no security rules set currently (still in development). The code below works perfect when using the firebase emulator. Just not using the "live" .cloudfunctions link.
Is there a permission or set up I am missing? This endpoint works if you access this directly via the browser or POSTMAN, just not from the react app.
An example of the error from the react app is:
Origin https://xxxxxxxxxxx is not allowed by Access-Control-Allow-Origin.
Fetch API cannot load https://us-central1-xxxxxxxx.cloudfunctions.net/app/api/admin/studio due to access control checks.
index.js
const functions = require("firebase-functions")
const express = require("express")
const app = express()
const cors = require("cors")({ origin: true })
app.use(cors)
const studio = require("./http/studio.js")
app.post("/api/admin/studio", studio.add)
exports.app = functions.https.onRequest(app)
db.js
const admin = require("firebase-admin")
admin.initializeApp()
module.exports = admin.firestore()
http/studio.js
const db = require("../db")
const error = require("./error")
exports.add = (req, res) => {
const { data } = req.body
if (!data) {
return error.no_data(res)
}
return db
.collection("data")
.doc("studio")
.update({ values: admin.firestore.FieldValue.arrayUnion(data) })
.then(res.status(200).send("Data updated successfully"))
.catch(err => error.updating(res, err))
}
I was able to fix with updating my index.js code to include a list of allowed origins.
const functions = require("firebase-functions")
const express = require("express")
const app = express()
const cors = require("cors")({ origin: true })
var allowedOrigins = ["https://domainone", "https://domaintwo"]
app.use(
cors({
origin: function(origin, callback) {
if (!origin) return callback(null, true)
if (allowedOrigins.indexOf(origin) === -1) {
var msg = "The CORS policy for this site does not " + "allow access from the specified Origin."
return callback(new Error(msg), false)
}
return callback(null, true)
}
})
)
const studio = require("./http/studio.js")
app.post("/api/admin/studio", studio.add)
exports.app = functions.https.onRequest(app)

Resources