How to setup caching for specific headers in Firebase Cloud Functions? - firebase

I'm unsure if this is even possible. I'm making requests to an external api and on each request from my app I pass the user's query into a custom 'query' header in the Cloud Function (Typescript). ⤵
export const searchQuery = functions.https.onRequest(async (request, response) => {
// : Reads request query data from user
const query = request.headers.query;
...
I attempted to setup caching so that with each query it caches the result separately but it doesn't seem to be working. ~
( caching does work without vary header set however it's only caching the first search result )
( all requests are sent as GET )
This is the block that sets the 'query' header as a vary rule for caching (written in Typescript). ⤵
...
return await admin.auth().verifyIdToken(tokenId) // : Authenticates response
.then(() => {
// : Set cache-control
console.log(request.headers);
response.set('Vary', 'Accept-Encoding, query');
response.set("Cache-Control", "public, s-maxage=600");
response.set("Access-Control-Allow-Origin", "*");
response.set("Access-Control-Allow-Methods", "GET");
// : Grab API search data
axio.get(urlAssembler).then(APIData => {
response.status(200).send(APIData.data);
}).catch(error => console.log(error));
})
.catch((err) => response.status(401).send(err));
...
I have this setup on Cloud Functions w/ Firebase and not Cloud Functions with Firebase Hosting. I'm wondering if maybe there's a difference there but it seems there isn't.
In my firebase.json I noticed that it is setup for Cloud Functions and not for Firebase Hosting. Perhaps I need to set it up as Firebase Hosting to define cache-control for headers there?
This is my firebase.json ⤵
{
"functions": {
"predeploy": [
"npm --prefix \"$RESOURCE_DIR\" run lint",
"npm --prefix \"$RESOURCE_DIR\" run build"
]
}
}

I contacted Firebase support regarding this and the individual helping me said that Firebase Hosting needs to be used in order to cache based on a header like described above.
I have not tested this yet so once I test it I will update this answer to confirm that it works.
There is an article suggested to me by the individual helping me from Firebase support for how to cache within a Cloud Function but I don't believe this makes use of a CDN.

Related

How to override AWS Amplify Api domain endpoint

I have added a custom domain to the API Gateway due to CORS/Cookies/Infosec and other fun reasons.
I then added the following code to hack the correct domain into my Amplify configuration:
import { Amplify, API } from "aws-amplify"
const myfunc () => {
const amplifyEndpoint = API._restApi._options.endpoints[0]
Amplify.configure({
aws_cloud_logic_custom: [
{
...amplifyEndpoint,
endpoint: process.env.REACT_APP_API_URL || amplifyEndpoint.endpoint,
},
]
})
const response = await API.post("MyApiNameHere", "/some-endpoint", {data:"here"})
}
This works but a) is this really the correct way to do it? and b) I'm seeing a weird issue whereby the first API.post request of the day from a user is missing the authorization & x-amz-security-token headers that I expect Amplify to be magically providing. If a user refreshes the page, the headers are sent and everything works as expected.
[edit] turns out my missing headers issue is unrelated to this override, so still need to get to the bottom of that!
The more correct place looks to be in the index.js:
import { Amplify, API } from "aws-amplify"
import awsExports from "./aws-exports"
Amplify.configure(awsExports)
const amplifyEndpoint = API._restApi._options.endpoints[0]
Amplify.configure({
aws_cloud_logic_custom: [
{
...amplifyEndpoint,
endpoint: process.env.REACT_APP_API_URL || amplifyEndpoint.endpoint,
},
]
})
That way it's done once - rather than needing to be done in each API call.
Naturally you would need some more complicated logic if you were dealing with multiple endpoints.

How to stub external services in Firebase emulator?

I have one firestore trigger function that is creating DNS record based on the slug attribute. I have some unit tests where I am stubbing #google-cloud/dns module so no external HTTP request is made. However, I have several integration tests as well. Those are hitting local firebase emulator (localhost:8080).
For instance, whilst testing firestore rules, I am simply calling db.collection('path').add(model) and that is triggering callable function inside emulator process.
Tests are running by this command: firebase emulators:exec 'mocha --config spec/.mocharc.yml
At first, it is initializing emulators and then running tests. As far as I understood these are different processes. So inside the mocha process, I am able to stub, mock with modules. On the other hand, inside the emulator process, functions, modules, dependencies are already loaded as it is. So when I am running this test script for testing firestore rules inside mocha test suite:
await assertSucceeds(db.doc('stores').set(store));
It actually runs the handler and sends the request to the google cloud DNS. Did anyone face issues something like this? Thanks in advance.
I'm facing the same situation. My plan is to make the Cloud Function initialize itself with a stubbed service that will record its own calls to a log collection in Firestore when NODE_ENV=test. Then the test will check whether the call was recorded by querying that collection.
test('fanOutCategoryFields', async () => {
// Initialize Firebase and seed test data
const app = await setup({
'categories/1': {
name: 'Some Name',
},
'posts/1': {
categoryId: '1',
},
})
// Update a document (will trigger the Cloud Function)
await app
.firestore()
.collection('categories')
.doc('1')
.update({ name: 'New Name' })
// Wait for the Cloud Function to run
await new Promise(resolve => setTimeout(resolve, 3000))
// Query the collection where function calls are recorded
const calls = await app
.firestore()
.collection('_calls')
.get()
.then(snap => snap.docs.map(snap => snap.data()))
// Check if Algolia was called with the expected arguments
expect(calls).toEqual([
{
fn: 'algolia.update',
args: ['products', '1', { categoryName: 'New Name' }],
},
])
})

firebase hosting blocking script due to CORS issue

I am using firebase hosting to host few scripts and trying to access them from another site. it naturally gets blocked due to CORS issues. based on my research on other forum threads etc i modified the firebase.json as below
{
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
"headers": [ {
"source" : "**",
"headers" : [ {
"key" : "Access-Control-Allow-Origin",
"value" : "*"
} ]
}]
}
}
which essentially allow any url to access the resources hosted here. however, on trying to run my site i still see below
Access to XMLHttpRequest at 'https://oracle-bot-sdk.firebaseapp.com//loader.json'
from origin 'https://insurance-bot.moblize.it' has been blocked by CORS policy:
No 'Access-Control-Allow-Origin' header is present on the requested resource.
what else is needed?
In addition to your firebase.json changes for cors, your firebase functions http / https function needs to also include the cors plugin.
Example
const cors = require('cors')({origin: true});
const functions = require('firebase-functions');
const app = functions.https.onRequest((req, res) => {
cors(req, res, () => {
// Your app stuff here
// Send Response
res.status(200).send(<response data>);
});
});
Express App Example
import express from "express";
const cors = require('cors')({origin: true});
const app = express();
app.get('**', (req, res) => {
cors(req, res, () => {
// Your App Here
// Send response
res.status(200).send(<response data>);
});
});
More documentation Serve Dynamic Content with Cloud Functions - Create an HTTP function to your Hosting site (Cors is not mentioned in the documentation btw)
Is the site (https://insurance-bot.moblize.it/) that is calling to https://oracle-bot-sdk.firebaseapp.com a Firebase hosted app?
I only ask because with version 4.2+ of Firebase Tools allows you to setup Multisite hosting using the same Firebase Project. I am not sure if that would help your situation out at all. Just wanted to mention it.
In the error message:
insurance-bot.moblize.it/:1 Failed to load https://oracle-bot-sdk.firebaseapp.com//loader.json: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'https://insurance-bot.moblize.it' is therefore not allowed access.
I noticed an extra '/' in https://oracle-bot-sdk.firebaseapp.com//loader.json. I doubt that is the issue, but wanted to mention it.
There is something that you could try. Similar to the answers above but a little different:
"headers": [
{
"source": "*",
"headers": [
{
"key": "Access-Control-Allow-Origin",
"value": "*"
}
]
}
]
Also I would read some of the info here: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS#Access-Control-Allow-Origin If you have not already.
I hope I was able to help in some way. Let me know.
My guess that you've mixed up firebase hosting and firebase cloud functions. Firebase hosting is made for hosting static websites and web apps. As you try to access from your website that is hosted on different domain your configuration for hosting is not applied. You mentioned that you host some scripts and it sounds like cloud functions. And good old CORS headers can help to your cloud functions like:
exports.corsEnabledFunction = (req, res) => {
res.set("Access-Control-Allow-Origin", "*");
res.set("Access-Control-Allow-Methods", "GET");
res.set("Access-Control-Allow-Headers", "Content-Type");
res.set("Access-Control-Max-Age", "3600");
// Continue with function code
...
}
More info: https://cloud.google.com/functions/docs/writing/http#handling_cors_requests
Make sure you have the Blaze or Flame plan, I think Spark plan blocks external access, maybe for the same reason as it does with cloud functions
Cloud Functions for Firebase - Billing account not configured
Go to the Google Cloud Console: https://console.cloud.google.com/functions/
Click the checkbox next to the function on which you want to grant access.
Click Show Info Panel in the top right corner to show the Permissions tab.
Click Add member.
In the New members field, type allUsers.
Select the role Cloud Functions > Cloud Functions Invoker from the Select a role drop-down menu.
Click Save.
taken from: https://github.com/firebase/firebase-functions/issues/645#issuecomment-605835353
This was the best solution for me as posted above
Go to the Google Cloud Console: https://console.cloud.google.com/functions/
Click the checkbox next to the function on which you want to grant access.
Click Show Info Panel in the top right corner to show the Permissions tab.
Click Add member.
In the New members field, type allUsers.
Select the role Cloud Functions > Cloud Functions Invoker from the Select a role drop-down menu.
Click Save.
taken from: https://github.com/firebase/firebase-functions/issues/645#issuecomment-605835353
Try pasting this as it's directly from the documentation, Customize Hosting Behavior:
"hosting": {
// Add the "headers" section within "hosting".
"headers": [ {
"source" : "**/*.#(eot|otf|ttf|ttc|woff|font.css)",
"headers" : [ {
"key" : "Access-Control-Allow-Origin",
"value" : "*"
} ]
}
}
Firebase hosting CORS doesn't work WITH custom domain.
However, CORS API works with https://yyyyyyy.web.app/ or firebaseapp.com domain

Best setup/workflow for testing and deploying an application using Google Firebase Hosting and Functions

I built a small web application (www.suntax.de) with vuejs and hosted on Google Firebase. I use the Firebase Hosting, Database and Functions. I use Functions to do some calculation on the backend.
Everything is running now, but I had some trouble getting there and some things I would like to improve regarding my workflow.
Thus, I would like to explain you what I did and where I struggle now.
First I set up the vuejs applicationd deployed it to firebase. I added my custom domain and everything was doing fine.
Later I implemented the cloud function and want to make a call from my vuejs app.
After firebase deploy, I can call this function in the browser and it just works fine:
https://us-central1-suntax-6d0ea.cloudfunctions.net/calcEEGcompensation?year=2013&month=1&size=10
Now I thought, that I just call the URL of the function from my vuejs app. But then I got the following error message:
[Error] Origin * is not allowed by Access-Control-Allow-Origin.
I was then reading that I had to add a rewritesection in the firebase.json:
Now my firebase.json looks like this:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
// Add the following rewrites section *within* "hosting"
"rewrites": [ {
"source": "/calcEEGcompensation", "function": "calcEEGcompensation"
} ]
}
}
Now I was able to call my firebase function with the following URL:
https://www.suntax.de/calcEEGcompensation?year=2013&month=1&size=10
After integrating the above URL in my vuejs application, the application is running fine after deployment to firebase server.
As I want to keep improving the application, I would like to test everything locally before deploying.
I know that I can run firebase hosting and functions locally by:
firebase serve --only functions,hosting
However, now my application has the hard coded call to my function https://www.suntax.de/calcEEGcompensation?year=2013&month=1&size=10 and this again leads to the error [Error] Origin * is not allowed by Access-Control-Allow-Origin.
But also changing the URL to the local function
http://localhost:5001/suntax-6d0ea/us-central1/calcEEGcompensation?year=2013&month=1&size=10
leads to the error message
[Error] Origin * is not allowed by Access-Control-Allow-Origin.
Some further reading brought me to the solution with cors. So I changed my function to:
const functions = require('firebase-functions');
const cors = require('cors')({origin: true});
exports.calcEEGcompensation = functions.https.onRequest((req, res) => {
cors(req, res, () => {
const yearOfCommissioning = req.query.year;
const monthOfCommissioning = req.query.month;
const pvsystemsize = req.query.size;
...
res.send("hello");
});
});
This helped and everything works now:
- The deployed application is still running fine.
- I can run the application locally while calling the local function as well as the deployed function. I just have to change the URL of the function.
But this is now my question:
Can I solve this issue in a better way? If I test the vuejs application and the function locally, I have to change the function URL before deployment. And then I have to change it back while testing locally.
I was not able to test my application and function locally without cors.
My ideal solution would be to have a setup, that can be fully tested locally and which can be easily deployed with firebase deploy without any changes of URLs. Is this possible?
Thanks and best regards,
Christoph
I found the solution which is pretty simple and does exactly what I want. I have no idea why I did not figure it out before.
Here is what I did:
I just call the relative URL from my firebase hosting:
calcEEGcompensation?year=2013&month=1&size=10
and everything works fine if the rewrites are properly set in firebase.json:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"ignore": [
"firebase.json",
"**/.*",
"**/node_modules/**"
],
// Add the following rewrites section *within* "hosting"
"rewrites": [ {
"source": "/calcEEGcompensation", "function": "calcEEGcompensation"
} ]
}
}
After setting up everything like this, I can just execute firebase serve --only functions,hosting and I can test everything locally.
After executing firebase deploy, everything runs smoothly on the server.
I do not need cors.
Thanks #wonsuc for your answers.
Update(For Firebase Hosting):
Currently there is no workaround you can solve it with Firebase Hosting SDK.
But there is alternative way you can achieve this.
Try below code in your hosting source.
if (location.hostname === 'localhost' || location.hostname === '127.0.0.1') {
console.log('It's a local server!');
}
In my opinion, these are best way to check dev environment currently.
Therefore you should use location.hostname in your Firebase Hosting, and server.address() in Cloud Functions.
And define your Functions end point with constant variable.
const DEBUG = location.hostname === 'localhost' || location.hostname === '127.0.0.1';
const FUNCTIONS_ENDPOINT_DEV = 'http://localhost:5001/';
const FUNCTIONS_ENDPOINT_PRD = 'https://us-central1-something.cloudfunctions.net/';
const FUNCTIONS_URL_CALC = 'calcEEGcompensation?year=2013&month=1&size=10';
var endpoint;
if (DEBUG) {
endpoint = FUNCTIONS_ENDPOINT_DEV + FUNCTIONS_URL_CALC;
} else {
endpoint = FUNCTIONS_ENDPOINT_PRD + FUNCTIONS_URL_CALC;
}
Original answer(For Cloud Functions for Firebase):
Have you tried node.js net module's server.address() function?
This method will tell you if your functions code is running on localhost or real deployed server.
For examples, you can use like this.
const server = app.listen(function() {
let host = server.address().address;
let port = server.address().port;
if (!host || host === '::') {
host = 'localhost:';
}
console.log('Server is running on %s%s', host, port);
});

Firebase set up rewrites to pass current user to Cloud Function

I'm trying to use Cloud Functions for Firebase to serve content and I'd like to detect whether a user is logged in or not. I've set up a rewrite in my firebase.json that looks like this:
{
"database": {
"rules": "database.rules.json"
},
"hosting": {
"public": "public",
"rewrites": [{
"source": "**",
"function": "getProfile"
}]
}
}
This works fine and I'm serving appropriate content based on the path that's being requested. However, because I'm not doing anything on the client side (i.e. I'm using rewrites rather than client-side redirects), I'm missing the opportunity to get the current user from a client-side script.
Is there some way I can use a header or a property of the request object so that I can serve different content to logged in vs. non-logged in users in my server-side rewrites scenario?
Firebase Hosting passes along any cookie named __session when it calls a Cloud Function. An easy way to do this is to simply listen for ID tokens in your web app and set the cookie appropriately:
firebase.auth().onIdTokenChanged(user => {
if (user) {
user.getIdToken().then(token => {
document.cookie = `__session=${token};max-age=3600`;
});
} else {
document.cookie = '__session=;max-age=0';
}
});
Then, in your Cloud Function, you can parse the ID token out of the cookie and verify it using code like in this sample.

Resources