Firebase (with react native) HTTPS function doesn't receive parameters - firebase

I'm using React native with Firebase. I deployed a function (without query params) and I'm able to call it without problems from my iPhone. When I add parameters, I'm able to run it only on my browser, but on my phone the parameters are undefined.
Firebase function
exports.testFunction = functions.https.onRequest((request, response) => {
const searchQuery = request.query.search;
response.status(200).send({data:searchQuery});
});
Client App
const testFunction = functions.httpsCallable('testFunction');
testFunction({search: "anything"})
I'm suspecting that this is a bug in either Firebase SDK, React Native's translation to iOS or hopefully a problem in my code, what could be the problem?

To call firebase functions from app, do not use onRequest; use onCall. See the doc for detailed implementation on both server and client side.
This is also mentioned in the doc of react-native-firebase, but the use of onCall is not emphasized (I think it should be highlighted to avoid confusion).
That said, it is possible to call an onRequest function from app, but the parameters are not under request.query, but under request.body.data. For example, in OP's scenario, the firebase function would be
exports.testFunction = functions.https.onRequest((request, response) => {
const searchQuery = request.body.data.search; // param is not in query
response.status(200).send({data:searchQuery});
});
However, calling onRequest from app is not recommended, because the success or error message sent back to the client cannot be properly handled by react-native-firebase. Thus, the best practice is to use onCall.

Related

How to restrict a Google Cloud function to be invoked only from the Google Cloud Console?

I have a Firebase function to do some data processing, and I only want to invoke it through Google Cloud Console manually.
Just a simple function like this (using node.js):
import * as functions from 'firebase-functions'
// // Start writing Firebase Functions
// // https://firebase.google.com/docs/functions/typescript
//
export const test = functions.https.onRequest(async (req, res) => {
res.send({
query: req.query,
body: req.body,
})
})
The way I did it is to deploy the function via the firebase-cli then remove the Cloud Function Invoker role in the permission tab from GUI.
It seems to work.
But I noticed one thing, when I send the request with Postman
when you send a GET, the error is 400,
But for any request that's not a GET, you get a proper 403
My questions are:
why GET is 400 while the others are 403?
am i doing it right in terms of my requirement?
what's the correct way of doing it?
does the function get invoked while sending a request like POST?

firebase functions emulator useFunctionsEmulator() method not working

