Cannot get req.path and req.query.abc with firebase functions - firebase

I'm trying to get the request query params and url in firebase functions.
Here is the code I'm using
firebase.json
{
"hosting": {
"public": "build",
"rewrites": [{
"source": "/getCoins",
"function": "getCoins"
}]
}
}
Using "firebase-functions": "^2.3.1" in package.json
functions/index.js
'use strict';
const functions = require('firebase-functions');
exports.getCoins = functions.https.onRequest((req, res) => {
console.log(req.query); // [object Object]
console.log(req.query.repeat); // empty
console.log(req.url); // '/'
console.log(req.originalUrl); // '/'
res.sendStatus(200);
});
Started the firebase functions in my windows command prompt using firebase serve --only functions. As it starts serving data from http://localhost:5000, I'm trying to request http://localhost:5000/coins-app/us-central1/getCoins?repeat=4
I'm not getting any error in the command prompt, but could only see the commented lines from the above functions/index.js code.

You should run firebase serve and request to http://localhost:5000/getCoins?repeat=4 .
functions.https.onRequest() can't accept query string parameter directly.

Related

Nuxt3 and Firebase Cloud Functions: Where to place Firebase cloud functions in the /server directory?

I successfully was able to deploy my Nuxt 3 app to Firebase hosting using Firebase cloud functions. Now, I want to create another Firebase cloud function that automatically runs some backend code in response to events triggered by Firebase Firestore database.
As a test, I wanted to add the following simple "Hello World" Http function as a test:
server/api/functions/helloWorld.js:
import * as functions from 'firebase-functions'
export default defineEventHandler(() => {
return functions.https.onRequest((request, response) => {
console.log(request)
return response.send('Hello from Firebase!')
})
})
I ran npm run build and saw the file in .output/server/chunks . Then, I ran the firebase emulator to test : I typed in http://localhost:5001/<myprojectid>/us-central1/server/api/functions/helloWorld but get the following server error:
{"url":"/api/functions/helloWorld","statusCode":404,"statusMessage":"Not Found","message":"Not Found","description":""}
However, when I try to access my other functions, I have no problem (ie, /server/api/posts/all):
This is the makeup of a "working" function (not an Http Cloud Function, though):
/server/api/posts/all.ts:
import { firestore } from '#/server/utils/firebase'
export default defineEventHandler(async (event) => {
const colRef = firestore.collection('posts').orderBy('createdAt', 'desc')
const querySnapshot = await colRef.get()
const posts = []
querySnapshot.forEach((doc) => {
if (doc.data().public_id) // ensure we only show posts with images
posts.push(doc.data())
})
return {
posts
}
})
How can I access the Firebase Function (helloWorld)?
Here is my firebase.json file:
{
"functions": {
"source": ".output/server"
},
"hosting": [
{
"site": "<removed>",
"public": ".output/public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"cleanUrls": true,
"rewrites": [
{
"source": "**",
"function": "server"
}
]
}
]
}
When using firebase preset, nuxt3 only treats functions as renderer. Here is the recommend way How to add additional firebase functions when deploying nuxt to firebase
Or you can write scripts to copy files and combine index.js, and run it at predeploy

How to deploy Nuxt SSR app to firebase through cloud functions?

