I am trying to consume an external api with Firebase Functions, but it gives a time out error when I use OPTIONS, when I use GET to work normally.
I don't want to use const request = require('request'); or var rp = require('request-promise');, as they are obsolete.
What may be wrong, I await help from colleagues.
const express = require('express');
const cors = require('cors');
const app = express();
// Permitir solicitações de origem cruzada automaticamente
app.use(cors({
origin: true
}));
//app.use(cors());
app.get('/criarcliente', (req, res) => {
let uri = "https://api.iugu.com/v1/customers?api_token=<mytoken>";
let headers = {
'Content-Type': 'application/json'
}
let body = {
custom_variables: [{
name: 'fantasia',
value: 'Dolci Technology'
}, {
name: 'vendedor',
value: ''
}],
email: 'teste1#teste1.com',
name: 'John Dolci',
phone: 9999999,
phone_prefix: 66,
cpf_cnpj: '00000000000',
cc_emails: 'test#test.com',
zip_code: '78520000',
number: '49',
street: 'Name Street',
city: 'Guarantã do Norte',
state: 'MT',
district: 'Jardim Araguaia'
}
var options = {
method: 'POST',
uri: uri,
body: body,
headers: headers,
json: true
};
const https = require('https');
var req = https.request(options, (resp) => {
let data = '';
resp.on('data', (chunk) => {
data += chunk;
});
resp.on('end', () => {
var result = JSON.parse(data);
res.send(result);
});
console.log("aqui");
}).on("error", (err) => {
console.log("Error: " + err.message);
});
}); ```
There are two points that I would point out to your case. First, you need to confirm that you are using the Blaze plan in your billing. As clarified in the official pricing documentation, in case you are not and are trying to use a non Google-owned service, you won't be able to do it and this will cause an error.
The second point would be to use the axios library within your application. It's the one that I believe to be most used with Node.js to allow you access to third-party application within Cloud Functions - I would say that it's pretty easy to use as well. You should find a whole example on using it with third-party API here.
To summarize, take a look at your pricing, as it's the most common issue to be caused and give it a try with axios, to confirm that you can avoid the error with it.
Related
I am currently working on social media mern stack react app. I am using node js and express as my backend services , also using mongoose to store my data and axios and redux thunk which connect the backend to the front end. Till now I had no issue recieving and sending data to the server. Right now I am trying to create search post get request ,base on a keyword the user entered. The issue with it, that when I am sending the keyword to the server instead of recieving the string it gets undefined value, like redux thunk not sending anything. I will be very thankful if someone could help me with that. I am watching the code over and over again and can't find out the reason for that.
My post controller class(I copied only the relevant function):
import express from "express";
const app = express();
import Post from "../model/PostModel.js";
import ErrorHandlng from "../utilities/ErrorHandling.js";
import bodyParser from "body-parser";
import catchAsync from "../utilities/CatchAsync.js";
import User from "../model/UserModel.js";
app.use(express.json());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(bodyParser.json());
export const getPosts = catchAsync(async (req, res, next) => {
const data = req.body.keyword;
const page = parseInt(req.query.page || "0");
const PAGE_SIZE = 20;
const query = new RegExp(data, "i");
const total = await Post.countDocuments({});
const posts = await Post.find({ $or: [{ title: query }, { content: query }] })
.limit(PAGE_SIZE)
.skip(PAGE_SIZE * page);
if (!posts) {
return next(new ErrorHandlng("No posts were found", 400));
}
res.status(200).json({
status: "success",
data: {
totalPages: Math.ceil(total / PAGE_SIZE),
posts,
},
});
});
My api class(front end,copied only the calling for that specific get request):
import axios from "axios";
const baseURL = "http://localhost:8000";
axios.defaults.withCredentials = true;
const API = axios.create({
baseURL,
credentials: "include",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
});
export const getPostsByKeyword = (keyword, page) =>
API.get(`/post/getPostsByKey?page=${page}`, keyword);
Post slice class:
export const fetchPostsByKeyWord = createAsyncThunk(
"post/getKeyword",
async ({ keyword, page }, { fulfillWithValue, rejectWithValue }) => {
try {
const response = await api.getPostsByKeyword(keyword, page);
if (response.statusCode === "400") {
throw new Error("There are no available posts");
}
const fetchData = await response.data.data.posts;
const totalPages = await response.data.data.totalPages;
return fulfillWithValue({ fetchData, totalPages });
} catch (err) {
console.log(err.response.message);
}
}
);
const initialState = { status: "undefined" };
const PostSlice = createSlice({
name: "post",
initialState,
reducers: {},
extraReducers: {},
});
export const postActions = PostSlice.actions;
export default PostSlice;
Calling the backend:
dispatch(fetchPostsByKeyWord({ keyword, page }))
.unwrap()
.then((originalPromiseResults) => {
console.log("thte " + " " + originalPromiseResults.totalPages);
console.log("The data is" + originalPromiseResults.fetchData);
setTotalPages(originalPromiseResults.totalPages);
})
.catch((err) => {
console.log(err.message);
});
As you can see I have not copied the whole code, I copied only the parts that are relevants for the question.
Browsers cannot currently send GET requests with a request body. XMLHttpRequest (which Axios uses) will ignore it and fetch() will trigger an error.
See also HTTP GET with request body for extra discussion on why trying this might be a bad idea.
You should instead pass everything required in the query string, preferably via the params option so it is correctly encoded...
export const getPostsByKeyword = (keyword, page) =>
API.get("/post/getPostsByKey", { params: { page, keyword } });
and grab the data via req.query server-side.
const { page, keyword } = req.query;
With vanilla JS, you can use URLSearchParams to construct the query string...
const params = new URLSearchParams({ page, keyword });
// XHR
const xhr = new XMLHttpRequest();
xhr.open("GET", `/post/getPostsByKey?${params}`);
// Fetch
fetch(`/post/getPostsByKey?${params}`); // GET is the default method
Your Axios instance creation could also be a lot simpler...
Axios is usually quite good at setting the correct content-type header, you don't have to
Your Express app isn't doing any content-negotiation so you don't need to set the accept header
Unless you're actually using cookies (which it doesn't look like), you don't need credential support
const API = axios.create({ baseURL });
this is my function which is triggered by a webhook using Axios. what it's doing is just bringing a response of all the payments in the system rather than adding a payment into the system.
Please assist.
// The Cloud Functions for Firebase SDK to create Cloud Functions and set up triggers.
const functions = require("firebase-functions");
// Get the default instance
const axios = require('axios')
var querystring = require("querystring");
var http = require("http");
// // Create and Deploy Your First Cloud Functions
// // https://firebase.google.com/docs/functions/write-firebase-functions
//
exports.afterPayments = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", { structuredData: true });
response.set("Access-Control-Allow-Origin", "*");
if (request.method === "OPTIONS") {
// Send response to OPTIONS requests
response.set("Access-Control-Allow-Methods", "GET");
response.set("Access-Control-Allow-Methods", "POST");
response.set("Access-Control-Allow-Headers", "Content-Type");
response.set("Access-Control-Max-Age", "3600");
response.status(204).send("");
} else {
}
console.log(request.body);
var secretHash =
"6*****";
let config = {
headers: {
Authorization:
'****** ',
}
}
const data = {
customer_id: request.body.data.customer.id,
payment_method: "20",
receipt_number: request.body.data.tx_ref,
date: request.body.data.created_at,
amount: request.body.data.amount,
field_1: "",
field_2: "",
field_3: "",
field_4: "",
field_5: "",
};
async function recordPayment() {
await axios
.post("http://********************/admin/finance/payments", data,config)
.then((response) => {
console.log(`Status: ${response.status}`);
console.log("Body: ", response.data);
response.send("Hello from Firebase!");
})
.catch((err) => {
console.error(err);
response.send(err);
});
}
recordPayment();
});
If I use my application using the same code, it's running properly, but when I use the cloud function it's not going through.
I am seeing the same issue when I use postman to make the same request, it does the same thing as that cloud function.
is there somethingn that i am missing on the server to server POST request.
As the title says my fetch isn't doing what I want it to. Im trying to implement stripe into my grocery store website. I'm using a node.js server with express, and ejs to integrate with the front end. My client side js uses a fetch method to send a POST request to the server with all the information from the client side. The client side pulls data from a JSON file to access the store items. Those items are to be restructured as objects in the fetch and sent to the server to initiate the stripe checkout.
However, the fetch fails at the get go with a status 500. It claims that an unidentified was passed instead of a JSON. As a result, I tried to send back a hard coded object to see where the error was occurring but it also came back as undefined. I'm stumped and need any help I can get.
I'm new at coding/programming so I'm sure there is a lot wrong with my code. Thank you for your time. My code is below.
Client side JS
let payButton = document.getElementsByClassName("pay")[0].addEventListener("click", function() {
// alert("Payment recieved");
// let totalItemsInCart = document.getElementsByClassName("shopping-cart-item-div");
//
// let x = 0;
//
// while (x < totalItemsInCart.length){
// totalItemsInCart[x].remove();
// }
// updateCartTotal();
let items = [];
let cartCollection = document.getElementsByClassName("shopping-cart-basket")[0];
let cartItems = cartCollection.getElementsByClassName("shopping-cart-items");
for (let x = 0; x < cartItems.length; x++) {
let cartItem = cartItems[x];
let cartItemQuantity = cartItem.parentElement.getElementsByClassName("shop-item-input")[0];
let quantity = cartItemQuantity.value;
let id = cartItem.parentElement.dataset.itemId;
let nameText = cartItem.innerText;
let name = nameText.replace(/per lb|per item|per bundle/g, "").replace("$", "").replace(":", "");
let cartTotal = document.getElementsByClassName("shopping-cart-number")[0].innerText;
let price = parseFloat(cartTotal.replace("$", "")) * 100;
items.push({
id: id,
quantity: quantity,
name: name,
price: price
});
}
fetch("/create-checkout-session", {
method: "POST",
header: {
"Content-Type": "application/json"
},
body: JSON.stringify({
id: 1,
quantity: 2,
name: "test",
price: 500})
}).then(function(res) {
if (res.ok) {
return res.json();
} else {
return res.json().then(function(json) {
Promise.reject(json)
});
}
}).then(({url}) => {
console.log();
window.location = url;
}).catch(function(e) {
console.error("Error: " + e.error)
});
});
Sever side JS
app.post("/create-checkout-session", async function(req, res) {
try {
const session = await stripe.checkout.sessions.create({
payment_method_types: ['card'],
mode: 'payment',
line_items: req.body.items.map(function(item) {
return {
price_data: {
currency: 'usd',
product_data: {
name: item.name
},
unit_amount: item.price
},
quantity: item.quantity
}
}),
success_url: `${process.env.SERVER_URL}/success.ejs`,
cancel_url: `${process.env.SERVER_URL}/cancel.ejs`
})
res.json({
url: session.url
})
res.redirect(303, session.url)
} catch (e) {
res.status(500).json({
error: e.message
})
}
});
app.get("/success", function(req, res) {
res.render('success');
});
app.get("/cancel", function(req, res) {
res.render('cancel');
});
Server Side dependencies
require("dotenv").config();
const express = require('express');
const ejs = require('ejs');
const bodyParser = require('body-parser');
const path = require('path');
const fs = require('fs');
const app = express();
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(bodyParser.json());
app.use(express.static('public'));
app.set('view engine', 'ejs');
EDIT: included my entire client side code for the checkout button. I originally left it out due to the code not being called anymore as I try to resolve the undefined issue. However, I got a few comments mentioning that items was not defined so I decided to add this in for clarity.
items is an empty array that has has objects pushed into it according to what ids were left in the shopping cart.
From MDN Docs
The Promise returned from fetch() won’t reject on HTTP error
status even if the response is an HTTP 404 or 500. Instead, as soon as
the server responds with headers, the Promise will resolve normally
(with the ok property of the response set to false if the response
isn’t in the range 200–299), and it will only reject on network
failure or if anything prevented the request from completing.
So what's happening here is that you're getting an error 500, so property res.ok is set to false, then it's not going through the conditional hence it's going to this part
return res.json().then(function(json) {
Promise.reject(json)
});
but you're not returning Promise.reject(json) because you're using an anonymous function, not an arrow function so you're returning undefinded, you must explicitly return the promise like this
return res.json().then(function(json) {
return Promise.reject(json)
});
^ Same goes for every anonymous function (you must return explicitly), instead of doing that I recommend you to use arrow functions and async-await
I looked at other answers for people who had similar issues, but I'm still a bit green in all this, so maybe I didn't understand or implement the solutions to similar issues correctly.
This is a Node/Express app that I'm trying to practice on, where I use an API to get the longitude and latitude from a search field, then use that API response data to use another API that uses longitude and latitude to get weather data. I have another app where I statically coded the latitude and longitude, and everything works fine. But, I do have nested API calls, and the post request that is new.
I am getting this error though, "Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client" when I try and render the view. When I tested and removed the second nested API call, it worked, or if I took the second res.render out, it seemed to work.
I don't know if just what I'm doing is impossible, or if, like I said, similar issues people have had, I'm just not implementing any solutions correctly.
Ideally, I need to get the data from the nested API call, the "weatherDataResponse", data to the res.render view.
I do have it working by itself, but when I introduced the /post and top level API call, it doesn't work.
Code:
function latLongInfo(results) {
let obj = {
method: 'GET',
url: 'https://forward-reverse-geocoding.p.rapidapi.com/v1/forward',
params: {
street: '',
city: results.city,
state: results.state,
postalcode: results.zip,
country: 'USA',
'accept-language': 'en',
polygon_threshold: '0.0'
},
headers: {
'x-rapidapi-key': rapidApiKey,
'x-rapidapi-host': 'forward-reverse-geocoding.p.rapidapi.com'
}
}
return obj
};
app.get('/', function(req, res) {
res.render('index', { title: 'idk' });
});
app.post('/getweather', (req, res, next) => {
const cityWeatherSearch = req.body.cityWeatherSearch
next();
axios.request(latLongInfo(parseLatLongSearch(cityWeatherSearch)))
.then(function (response) {
const getLatLongInfoResponse = response.data[0]
console.log(getLatLongInfoResponse)
if (Object.keys(getLatLongInfoResponse).length < 1) {
res.render('invalidweather', { title: 'Invalid Results' });
}
else {
let lat = getLatLongInfoResponse.lat, lon = getLatLongInfoResponse.lon;
let latLonString = `${lat},${lon}`;
const getWeatherInfo = axios.get(`https://api.pirateweather.net/forecast/${pirateWeatherKey}/${latLonString}`)
axios.all([getWeatherInfo]).then(axios.spread((...responses) => {
const weatherDataResponse = responses[0].data;
console.log(weatherDataResponse)
res.render('weather', { title: 'Weather Results' });
})).catch(errors => {
console.error(errors);
})
}
})
.catch(function (error) {
console.log(error)
})
res.end()
})
Try something like this. It looks like the next() call is causing the issues here. I also refactored the async stuff a little so it's more readable.
function latLongInfo(results) {
let obj = {
method: 'GET',
url: 'https://forward-reverse-geocoding.p.rapidapi.com/v1/forward',
params: {
street: '',
city: results.city,
state: results.state,
postalcode: results.zip,
country: 'USA',
'accept-language': 'en',
polygon_threshold: '0.0'
},
headers: {
'x-rapidapi-key': rapidApiKey,
'x-rapidapi-host': 'forward-reverse-geocoding.p.rapidapi.com'
}
}
return obj
};
app.get('/', function(req, res) {
res.render('index', { title: 'idk' });
});
app.post('/getweather', async (req, res, next) => {
try {
const cityWeatherSearch = req.body.cityWeatherSearch;
const response = await axios.request(latLongInfo(parseLatLongSearch(cityWeatherSearch)));
const getLatLongInfoResponse = response.data[0]
console.log(getLatLongInfoResponse)
if (Object.keys(getLatLongInfoResponse).length < 1) {
res.render('invalidweather', { title: 'Invalid Results' });
return;
}
const lat = getLatLongInfoResponse.lat;
const lon = getLatLongInfoResponse.lon;
const latLonString = `${lat},${lon}`;
const getWeatherInfo = await axios.get(`https://api.pirateweather.net/forecast/${pirateWeatherKey}/${latLonString}`);
const weatherDataResponse = getWeatherInfo.data;
console.log(weatherDataResponse)
res.render('weather', { title: 'Weather Results' });
} catch (error) {
console.log(error);
}
});
People, how are you? I have a query, I just implemented my API made with apollo server in an AWS Lambda. I used the official documentation as a guide, but I'm noticing that the context handling varies a bit. I have a doubt with the latter, since I made certain changes and everything works fine locally using "serverless offline", but once I deploy it doesn't. Apparently the authentication context that I generate does not finish reaching my query. If someone can guide me a bit with this, I will be very grateful.
This is my API index:
const { ApolloServer, gql } = require('apollo-server-lambda');
const typeDefs = require('./db/schema');
const resolvers = require('./db/resolvers');
const db = require('./config/db');
const jwt = require('jsonwebtoken');
require('dotenv').config({ path: 'variables.env' });
db.conectDB();
// The ApolloServer constructor requires two parameters: your schema
// definition and your set of resolvers.
const server = new ApolloServer({
typeDefs,
resolvers,
playground: {
endpoint: "/graphql"
},
context: ({ event, context }) => {
try {
const token = event.headers['authorization'] || '';
if(token){
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);
}
return {
headers: event.headers,
functionName: context.functionName,
event,
context,
}
} catch (error) {
console.error(error);
}
}
});
exports.graphqlHandler = server.createHandler({
cors: {
origin: '*',
credentials: true,
},
});
This is my query:
getUserByToken: async (_, {}, { context }) => {
if(context)
throw new Error((context ? 'context' : '') + ' ' + (context.user ? 'user' : ''));
let user = await db.findOne('users',{ _id: ObjectId(context.user._id) });
if(user.birthdate)
user.birthdate = user.birthdate.toString();
if(user.password)
user.password = true;
else
user.password = false;
return user;
}
My API response:
API response
From what I can see, you're not calling getUserByToken in your context. Is that correct? So, I'm not sure how you're encountering this error.
Can I give you some pointers?
Connecting to your DB is probably (or it should be) asynchronous. For that, I'd run your code like this:
db.connect()
.then(() => {
... handle your request in here
})
.catch(console.error);
I think you meant to call your getUserByToken in this line:
context.user = jwt.verify(token.replace('Bearer ',''), process.env.KEY_TOKEN);