I am currently working on the way to test my cloud functions locally.
I found several ways but using firebase emulator and useFunctionsEmulator() method seemed great. At https://firebase.google.com/docs/functions/local-emulator, they didn't say about the method, but I found it on this url How to test `functions.https.onCall` firebase cloud functions locally?.
However, when I run firebase emulator:start and console.log(firebase.functions().useFunctionsEmulator('http://localhost:5001'), it just showed undefined.
I tried several inputs on the origin but nothing changed. There's so little information on the internet about this, I think that's because this is alpha, so Please help me on this.
I got the emulators working and handling local requests by calling the useFunctionsEmulator() method just after initializing the firebase app in the client. Calling it prior caused errors.
firebase.initializeApp(config);
firebase.functions().useFunctionsEmulator("http://localhost:5001");
useFunctionsEmulator() doesn't return anything, it's just a setter.
Use it in the following way:
firebase.initializeApp(config);
const functions = firebase.functions();
functions.useFunctionsEmulator("http://localhost:5001");
I haven't been able to get useFunctionsEmulator() either but I have a workaround:
I switch my onCall function to an onRequest function like so:
// FROM THIS
exports.exampleFunction = functions.https.onCall((data, context) => {
// CODE FOR CLOUD FUNCTION
});
// TO THIS
exports.exampleFunction = functions.https.onRequest((request, response) => {
// CODE FOR CLOUD FUNCTION
});
Then I can serve my function locally with this command firebase serve --only functions which will display a url that I can send requests to via curl, postman or my browser. When I'm done editing the function I switch it back to an onCall function. I hope this helps!
As #app_ wrote, but also this may be worth to someone:
If you use regions, they should only be set for the online call, not the emulated one. This works for me (JavaScript client):
const fns = LOCAL ? firebase.app().functions() :
firebase.app().functions(functionsRegion);
const log = fns.httpsCallable('logs_v200719');
The LOCAL is set earlier to true if we know we are run against emulated back-end. Please let me know if there's a standard way to sniff that, from the Firebase client.
For developer experience, it would be best if regions are handled the same way locally, meaning one doesn't need the above ternary operator.
firebase-tools 8.6.0, JavaScript client 7.16.1
For having troubles calling
firebase.functions().useFunctionsEmulator("http://localhost:5001");
You can test your onCall method in your app by adding this lines.
FirebaseFunctions functions = FirebaseFunctions.getInstance();
functions.useEmulator("10.0.2.2", 5001);
or
functions.UseFunctionsEmulator("http://localhost:5004");
Here is my source : https://firebase.google.com/docs/functions/local-emulator
EDIT: Also in node.js you can use
const admin = require('firebase-admin');
admin.initializeApp();
admin.database().useEmulator('127.0.0.1', 5001);
For Firebase 9 (modular), use connectFunctionsEmulator instead:
import { getApp } from "firebase/app";
import { getFunctions, connectFunctionsEmulator } from "firebase/functions";
const functions = getFunctions(getApp());
connectFunctionsEmulator(functions, "localhost", 5001);
If you have problem using Firebase 9, follow this guide.

How to use httpsCallable on a region other then us-central1 for web

I have a deployed a cloud function which looks like this:
export const publishVersion = functions
.region("europe-west2")
.https.onCall(async (data, context) => {}
Then in my web client I am defining a function to call it like this:
import { functions } from "firebase";
const callPublishVersion = functions().httpsCallable("publishVersion");
export function publishVersion(
guidelineId: string,
guidelineReference: string
) {
return callPublishVersion({
id: guidelineId,
reference: guidelineReference
});
}
Note that the only reason I wrap the function is to have strict typing in my client, but that's besides the point of this question.
The problem is, if I run the web client on a local server (localhost) and call the function, two things seem to go wrong. I get a CORS error:
Access to fetch at 'https://us-central1-my-project.cloudfunctions.net/publishVersion' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: Redirect is not allowed for a preflight request.
And also it looks like it is trying to communicate with us-central1 even though my functions are deployed to europe-west2. When I deploy firebase-tools tells me what the url should be:
✔ functions[publishVersion(europe-west2)]: Successful create operation.
Function URL (publishVersion): https://europe-west2-baymard-gemini-dev.cloudfunctions.net/publishVersion
Where/how do I control things to prevent this CORS error and how can I direct httpsCallable to a different region?
The official documentation does not mention any of this.
--- edit ---
I just found out that if I deploy the function to us-central1 it works without errors. So the CORS part is not an issue (I'll remove it from the title) and the question becomes: How do I configure the web client to call the right region?
--- edit ---
I noticed this in the docs:
Note: To call a function running in any location other than the default us-central1, you must set the appropriate value at initialization. For example, on Android you would initialize with getInstance(FirebaseApp app, String region).
Which seems to be a good pointer, but I don't see how to configure this for web. So I'll narrow it down in the title too.
Found it by inspecting the source code. So this is not in the docs, and it was a bit confusing because of all the different ways you can get a handle to the functions instance, but this is the way:
const app = firebase.app();
const functions = app.functions("europe-west2");
const callPublishVersion = functions.httpsCallable("publishVersion");
here is a different version of this, uses the Modular Web-Version 9
import { initializeApp } from 'firebase/app';
import { getFunctions, httpsCallable } from "firebase/functions";
const app = initializeApp({
// Auth stuff
});
// add the location string as you call getFunctions
const functions = getFunctions(app, "europe-west3");
const myFunction = httpsCallable(functions, "myFunction");
The documentation just explains what Thijs found in the source. Extract at the time of writing:
To set regions on the client, specify the desired region at initialization:
var functions = firebase.app().functions('us-central1');
Certainly not easy to find, so an extra pointer here.

Firebase Callable Function: Response for preflight in invalid

I have created a Firebase callable function, with a simple text return, but am receiving an error when I call the function, both on local and on my deployed app.
The callable function is a simple function to return some text for now:
exports.getSomeInfo = functions.https.onCall(async (data, context) => {
return 'some info';
});
In my app I load the function with:
const getSomeInfo = firebase.functions().httpsCallable('getSomeInfo');
And call it in the app with:
getSomeInfo();
This produces an error of:
Failed to load https://us-central1-[project-ID].cloudfunctions.net/getSomeInfo: Response for preflight is invalid (redirect)
This error occurs when calling the function on local using firebase serve and on the deployed app.
Viewing the logs in the Firebase Console shows no logs or errors.
Other issues mention this could be a CORS issue, or an incorrect Firebase config. I've ensured the Firebase config is correct. And tried a few of the CORS solutions, but continue to get the error above.
Using Firebase#5.5.2.
What else could be causing this error?
As indicated in the documentation, for an HTTPS Callable function you need to "return data that can be JSON encoded".
So if you do something like the following, it should work.
exports.getSomeInfo = functions.https.onCall((data, context) => {
return {result: 'some info'};
});
Update: removed the async
April 2020, I just learned the hard way that callable functions have their module name prepended...
In index.js:
const functions = require('firebase-functions')
// ...
exports.callable = require('./callable')
In callable.js:
const functions = require('firebase-functions');
// ... other stuff
exports.myCloudFunction = functions.https.onCall((data, context) => {
// ...
The way to call this "myCloudFunction" from a JS client is to use its name, prepended with its module name, like this
const fn = firebase.functions().httpsCallable("callable-myCloudFunction")
fn().then(result => { //...
This is documented nowhere, as far as I have found, and, as others have mentioned, almost any error that occurs prior to actually executing the cloud function ends up mislabeled as a CORS error.
After trying across 2 days a variety of refreshes/clean-ups and stuff with CORS, finally found it working after first deleting the function via Firebase console and then deploying the function.
In my case it seems the deployed version got corrupted somehow. It started yesterday when deploy for functions was getting stuck - it would hang and never exit - even though Firebase Status page said all is well. It lasted all day, and I finally let it go to see if it will work today. I thought it was my code, but the deploy is back to working today.
This happened to me a couple days ago. The problem was that when i ran firebase deploythe functions in my src directory were not being compiled. There were typescript errors which stopped it from compiling. You can see if it compiled by checking your lib folder and index.ts inside there I believe.

Use Firebase onRequest() or Express app.use() for the Slack API

Goal
Use the #slack/interactive-message package with firebase-functions to listen and respond to Slack messages and dialogs.
Question
I'm not sure how to use the #slack/interactive-message listener with firebase.
1) Do I use Firebase's functions.https.onRequest(), and somehow pass the req from Slack to slackInteractions.action()?
OR
2) Do I use app.use("/app", slackInteractions.expressMiddleware()); If so, where do slackInteractions.action()s go?
OR
3) Something else?
Code
// Express
import express = require("express");
const app = express();
const cors = require("cors")({
origin: "*"
});
app.use("*", cors);
// Firebase Functions SDK
import functions = require("firebase-functions");
const slackbotConfig = functions.config().slackbot;
const { createMessageAdapter } = require("#slack/interactive-messages");
const slackInteractions = createMessageAdapter(slackbotConfig.signing_secret);
app.use("/app", slackInteractions.expressMiddleware());
// Express route
app.post("/go", (req, res) => {
console.log("Hello from Express!");
res
.status(200)
.send("Hello from Express!")
.end();
});
exports.app = functions.https.onRequest(app);
exports.helloWorld = functions.https.onRequest((_req, res) => {
console.log("Hello from Firebase!");
res
.status(200)
.send("Hello from Firebase!")
.end();
});
tl;dr
I'm new to the details of Express and using middleware. Examples of the #slack/interactive-message show...
slackInteractions.start(port).then(() => {
console.log(`server listening on port ${port}`);
});
...and with Firebase Cloud Functions, this bit isn't relevant. I'm not sure how listeners, requests, and responses are integrated between Firebase and #slack/interactive-message
creator of #slack/interactive-messages here 👋
In short, your solution number 2 seems correct to me. While I don't have experience with Firebase functions, I have a pretty good understanding of express, and I'll provide some more details.
What is express middleware?
Express middleware is a name for a kind of function that processes an incoming HTTP request. All middleware functions can, on a request-by-request basis, choose to pre-process a request (usually by adding a property to the req argument), respond to the request, or post-process a request (like calculate the timing between the request and the response). It can do any one or combination of those things, depending on what its trying to accomplish. An express app manages a stack of middleware. You can think of this as a list of steps a request might work through before a response is ready. Each step in that list can decide to offer the response so that the next step isn't even reached for that request.
The cors value in your code example is a middleware function. It applies some rules about which origins your Firebase function should accept requests from. It applies those rules to incoming requests, and when the origin is not allowed, it will respond right away with an error. Otherwise, it allows the request to be handled by the next middleware in the stack.
There's another middleware in your example, and that's a router. A router is just a kind of middleware that knows how to split an app up into separate handlers based on the path (part of the URL) in the incoming request. Every express app comes with a built in router, and you attached a handler to it using the app.post("/go", () => {}); line of code in your example. Routers are typically the last middleware in the stack. They do have a special feature that people often don't realize. What are these handlers for routes? They are just more middleware functions. So overall, you can think of routers as a type of middleware that helps you divide application behavior based on the path of a request.
What does this mean for slackInteractions?
You can think of the slackInteractions object in your code as a router that always handles the request - it never passes the request onto the next middleware in the stack. The key difference is that instead of dividing application behavior by the path of the request, it divides the behavior using the various properties of a Slack interaction. You describe which properties exactly you care about by passing in constraints to the .action() method. The only significant difference between a typical router and slackInteractions, is that the value itself is not the express middleware, you produce an express middleware by calling the .expressMiddleware() method. It's split up like this so that it can also work outside of an express app (that's when you might use the .start() method).
Putting it together
Like I said, I don't have experience with Firebase functions specifically, but here is what I believe you should start with as a minimum for a function that only handles Slack interactions.
// Firebase Functions SDK
import functions = require("firebase-functions");
const slackbotConfig = functions.config().slackbot;
// Slack Interactive Messages Adapter
const { createMessageAdapter } = require("#slack/interactive-messages");
const slackInteractions = createMessageAdapter(slackbotConfig.signing_secret);
// Action handlers
slackInteractions.action('welcome_agree_button', (payload, respond) => {
// `payload` is an object that describes the interaction
console.log(`The user ${payload.user.name} in team ${payload.team.domain} pressed a button`);
// Your app does some asynchronous work using information in the payload
setTimeout(() => {
respond({ text: 'Thanks for accepting the code of conduct for our workspace' });
}, 0)
// Before the work completes, return a message object that is the same as the original but with
// the interactive elements removed.
const reply = payload.original_message;
delete reply.attachments[0].actions;
return reply;
});
// Express
import express = require("express");
const app = express();
app.use("/", slackInteractions.expressMiddleware());
exports.slackActions = functions.https.onRequest(app);

Resources