I followed this medium post as a guide to host my nuxt app on firebase hosting.
The deploy goes through, however, when I then visit the url I get a Cannot GET / error message. If I check the function logs I see that the function completes with a 404 error.
This is my functions/index.js
const functions = require('firebase-functions')
const admin = require("firebase-admin")
const { Nuxt } = require('nuxt-start')
const nuxtConfig = require('./nuxt.config.js')
admin.initializeApp()
const config = {
...nuxtConfig,
dev: false,
debug: true,
buildDir: "nuxt",
publicPath: "public",
}
const nuxt = new Nuxt(config)
exports.ssrapp = functions.https.onRequest(async (req, res) => {
await nuxt.ready()
nuxt.render(req, res)
})
And this is the firebase configuration
{
"functions": {
"source": "functions",
"predeploy": [
"npm --prefix src run build && rm -rf functions/nuxt && cp -r src/nuxt/ functions/nuxt/ && cp src/nuxt.config.js functions/"
]
},
"hosting": {
"predeploy": [
"rm -rf public/* && mkdir -p public/_nuxt && cp -a src/nuxt/dist/client/. public/_nuxt && cp -a src/static/. public/ && cp -a public_base/. public/"
],
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"rewrites": [
{
"source": "**",
"function": "ssrapp"
}
]
}
}
The project structure is this
/
/src
/functions
/public
/public_base
When I run firebase deploy I do see the content of /src being copied to /functions/nuxt. For the sake of testing I also have my /functions/package.json file include all the dependencies that I have in my /src/package.json file.
How can I get this app up and running? If you need any more details, let me know.
Update #1
I tried serving hosting and functions using the command firebase serve --only functions,hosting -p 5004.
Running the server like so gave me some more insights. Specifically I got this warning message at load time that then resulted in actual error message when loading the page.
WARN Module #firebase/app not found. Silently ignoring module as programatic usage detected
I didn't install this package in the functions directory as I did not have it in my src directory either. However, after installing it inside /functions and restarting the dev server, that warning message was gone and with it also the error I was getting on page load.
I also spotted a couple of warnings that had to do with vue, vue-server-renderer and vue-template-compiler not running the same version.
Now everything seems to be working fine!
Your config and structure look right.
I also did this about a year ago with my app and I ran into the same problem. It has to do with the function code itself. I also tried using nuxt.render(req, res) (Docs) but it never worked for me so I ended up using nuxt.renderRoute(route) (Docs) instead. This worked perfectly fine.
You could change your code to something like this:
const { Nuxt } = require('nuxt')
...
exports.ssrapp = functions.https.onRequest(async (req, res) => {
await nuxt.ready()
const result = await nuxt.renderRoute(req.path) // Returns { html, error, redirected }
res.send(result.html) // Sends html as response
})
This worked for me. You should consider catching any errors though. I hope this solves your problem.
This is only a work-around, if anyone knows why nuxt.render isn't working, I would be interested to know. I wasted many hours because of this with my app...
I tried serving hosting and functions locally using the command firebase serve --only functions,hosting -p 5004.
By doing this I was able to spot a bunch of warnings and errors such as
FATAL
Vue packages version mismatch:
- vue#2.5.21
- vue-server-renderer#2.6.11
This may cause things to work incorrectly. Make sure to use the same version for both.
WARN Module #firebase/app not found. Silently ignoring module as programatic usage detected
I then made sure that those vue packages were indeed running the same version. Then I also added the #firebase/app package in /functions/package.json (that however, is not present in /src/package.json.
I run the local server again and everything worked. I then deployed to firebase and everything was working as expected.
the following works for me (using nuxt-start instead):
const functions = require("firebase-functions");
const { Nuxt } = require("nuxt-start");
const config = {
dev: false
};
const nuxt = new Nuxt(config);
let isReady = false;
async function handleRequest(req, res) {
if (!isReady) {
try {
isReady = await nuxt.ready();
} catch (error) {
process.exit(1);
}
}
await nuxt.render(req, res);
}
exports.nuxtssr = functions.https.onRequest(handleRequest);

Firebase Hosting Rewrite to Cloud Function Prompting for Login

