I have some firebase cloud functions set up like this:
const app = express();
app.get('/', (req, res) => res.send('404'));
app.head('/:userId/:slug', trackFile);
app.get('/:userId/:slug', trackFile);
app.use('/api', app);
export const api = functions.https.onRequest(app);
As you can see I have defined a route for HEAD requests, but in the trackFile function, I am writing the request method (req.method) to firestore, and it's always coming back as 'GET'.
I am testing it with curl to make the head request: curl -I https://myfirebaseapp.com/etc/etc/
Is there some gotcha with firebase functions where it always passes HEAD requests to GET routes? I need to know when a request is a HEAD request to properly track it.
Update: I opened an issue in the firebase function github repo.
This is standard Express behavior, as detailed here: https://github.com/expressjs/expressjs.com/issues/748
Related
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?
I am using functions and hosting from firebase.
I have defined one function as shown below.
const functions = require("firebase-functions")
const cors = require('cors')
exports.hello = functions.https.onRequest((request, response) => {
functions.logger.info("Hello logs!", {structuredData: true})
return cors()(request, response, () => {
response.send({data: 'hello fire functions'})
})
})
And in hosting call the function like this:
import firebase from "firebase/app"
import "firebase/functions"
const config = { ... }
firebase.initializeApp( config )
const test = firebase.functions().httpsCallable('hello')
test().then( result => console.log(result) )
Then the functions log will be written twice as follows:
2:37:07.548 PM hello: Function execution started
2:37:07.599 PM hello: Hello logs!
2:37:07.600 PM hello: Function execution took 53 ms, finished with status code: 204
2:37:07.809 PM hello: Function execution started
2:37:07.816 PM hello: Hello logs!
2:37:07.817 PM hello: Function execution took 8 ms, finished with status code: 200
It is also displayed twice in the usage graph.
This behavior means I have to pay twice as much usage. This is not normal.
If cors is not used, the log and usage graph will show that it has been executed only once.
But if you don't use cors: When you call a function in the browser, the function is executed, but the browser gets a CORS error.
How can I solve this problem? I couldn't find a solution in the official documentation. (This is a problem after hosting and functions deploying. It is not a localhost environment.)
Firstly, you are mixing up HTTP requests on the client with callable functions. That's not what you're supposed to do. Please review the documentation for both HTTP functions and callable functions to see how they are different. If you're using the callable SDK on the client, you should use a callable function on the backend.
Second, this is the normal expected behavior. Callable functions use CORS between the client and server. CORS clients issue a preflight request which causes the first request and first log. Then, the actual request which causes the second request and second log. You cannot avoid this when using CORS - that's simply how the protocol works.
See also:
Firebase Cloud Function executing twice when triggered over HTTP
Enabling CORS in Cloud Functions for Firebase
Cloud Functions for Firebase triggering function on CORS preflight request
If you're using Firebase Hosting, you probably don't need CORS.
You can avoid preflight requests by adding rewrites to firebase.json.
However, there are conditions such as the position of the function must be us-central1.
https://firebase.google.com/docs/hosting/functions
This question already has answers here:
How to resolve 'preflight is invalid (redirect)' or 'redirect is not allowed for a preflight request'
(6 answers)
Closed 2 years ago.
server code
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.hello = functions.https.onCall((data, context) => {
console.log(data.text);
return 123;
});
client code
const myFunc = firebase.functions().httpsCallable('hello');
myFunc({text: 'world'}).then((rslt)=>{
console.log('rslt:', rslt);
});
But it is not work.
I got error message at client browser console.
Access to fetch at 'https:// ==skip== /hello' from origin
'http://localhost:5000' has been blocked by CORS policy: Response to
preflight request doesn't pass access control check: Redirect is not
allowed for a preflight request.
How can I solve this problem?
I did not find a solution in the google guide documentation.
I see comments and add content.
The query does not arrive at the server.
If I try after functions deploy it works fine.
But it doesn't show up on the local console, but on the firebase console.
How can I make a local server call?
It's crazy to deploy every time you test a function.
Hard to tell from the given code why your request violates Cross-Origin Resource Sharing. Take a read on this matter and figure out what is it that you include (whether by intention or not) in your HTTP request that causes it. Headers maybe?
In the meantime, you can enable CORS in Firebase Functions with require("cors"):
const cors = require("cors")({origin: true});
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.hello = functions.https.onCall((data, context) => {
cors(data, context, () => {
console.log(data.text);
res.send("123");
}
});
Regarding local testing of Firebase Functions, you can do so by using this command:
firebase serve --only functions
Local testing of Firebase Hosting / Functions
Enabling CORS in Firebase Functions.
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);
I am new to node and express. I have encountered a cors error when I am building a very simple API. I have tried several hours to solve it in different method but none of these work.
Here's my approach
const functions = require('firebase-functions');
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({ origin: true }));
app.get('/api', (req, res) => {
res.send('Hello');
});
exports.api = functions.https.onRequest(app);
and got 4 errors all about :
http://localhost:3000 is not allowed by Access-Control-Allow-Origin.
I have also tried several other some methods like this:
var allowCrossDomain = function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept, Cache-Control");
next();
};
app.use(allowCrossDomain);
Which gives me the same error.
I am using Firebase Cloud Function to deploy this api, because the code is so simple so I really can not figure out which part is not doing right.
CORS is always a sticky situation, but in this case, I think I might be able to help. When you run firebase deploy you should see your endpoint get deployed. If it's the first time you are deploying that function, it should print out that new function's full URL in the console, it usually looks something like this:
https://us-central1-your-project-name.cloudfunctions.net/apiEndpointName
If you've already deployed it, you can see the function's full URL in the Firebase console -> Functions -> Dashboard
That URL is the normal public API endpoint or "HTTP-trigger" for that function. If you would use Postman to make a GET request to that URL, you should expect to receive your Hello response. (Or if you visited that URL in your browser, your browser would make a GET request to that URL, you should get your Hello response there too)
The problem comes when you want to access it from your deployed/hosted website. You need to tell the hosting portion of Firebase to route any traffic for /api to your function - your Firebase hosting should not try to resolve the /api route as a normal HTML page deployed along-side the primary index.html file... instead it should direct any traffic for /api to the cloud function api
So, you need to tell Firebase to direct any traffic for /api to the cloud function, not hosting. You give Firebase commands/configuration in the firebase.json file... in this case, under a section named "rewrites" like this:
{
"hosting": {
"public": "public",
// Add the following rewrites section *within* "hosting"
"rewrites": [ {
"source": "/bigben", "function": "bigben"
} ]
}
}
Check out this documentation link where it explains all that^^
Once you've done that, redeploy everything, and now you should be able to visit /api in your browser and trigger the function. NOTE Unless you are using firebase serve you should visit the route on the deployed website, not localhost. Check out this link for more details on firebase serve.