I have a relatively simple Firebase function that simply creates an https request to an API and then is supposed to do a couple writes to the database. However, even though it successfully completes the API call (I can see that in the debugger of the service), it always results in Error: could not handle request.
The Function
export const CreateNoonlightAlarm = functions.https.onRequest((request, response) => {
const options = {
host: noonlightURL,
path: "/dispatch/v1/alarms",
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${noonlightToken}`,
},
};
var req = https.request(options, (res) => {
res.on("data", (response) => {
let data = JSON.parse(response);
const docRef = database
.collection("systems")
.doc(request.body.owner_id)
.collection("alarms")
.doc(data.id);
docRef
.set(data, { merge: true })
.catch((error: any) => console.log("docRef:", error));
const referenceRef = database
.collection("alarmsToSystems")
.doc(data.id);
referenceRef
.set({
alarm_id: data.id,
system_id: request.body.owner_id,
})
.catch((error: any) => console.log("referenceRef:", error));
response.send(response);
});
});
req.on("error", (e) => {
response.send(e);
});
req.write(JSON.stringify(request.body));
req.end();
}
);
Now, I have been at this for a while. I've tried removing everything inside the res.on("data", (response) => {} block except for the response.send(response); and I still received the error. I've also seen that many people had issues with Firebase actually updating the function and said to run firebase init again prior to running firebase deploy and that still isn't working out for me. I've also console logged things like the request.body.owner_id and the data.id to ensure they are readable, and they are. Other functions in the same file run perfectly fine when called and return what they are expected to return.
Am I missing something in allowing for this function to successfully run?
So, after another hour of troubleshooting and digging in line by line I realized something. You can not name two things the same thing in the same function.... It never turns out well.
In line 1, I defined the second parameter in the OnRequest() as response.
In line 14, I again defined response as the parameter in res.on().
So, as I changed the response on line 14 to res and then each corresponding use of said variable, the function began to work exactly as it was expected to be. The updated code can be found below.
export const CreateNoonlightAlarm = functions.https.onRequest((request, response) => {
const options = {
host: noonlightURL,
path: "/dispatch/v1/alarms",
method: "POST",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
Authorization: `Bearer ${noonlightToken}`,
},
};
var req = https.request(options, (res) => {
res.on("data", (res) => {
let data = JSON.parse(res);
const docRef = database
.collection("systems")
.doc(request.body.owner_id)
.collection("alarms")
.doc(data.id);
docRef
.set(data, { merge: true })
.catch((error: any) => console.log("docRef:", error));
const referenceRef = database
.collection("alarmsToSystems")
.doc(data.id);
referenceRef
.set({
alarm_id: data.id,
system_id: request.body.owner_id,
})
.catch((error: any) => console.log("referenceRef:", error));
response.send(res);
});
});
req.on("error", (e) => {
response.send(e);
});
req.write(JSON.stringify(request.body));
req.end();
}
);
Related
I'm building a mobile app and on some actions (like userCreate), I trigger some Firebase functions to perform an API call to a third service, and then write something in the Firestore database.
Everything works in theory, but in practice, the API calls are quite fast (even with cold start scenarios), but the database writes can take several MINUTES to complete (if they do at all, I suspect that sometimes it takes too long and times out).
Since the API call work just fine, and so does the DB write on some occasion, I suspect that this is simply due to very poor async management from me since I know about nothing in JS.
Here are two of many example functions which are concerned by this issue, just to showcase that it happens whereas I'm triggering functions onCreate or on HTTPS.
onCreate function
const functions = require("firebase-functions");
const axios = require('axios')
// The Firebase Admin SDK to access the Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// Third party service credentials generation during onCreate request
exports.
buildCredentials = functions.auth.user().onCreate((user) => {
// Request to Third party to generate an Client Access Token
axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/oauth/token",
data: "client_id=xxx&client_secret=yyy&grant_type=client_credentials&scope=all:all",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
})
.then(function (response) {
//handle success
console.log('new user created: ')
console.log(user.id);
console.log(response.data);
// We write a new document in the users collection with the user ID and the Client Access Token
const db = admin.firestore();
const newUser = {
uid: user.uid,
clientAccessToken: response.data.access_token
};
db.collection('users').doc(user.uid).set(newUser)
.catch(function (error) {
//handle error
console.log(error);
});
})
.catch(function (response) {
//handle error
console.log(response);
});
})
HTTPS onCall function
exports.paymentRequest = functions.https.onCall(async (data, context) => {
const clientAccessToken = data.clientAccessToken;
const recipientIban = data.recipientIban;
const recipientName = data.recipientName;
const paymentDescription = data.paymentDescription;
const paymentReference = data.paymentReference;
const productPrice = parseInt(data.productPrice);
const uid = data.uid;
const jsonData = {
"destinations": [
{
"accountNumber": recipientIban,
"type": "iban"
}
],
"amount": productPrice,
"currency": "EUR",
"market": "FR",
"recipientName": recipientName,
"sourceMessage": paymentDescription,
"remittanceInformation": {
"type": "UNSTRUCTURED",
"value": paymentReference
},
"paymentScheme": "SEPA_INSTANT_CREDIT_TRANSFER"
};
(async function(){
response = await axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/pay",
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${clientAccessToken}`,},
data: jsonData,
})
// We write a the payment request ID in the user's document
const db = admin.firestore();
const paymentRequestID = response.data.id;
db.collection('users').doc(uid).set({
paymentRequestID: paymentRequestID
}, { merge: true })
.catch(function (error) {
//handle error
console.log(error);
});
console.log(response.data)
return response.data
})()
})
Am I on the right track thinking that this is an async problem?
Or is it a Firebase/Firestore issue?
Thanks
You are not returning the promises returned by the asynchronous methods (axios() and set()), potentially generating some "erratic" behavior of the Cloud Function.
As you will see in the three videos about "JavaScript Promises" from the official Firebase video series you MUST return a Promise or a value in a background triggered Cloud Function, to indicate to the platform that it has completed, and to avoid it is terminated before the asynchronous operations are done or it continues running after the work has been completed.
The following adaptations should do the trick (untested):
onCreate Function:
buildCredentials = functions.auth.user().onCreate((user) => {
// Request to Third party to generate an Client Access Token
return axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/oauth/token",
data: "client_id=xxx&client_secret=yyy&grant_type=client_credentials&scope=all:all",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
})
.then(function (response) {
//handle success
console.log('new user created: ')
console.log(user.id);
console.log(response.data);
// We write a new document in the users collection with the user ID and the Client Access Token
const db = admin.firestore();
const newUser = {
uid: user.uid,
clientAccessToken: response.data.access_token
};
return db.collection('users').doc(user.uid).set(newUser)
})
.catch(function (response) {
//handle error
console.log(response);
return null;
});
})
Callable Function:
exports.paymentRequest = functions.https.onCall(async (data, context) => {
try {
const clientAccessToken = data.clientAccessToken;
const recipientIban = data.recipientIban;
const recipientName = data.recipientName;
const paymentDescription = data.paymentDescription;
const paymentReference = data.paymentReference;
const productPrice = parseInt(data.productPrice);
const uid = data.uid;
const jsonData = {
// ...
};
const response = await axios({
method: "post",
url: "https://api.ThirdParty.com/api/v1/pay",
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
Authorization: `Bearer ${clientAccessToken}`,
},
data: jsonData,
})
// We write a the payment request ID in the user's document
const db = admin.firestore();
const paymentRequestID = response.data.id;
await db.collection('users').doc(uid).set({
paymentRequestID: paymentRequestID
}, { merge: true })
console.log(response.data)
return response.data
} catch (error) {
// See https://firebase.google.com/docs/functions/callable#handle_errors
}
})
I'm trying to build a basic login page with a dashboard using Express server and Nextjs. After the user logs in with proper credentials, they are authenticated and then redirected to the dashboard... Or that's what's supposed to happen at least. It seems that when Router.push("/Dashboard") is called, an improper request is made. However, if I just type http://localhost:3000/Dashboard into the address bar it works.
Get dashboard route
server.get('/Dashboard', checkSignIn, (req, res) => {
console.log("In dashboard")
if(req.session.page_views){
req.session.page_views++;
} else {
req.session.page_views = 1;
}
console.log("Page views: ", req.session.page_views)
return handle(req, res)
})
Log in and redirect from client side
const router = useRouter();
const attemptLogin = async (event: KeyboardEvent) => {
const username: string = event!.target!.username!.value;
const password: string = event!.target!.password!.value;
fetch("http://localhost:3000/SignIn", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
})
.then((res) => {
if (res.status === 200) {
console.log("Status is 200");
return router.push("/Dashboard");
}
})
.catch((err) => console.log("err is", err));
};
Here is what the request looks like when I manually type http://localhost:3000/Dashboard into the address bar
And here is what the request looks like when router.push is called
Hope someone can help with this. Thanks.
Edit: I get these errors (and output) in the console while rediredirecting
So I figured out the issue. It's because I was submitting a form without calling event.preventdefault(), which I think was making an improper fetch request (hence the error above), as well as reloading the page. The new working code for attemptLogin (the function I call on form submit) is
const attemptLogin = async (event: KeyboardEvent) => {
event.preventDefault();
const username: string = event!.target!.username!.value;
const password: string = event!.target!.password!.value;
fetch("http://localhost:5000/SignIn", {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
})
.then((res) => {
if (res.status === 200) {
console.log("Status is 200");
return router.push("/Dashboard");
}
})
.catch((err) => {
console.log("err is", err);
event!.target!.reset();
});
};
I am trying to import a function from an external file I have, which contains a promise.
The resolution of the promise is what should be returned, but I get an error message:
The requested module './functions.js' does not provide an export named 'getOkapiToken'
The POST request works fine when I run it directly in the server, but I need it in a separate file as it will be used by several different files in the future.
This is also the first time I'm using promises, so I'm not sure I handled it properly (though I can't get any errors registered until I deal with the first one).
The functions.js file is built as follows:
import post from 'axios';
export function getOkapiToken(url, user, password) {
//Get username and password for API
const auth = {
"username": user,
"password": password
};
//Create headers for POST request
const options = {
method: 'post',
data: auth,
headers: {
'Content-Type': 'application/json',
'Content-Length': auth.length,
'X-Okapi-Tenant': 'diku'
}
};
//Make API post call
post(url+':9130/authn/login', options)
.then(response => {
return(response.headers['x-okapi-token'])
}).catch((err) => {
return(`There was an error 2: ${err}`)
})
}
And I try to import it as follows:
import { getOkapiToken } from './functions3.js'
import settings from './jsons/config.json';
let OkapiKey = await new Promise((resolve,reject) => {
//Call function to make API post call
let keytoken = getOkapiToken(settings.url,settings.userauth,settings.passauth)
console.log(`There was an error: ${keytoken}`)
if (keytoken.length == 201) {
resolve(keytoken)
} else {
reject('There was an error')
}
})
OkapiKey.then((data) => {
console.log(data)
})
.catch((err) => {
console.error('I have an error:'+err.code);
})
There are three ways to handle asynchronous task in Javascript and wit Node.JS
Pass in a Callback to run in the asynchronous code
Use a Promise that will either resolve or reject the promise
Use the async keyword in front of the function and place await in front of the asynchronous code.
With that said I was able to get the code to work by running a simple node server and I modified your code just a bit to get a response back from the server.
index.js
const { getOkapiToken } = require('./functions.js')
const settings = require('./settings.json')
var OkapiKey = getOkapiToken(settings.url,settings.userauth,settings.passauth)
OkapiKey
.then((data) => {
console.log('I have data'+ data.toString())
})
.catch((err) => {
console.error('I have an error:'+err.code);
})
functions.js
const post = require('axios');
const getOkapiToken = (url, user, password) =>
new Promise(function (resolve, reject) {
//Get username and password for API
const auth = {
"username": user,
"password": password
};
//Create headers for POST request
const options = {
method: 'post',
data: auth,
headers: {
'Content-Type': 'application/json',
'Content-Length': auth.length,
'X-Okapi-Tenant': 'diku'
}
};
post('http://localhost:3000/', options)
.then(response => {
resolve(response.data)
// if (response.headers['x-okapi-token']) {
// resolve(response.headers['x-okapi-token']);
// } else {
// reject((error));
// }
}).catch((err) => {
console.error('Response Error:'+err)
})
})
exports.getOkapiToken = getOkapiToken;
My NodeJS controller is sending the response before finish running the wait methods.
The response with the 'OK' message is immediately send back to the client (browser) when I run it. It sends the response before running all the methods.
try {
const ep = await new keysModel().getKeys(); // key is a list of objects
await ep.map( async (key) => {
// some operations with the keys here
// call a fetch to a third party API
await fetch(urlRequest, {
method: 'post',
body: JSON.stringify(myJSONObject),
headers: {
'Authorization': `Basic ${base64.encode(`${key.APIKey}:${key.PasswordKey}`)}`,
'Content-Type': 'application/json'
},
})
.then(async res => res.json())
.then(async json => {
// Here I have some operations with the json response data
// It includes some database queries
})
.catch(async err => {
// await method to send me an email
res.status(501).send('error');
});
});
res.status(202).send('OK');
} catch() {
// await method to send me an email
res.status(501).send('error');
}
Below is the route:
const express = require('express');
const router = express.Router();
router.get('/myPath', myController.testMethod);
I'm new with NodeJS. Trying to understand how it works.
Thanks
I think Promise.all should work, eg. use async await with array map :)
try {
const ep = await new keysModel().getKeys(); // key is a list of objects
await Promise.all(ep.map(async (key) => {
// some operations with the keys here
// call a fetch to a third party API
return fetch(urlRequest, {
method: 'post',
body: JSON.stringify(myJSONObject),
headers: {
'Authorization': `Basic ${base64.encode(`${key.APIKey}:${key.PasswordKey}`)}`,
'Content-Type': 'application/json'
},
})
.then(async res => res.json())
.then(async json => {
// Here I have some operations with the json response data
// It includes some database queries
})
.catch(async err => {
// await method to send me an email
res.status(501).send('error');
});
}));
res.status(202).send('OK');
} catch() {
// await method to send me an email
res.status(501).send('error');
}
By default, map is not async function, using async on map's call back would not be asynced.
you can use one of Promise.all, Promise.allSettled, Promise.any, Promise.race (depends on your needs, please read more about Promises here.
short example for your case, i will use Promise.all for the example.
try {
const ep = await new keysModel().getKeys(); // key is a list of objects
Promise.all(
ep.map((key) => {
// some operations with the keys here
// call a fetch to a third party API
fetch(urlRequest, {
method: 'post',
body: JSON.stringify(myJSONObject),
headers: {
'Authorization': `Basic
${base64.encode(`${key.APIKey}:${key.PasswordKey}`)}`,
'Content-Type': 'application/json'
},
})
.then(/* Handle array of responses */)
.catch(/* Handle errors */);
);
// proceed..
} catch() {
// await method to send me an email
res.status(501).send('error');
}
I am trying to make my server a middleman so that the frontend queries the server with a searchedValue, the server queries the API with the searchedValue, and the server returns the API response to the frontend.
Currently, the server is querying the API correctly. Here is the code and responses:
Query: http://localhost:3000/optionsAPI/AAPL
Code [server.js]:
app.get("/optionsAPI/:ticker", (req, res) => {
var tempJSON = [];
const searchString = `${req.params.ticker}`;
const url = `API URL HERE, HIDING FOR SECURITY`;
fetch(url, { headers: { Accept: 'application/json' } })
.then(res => res.json()
.then((json) => {
tempJSON = json;
console.log(tempJSON);
}))
.catch(err => console.error(err)); // eslint-disable-line
res.send({ message: tempJSON });
});
Here is the code in the component:
Code [Component.js]:
useEffect(() => {
const fetchData = () => {
const url = `/optionsAPI/${searchedValue}`;
fetch(url, { headers: { Accept: 'application/json' } })
.then(res => res.json()
.then((json) => {
setOptions(json.option_activity || []);
}))
.catch(err => console.error(err)); // eslint-disable-line
};
debounce(fetchData());
}, [searchedValue]);
The console log is perfect! It logs tempJSON as I would expect to see it, but the res.send message is simply {"message":[]}. Therefore, the response my frontend gets is an empty []. This doesn't; make sense - the console is logging the response, so why is the frontend receiving a blank []?
Thanks in advance.
In your code you are calling an api which returns a promise, so to handle the data returned by the promise you should add your code inside .then() function, meaning you have to wait for the promise to be resolved before accessing the data and sending it to the client
app.get("/optionsAPI/:ticker", (req, res) => {
var tempJSON = [];
const searchString = `${req.params.ticker}`;
const url = `API URL HERE, HIDING FOR SECURITY`;
fetch(url, { headers: { Accept: 'application/json' } })
.then(res => res.json()
.then((json) => {
tempJSON = json;
console.log(tempJSON);
// the response should be sent from here
res.send({ message: tempJSON });
}))
.catch(err => {
console.error(err);
// you also need to send a response when catching erros
res.status(400).send({ err });
});
});
you can use async / await to make your code much cleaner
app.get("/optionsAPI/:ticker", async (req, res) => {
var tempJSON = [];
const searchString = `${req.params.ticker}`;
const url = `API URL HERE, HIDING FOR SECURITY`;
try {
const result = await fetch(url, { headers: { Accept: 'application/json' } })
const json = await result.json()
tempJSON = json;
console.log(tempJSON);
res.send({ message: tempJSON });
} catch (error) {
console.error(error);
res.status(400).send({ error });
}
});