I'm having some trouble with serving some dynamic content from Firebase Hosting.
I've written an http.onRequest() cloud function that returns an image (content-type: image/jpeg) as its response. The function works as expected if I access it directly at its url:
https://us-central1-my-project-id.cloudfunctions.net/hosting-getPartnerImg
Per the documentation, I am using the us-central1 region.
I would like to be able to invoke this function using Firebase Hosting as well, which I've configured as follows:
firebase.json (snippet)
"rewrites" : [
{
"source" : "/pimg",
"function" : "hosting-getPartnerImage"
}
],
"headers": [
{
"source": "/pimg",
"headers": [ {
"key": "Cache-Control",
"value": "max-age=60"
},
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
}]
index.js (snippet)
const functions = require('firebase-functions');
const admin = require('firebase-admin');
let firebaseDefaultConfig = JSON.parse(process.env.FIREBASE_CONFIG);
admin.initializeApp(firebaseDefaultConfig);
const fn = process.env.FUNCTION_NAME;
if(!fn || fn === 'hosting-getPartnerImg'){
exports.hosting = require('./hosting.js');
}
hosting.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
exports.getPartnerImg = functions.region("us-central1").https.onRequest((req, res) => {
const partnerId = req.query.partner;
const fileName = req.query.file;
res.set("content-type", "image/jpeg");
const bucket = admin.storage().bucket();
let file = bucket.file("partnerImgs/" + partnerId + "/" + fileName);
let readStream = file.createReadStream();
readStream.pipe(res);
});
This one has me stumped. Navigating to a URL like:
https://my-project-id.web.app/pimg?partner=BLAH&file=foo.jpg does not generate a Page Not Found as other URLs, so I'm reasonably confident that the rewrite is taking hold as it should. Be that the case though, why am I immediately taken to:
appengine.google.com/_ah/loginform... with the message:
** An application is requesting permission to your Google Account **
Can't HTTP onRequest cloud functions be used anonymously via Firebase Hosting? Why does the function work when I hit it directly, yet requests permission when I access it via the Firebase Hosting rewrite.
Any ideas would be appreciated.
The AppEngine login page behavior is what happens when the function being called doesn't exist (regardless of if it is via a rewrite or not).
Your problem is this rewrite:
"function" : "hosting-getPartnerImage"
vs the actual function name:
if(!fn || fn === 'hosting-getPartnerImg'){
or
exports.getPartnerImg = ...
Notably, the rewrite doesn't call the correct function.
You should change the rewrite to call hosting-getPartnerImg instead.

Firebase Functions on Firebase Hosting: 404

I'm trying to call functions from app but it doesn't work and I'm getting the following error from the console:
index.esm.js:402 OPTIONS https://us-central1-undefined.cloudfunctions.net/addMessage 404 ()
Failed to load https://us-central1-undefined.cloudfunctions.net/addMessage: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://MYWEBADDRESS' is therefore not allowed access. The response had HTTP status code 404. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
firebase.json:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"rewrites": [
{
"source": "**",
"function": "addMessage"
}
]
},
"functions": {
"predeploy": [
"npm --prefix $RESOURCE_DIR run lint"
],
"source": "functions"
}
}
index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.addMessage = functions.https.onCall((data, context) => {
// Message text passed from the client.
const text = data.text;
// Checking attribute.
if (!(typeof text === 'string') || text.length === 0) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('invalid-argument', 'The function must be called with ' +
'one arguments "text" containing the message text to add.');
}
// Checking that the user is authenticated.
if (!context.auth) {
// Throwing an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'while authenticated.');
}
// Saving the new message to the Realtime Database.
return admin.database().ref('/messages').push({
text: text
}).then(() => {
console.log('New Message written');
// Returning the sanitized message to the client.
return { text: sanitizedMessage };
}).catch((error) => {
// Re-throwing the error as an HttpsError so that the client gets the error details.
throw new functions.https.HttpsError('unknown', error.message, error);
});
});
my script in index.html
var addMessage = firebase.functions().httpsCallable('addMessage');
addMessage({text: "messageText"}).then(function(result) {
var message = result.data.text;
console.log(message);
});
How I initialize Firebase:
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-auth.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-database.js"></script>
<script src="https://www.gstatic.com/firebasejs/5.0.4/firebase-functions.js"></script>
<script>
// Initialize Firebase
var config = {
apiKey: "**",
authDomain: "***",
databaseURL: "***",
storageBucket: "***",
};
firebase.initializeApp(config);
var functions = firebase.functions();
</script>
I was having the same exact problem, found your question without an answer, but managed to figure it out in the end.
As #Doug Stevenson mentioned above in the comments, the problem is that the Cloud Functions URL you are seeing has undefined instead of your project ID as the last piece of the url subdomain.
The reason it is undefined is because your project ID is not part of your initial Firebase config object. Like me, you likely copied and pasted the starter snippet from Firebase for the JS SDK, but you did it before they started including the Project ID as part of it. For some reason, even though the project ID is now needed to construct the cloud functions URL, the SDK doesn't error / warn if you don't include it.
All you need to do is add the following field to your config object:
projectId: <YOUR_PROJECT_ID_HERE>
Then you should see the requests no longer 404.

Access-Control-Allow-Origin Error when trying to add open graph meta tags dynamically via Firebase functions

I'm trying to add open graph meta tags dynamically to my index.html using firebase functions. I'm following this tutorial but because I'd like to allow users to share on their facebook I need to add open graph meta tags on-demand when user wants to share content.
So, it seems to me that running the function via rewrite in firebase.json is not best solution here and I'd like to call the function when user wants to share content to Facebook.
I try to use CORS but it seems I get the "Access-Control-Allow-Origin" error in the browser when I am sending my response back. If I redirect I have no issue. But I need to send the index.html that's been updated. How can I resolve this issue?
Here's my function:
const cors = require('cors')({ origin: true });
const fs = require('fs');
exports.handler = function(req, res) {
cors(req, res, () => {
let indexHTML = fs.readFileSync('./hosting/index.html').toString();
console.log(`#share-fb-formfeed inside cors`, indexHTML);
const ogPlaceholder = '<meta name="functions-insert-dynamic-og">';
res.set('Cache-Control', 'public, max-age=300, s-maxage=600');
res.status(200).send(indexHTML); //results in Access-Control-Allow-Origin
// res.redirect(`https://thekasis.com/feed`); //this works
});
}
Using Rewrites
I also removed cors in my function and here is my firebase.json but I'm not sure if running the Firebase function all the time would be a good idea. It would be ideal to run the function only on-demand when the user is sharing something and so I really like to be able to call the function client side on-demand.
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "build/es5-bundled",
"rewrites": [{
"source": "**",
"function": "shareContentG-shareToFb"
}]
}
}

Resources