Firebase functions - res.status is undefined - firebase

This is my code which I am executing:
exports.checkPin = functions.https.onCall(async (req, res) => {
let roomDoc = await db.collection('Rooms').where('roomNum', "==", req.roomNum).get();
if (roomDoc.exists) {
if (bcrypt.compareSync(roomDoc.data().pin, req.pin)) {
res.status(200).send("authorised");
} else {
res.status(401).send("unauthorised");
}
} else {
res.status(401).send("unauthorised");
}
});
After executing the code, it throws an error saying that res.status() is undefined
Am I doing something wrong? I want to be able to send a response HTTP code, once the firebase function has finished executing, if this isn't the right way to do it?

You are mixing up Callable Cloud Functions and HTTP Cloud Functions.
Doing res.status(XXX).send("..."); shall be done in an HTTP Cloud Function and not in a Callable one.
Also, note that roomDoc returns a QuerySnapshot and not a DocumentSnapshot.
So you should adapt your function as follows (making the assumption that the query will return only one document):
exports.checkPin = functions.https.onRequest(async (req, res) => { // <--- See here, we use onRequest and not onCall
const querySnapshot = await db.collection('Rooms').where('roomNum', "==", req.roomNum).get();
if (!querySnapshot.empty) {
const roomDoc = querySnapshot.docs[0];
if (bcrypt.compareSync(roomDoc.data().pin, req.pin)) {
res.send("authorised");
} else {
res.status(500).send("unauthorised");
}
} else {
res.status(500).send("unauthorised");
}
PS: you may watch this official video on HTTPS Cloud Functions: https://www.youtube.com/watch?v=7IkUgCLr5oA&t=1s&list=PLl-K7zZEsYLkPZHe41m4jfAxUi0JjLgSM&index=3

Related

Google Functions - httpCallable.onRequest display error message [duplicate]

I am trying to update some values that I will enter from my Flutter app to FireStore using Cloud Functions. Here is my code so far:
This is my Cloud function (in JavaScript, index.js) to update a document in FireStore:
exports.update = functions.https.onRequest((req, res) => {
const getNewPercentage = req.body;
const getDocument = admin.firestore().collection('waterpercentage').doc('percentage');
getDocument.get().then((doc) => {
if(doc.exists) {
getDocument.update({'percentage': getNewPercentage}).catch(err => {
console.log("Error: ", err);
res.send("500");
})
}
}).catch(err => {
console.log("Error: ", err)
res.send("500");
});
res.send(200);
})
In Flutter, here's what I tried:
Future<void> updateWaterPercentage() async {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('update');
//final results = await callable.call(<dynamic, int>{'percentage' : percentage.round()});
log("Calling percentage here: ");
log(percentage.round().toString());
dynamic resp = await callable.call(<double, dynamic> {
percentage : percentage.round(),
});
When I call updateWaterPercentage() from a Button in Flutter, the data doesn't get updated in FireStore. I also tried using:
CloudFunctions.instance.call(
functionName: "update",
parameters: {
"percentage": percentage.round(),
}
);
However, even though I imported 'package:cloud_functions/cloud_functions.dart'; on top, Flutter doesn't recognize CloudFunctions. How can I get the code to call update that takes in a parameter to correctly update a value in Firestore?
You are mixing Callable Cloud Functions and HTTPS Cloud Functions.
Your Cloud Function code corresponds to an HTTP one (functions.https.onRequest((req, res) => {(...)}) but the code in your app declares and calls a Callable one (HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('update');).
In addition, in your HTTPS Cloud Function code, you send back a response before the asynchronous operation is complete (see the last line res.send(200);).
So, to fix the problem:
You could call the HTTPS Cloud Function from your flutter app with the http package;
But the best would probably be to adapt your Cloud Function to be a Callable one in order to get the advantages of a Callable, including the use of the cloud_functions package which makes very easy to call the CF from your app. Something along the following lines.
exports.update = functions.https.onCall((data, context) => {
const getNewPercentage = data.percentage;
const documentRef = admin.firestore().collection('waterpercentage').doc('percentage');
return documentRef.get()
.then((doc) => {
if (doc.exists) {
return documentRef.update({ 'percentage': getNewPercentage });
} else {
throw new Error('Doc does not exist');
}
})
.then(() => {
return { result: "doc updated" };
})
.catch(err => {
console.log("Error: ", err)
// See the doc: https://firebase.google.com/docs/functions/callable#handle_errors
});
});

How to call Cloud Function from Firebase in Flutter?

I am trying to update some values that I will enter from my Flutter app to FireStore using Cloud Functions. Here is my code so far:
This is my Cloud function (in JavaScript, index.js) to update a document in FireStore:
exports.update = functions.https.onRequest((req, res) => {
const getNewPercentage = req.body;
const getDocument = admin.firestore().collection('waterpercentage').doc('percentage');
getDocument.get().then((doc) => {
if(doc.exists) {
getDocument.update({'percentage': getNewPercentage}).catch(err => {
console.log("Error: ", err);
res.send("500");
})
}
}).catch(err => {
console.log("Error: ", err)
res.send("500");
});
res.send(200);
})
In Flutter, here's what I tried:
Future<void> updateWaterPercentage() async {
HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('update');
//final results = await callable.call(<dynamic, int>{'percentage' : percentage.round()});
log("Calling percentage here: ");
log(percentage.round().toString());
dynamic resp = await callable.call(<double, dynamic> {
percentage : percentage.round(),
});
When I call updateWaterPercentage() from a Button in Flutter, the data doesn't get updated in FireStore. I also tried using:
CloudFunctions.instance.call(
functionName: "update",
parameters: {
"percentage": percentage.round(),
}
);
However, even though I imported 'package:cloud_functions/cloud_functions.dart'; on top, Flutter doesn't recognize CloudFunctions. How can I get the code to call update that takes in a parameter to correctly update a value in Firestore?
You are mixing Callable Cloud Functions and HTTPS Cloud Functions.
Your Cloud Function code corresponds to an HTTP one (functions.https.onRequest((req, res) => {(...)}) but the code in your app declares and calls a Callable one (HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('update');).
In addition, in your HTTPS Cloud Function code, you send back a response before the asynchronous operation is complete (see the last line res.send(200);).
So, to fix the problem:
You could call the HTTPS Cloud Function from your flutter app with the http package;
But the best would probably be to adapt your Cloud Function to be a Callable one in order to get the advantages of a Callable, including the use of the cloud_functions package which makes very easy to call the CF from your app. Something along the following lines.
exports.update = functions.https.onCall((data, context) => {
const getNewPercentage = data.percentage;
const documentRef = admin.firestore().collection('waterpercentage').doc('percentage');
return documentRef.get()
.then((doc) => {
if (doc.exists) {
return documentRef.update({ 'percentage': getNewPercentage });
} else {
throw new Error('Doc does not exist');
}
})
.then(() => {
return { result: "doc updated" };
})
.catch(err => {
console.log("Error: ", err)
// See the doc: https://firebase.google.com/docs/functions/callable#handle_errors
});
});

Setting up a firebase cloud function call

I am trying to set up cloud functions with firebase and I am having a slightly difficult time getting it set up.
I want to set up a function that gets called by an HTTP request. The function would take the information provided, double-check if those values are indeed the same values as the ones found in my firestorm
and then execute some Javascript code before responding; this is my code:
// The Cloud Functions for Firebase SDK to create Cloud Functions and setup triggers.
const functions = require("firebase-functions");
// The Firebase Admin SDK to access Firestore.
const admin = require('firebase-admin');
admin.initializeApp();
// [START trigger]
exports.buyCrypto = functions.https.onRequest((request, res) =>
{
// [END trigger]
// [START sendError]
// Forbidding PUT requests.
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
// [END sendError]
// [START readQueryParam]
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if(crypto === "BTC")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
if(crypto === "ETH")
{
if(doc.data.btc <= amount)
{
//execute buy
return res.status(200).send("Sucess");
}
}
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
}
}).catch((error) => {
console.log("Error getting document:", error);
});
// Push the new message into Firestore using the Firebase Admin SDK.
//const writeResult = await admin.firestore().collection('messages').add({original: original});
// Send back a message that we've successfully written the message
// [START sendResponse]
const formattedResponse = "IDK"
return res.status(403).send("Failed");
// [END sendResponse]
});
Unfortunatly I cannot seem to find a great deal of documentation for firebase functions and when I try to test it with the emulator through a web browser it goes into infinite loading and does not display an error message so I am finding it impossible to debug anything.
Thank you for your time
You are calling return res.status(403).send("Failed"); outside of the then() block, so this line will be called before the asynchronous call to the get() method is completed and the Promise returned by this method is fulfilled. Result: your Cloud Function always sends back an error to its caller.
In addition, you do doc.data.btc instead of doc.data().btc. See the doc for the DocumentSnapshot, data() is a method.
Also, note that you don't need to use return in an HTTPS Cloud Function. Just send back a response with res.redirect(), res.send(), or res.end(). You may watch this video: https://www.youtube.com/watch?v=7IkUgCLr5oA.
The following should therefore do the trick:
exports.buyCrypto = functions.https.onRequest((request, res) => {
if (request.method === 'PUT') {
return res.status(403).send('Forbidden!');
}
const uid = request.body.uid
const crypto = request.body.crypto
const amount = request.body.amount
const docRef = admin.firestore().collection("users").doc(uid);
docRef.get().then((doc) => {
if (doc.exists) {
if (crypto === "BTC") {
if (doc.data().btc <= amount) {
//execute buy
res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else if (crypto === "ETH") {
if (doc.data.btc <= amount) {
//execute buy
return res.status(200).send("Success");
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
// send a 200 response or throw an error res.status(200).send("....");
// Depends on your functional requirements
}
} else {
console.log("No such document!");
// send a 200 response or throw an error res.status(200).send("....");
}
}).catch((error) => {
console.log("Error getting document:", error);
res.status(500).send(error);
});
});

How to test `functions.https.onCall` firebase cloud functions locally?

I can't seem to find the solution for this in the Firebase Documentation.
I want to test my functions.https.onCall functions locally. Is it possible using the shell or somehow connect my client (firebase SDK enabled) to the local server?
I want to avoid having to deploy every time just to test a change to my onCall functions.
My code
Function :
exports.myFunction = functions.https.onCall((data, context) => {
// Do something
});
Client:
const message = { message: 'Hello.' };
firebase.functions().httpsCallable('myFunction')(message)
.then(result => {
// Do something //
})
.catch(error => {
// Error handler //
});
For locally you must call (after firebase.initializeApp)
firebase.functions().useFunctionsEmulator('http://localhost:5000')
Although the official Firebase Cloud Function docs have not yet been updated, you can now use firebase-functions-test with onCall functions.
You can see an example in their repository.
I have managed to test my TypeScript functions using jest, here is a brief example. There are some peculiarities here, like import order, so make sure to read the docs :-)
/* functions/src/test/index.test.js */
/* dependencies: Jest and jest-ts */
const admin = require("firebase-admin");
jest.mock("firebase-admin");
admin.initializeApp = jest.fn(); // stub the init (see docs)
const fft = require("firebase-functions-test")();
import * as funcs from "../index";
// myFunc is an https.onCall function
describe("test myFunc", () => {
// helper function so I can easily test different context/auth scenarios
const getContext = (uid = "test-uid", email_verified = true) => ({
auth: {
uid,
token: {
firebase: {
email_verified
}
}
}
});
const wrapped = fft.wrap(funcs.myFunc);
test("returns data on success", async () => {
const result = await wrapped(null, getContext());
expect(result).toBeTruthy();
});
test("throws when no Auth context", async () => {
await expect(wrapped(null, { auth: null })).rejects.toThrow(
"No authentication context."
);
});
});
There is a simple trick, how you can simplify onCall -function testing. Just declare the onCall function callback as a local function and test that instead:
export const _myFunction = (data, context) => { // <= call this on your unit tests
// Do something
}
exports.myFunction = functions.https.onCall(_myFunction);
Now you can variate all cases with a normal function with the input you define on your function call.
Callables are just HTTPS functions with a specific format. You can test just like a HTTPS function, except you have to write code to deliver it the protocol as defined in the documentation.
you should first check for dev environment and then point your functions to local emulator.
For JS:
//after firebase init
if (window.location.host.includes("localhost") ||
window.location.host.includes("127.0.0.1")
) {
firebase
.app()
.functions() //add location here also if you're mentioning location while invoking function()
.useFunctionsEmulator("http://localhost:5001");
}
or if you don't create instance of firebase then
//after firebase init
if (window.location.host.includes("localhost") ||
window.location.host.includes("127.0.0.1")
) {
firebase
.functions()
.useFunctionsEmulator("http://localhost:5001");
}
or when serving pages from backend (node.js):
//after firebase init
if (process.env.NODE_ENV === 'development') {
firebase.functions().useFunctionsEmulator('http://localhost:5001');
}
if you are using angularfire, add this to you app.module
{
provide: FirestoreSettingsToken,
useValue: environment.production
? undefined
: {
host: "localhost:5002",
ssl: false
}
}

Asynchronous https firebase functions

Should HTTPS functions return asynchronous promises like realtime functions have to?
We haven't been returning in HTTPS functions (just using res.status.send etc), and it looks like firebase/function-samples aren't either. But the documentation is slightly ambiguous https://firebase.google.com/docs/functions/terminate-functions .
This works now in the latest Firebase:
exports.asyncFunction = functions.https.onRequest(async (request, response) => {
const result = await someAsyncFunction();
response.send(result);
});
HTTP functions currently do not respect returned promises - they require a sent result in order to terminate normally. If an HTTP function doesn't send a result, it will time out.
All other types of functions require a returned promise in order to wait for asynchronous work to fully complete.
If you don't have any async work to wait for, you can just return immediately.
These are the three cases outlined in the docs.
After much looking around , this is implementation with a Promise worked for me to return a value from a Google Cloud Function where the function needs to make a third-party asynchronous call :
exports.getSomeAccessToken = functions.https.onCall((data, context) => {
var dataStr = JSON.stringify(data, null, '\t');
console.log('ENTER [getSomeAccessToken], got dataStr: ' + dataStr);
return new Promise((resolve, reject) => {
gateway.clientToken.generate({}, function (err, gatewayResponse) {
var result = {
clientToken: gatewayResponse.clientToken
};
var resultStr = JSON.stringify(result, null, '\t');
console.log("resultStr : " + resultStr);
resolve(result);
});
});
});
Your cloud functions should return"end" with either of the following
res.redirect(), res.send(), or res.end()
What they mean by returning promises, is lets imagine you have a cloud function that updated a node in your realtime database, you would like to complete that work before responding to the HTTP request.
Example code
let RemoveSomething = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// Remove something
DoDatabaseWork()
.then(function (result) {
res.status(200).send();
})
.catch(function (err) {
console.error(err);
res.status(501).send();
});
});
});
Update: Added DoDatabaseWork example.
const DoDatabaseWork = function () {
return new Promise(function (resolve, reject) {
// Remove SomeNode
admin.database().ref('/someNode/').remove()
.then(function (result) {
resolve();
})
.catch(function (err) {
console.error(err);
reject();
});
});
}

Resources