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.
Related
I am trying to get simple auth in my Teams app working with Adobe ID (a third party Oauth provider that I use on my site).
I am following the sample here. Everything is working to authorize with the Adobe ID, but when it gets to my end authentication page like this, I get an exception thrown with the message "SDK initialization timed out." when I call await app.initialize();. The sample shown does not have the await term before app.initialize(). Is that incorrect? If I remove the await, my code later on to notify authentication of success, fails with the exception "The library has not yet been initialized".
authentication.notifySuccess("Yippee");
What do I need to do to allow app.initialize() to work?
How can the sample work if there is no await before it?
Here is the TypeScript code for my OAuth End page that is loaded after the Adobe Authentication succeeds.
import $ from "jquery";
import {app, authentication} from "#microsoft/teams-js";
startup();
async function startup(){
try{
$("#status").text("Initializing");
await app.initialize();
$("#status").text("Initialized");
console.log("notifying of success");
authentication.notifySuccess("Yippee");
}
catch(error){
handleError(error, "initializing");
}
}
function handleError(error:Error, context: string){
console.error(`💥 Error ${context}: ${error.message}`);
$("#status").text(error.message);
}
What version of the js library are you using?
There have been some changes to how app.initialize is handled depending on the version of the SDK you are using- see details here
https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/using-teams-client-library?tabs=typescript%2Cmanifest-teams-toolkit#callbacks-converted-to-promises
You should be able to see this in package.json for typescript, although I think there may be other areas where you may need to update this.
Try this with the version referenced in the sample- 2.0.0 does that resolve the issue?
re: await- that just tells the application to wait until a response is returned (a success or failure) before moving on to the next line. In your case, you need the await as your next line is dependent on the value of status, but your function is async, so it won't block the execution of other async functions. You want to use await where you have other requests that must have a response on that call- like getauthtoken.
I'm trying to follow this guide to put some custom logging into a firebase function. The function itself is running, and I can see the data being passed in (it's an https 'callable' function). But as soon as it hits the line where it tries to actually write that log entry, I get "Error: 7 PERMISSION_DENIED"
Since the console.log() calls write to the cloud logs, I'd assumed the firebase function has access to Cloud Logging. But perhaps it needs additional permission? I can't find any reference to where this should be set on that page though.
// Logging, logName, region, functions are provided by the surrounding app
const logging = new Logging()
const log = logging.log(logName)
const METADATA = {
resource: {
type: 'cloud_function',
labels: {
function_name: 'CustomLog',
region
}
}
};
exports = module.exports = functions.https.onCall(async data => {
const exVersion = 6
const exId = data.exId
console.log('***** exVersion:', exVersion, 'exId:', exId) // exId from caller
const entry = log.entry(METADATA, data.error) // data.error from caller
console.log('METADATA:', METADATA) // Shows in Logs Explorer
console.log('entry:', entry) // Shows in Logs Explorer
log.write(entry) // Results in Error: 7 PERMISSION_DENIED
return {
exVersion,
exId,
}
})
If I run it from the CLI using firebase function:shell, the log entry is created correctly, so I'm pretty confident the code is correct.
OK, I finally tracked it down. According to this answer, the service account used by firebase functions is {project-id}#appspot.gserviceaccount.com, and in my project, that account did not have the 'Logs Writer' role. Adding that role solves the problem.
I find it odd that the firebase functions don't need that role to log messages using console.log(), but perhaps that call is intercepted by the functions environment, and the logs are written as a different service account. It also explains why the functions running locally were able to write the logs, as they run using the 'owner' service account, which has full access.
According to the Firebase documentation page you have linked:
The recommended solution for logging from a function is to use the
logger SDK. You can instead use standard JavaScript logging calls such
as console.log and console.error, but you first need to require a
special module to patch the standard methods to work correctly:
require("firebase-functions/lib/logger/compat");
Once you have required the logger compatibility module, you can use console.log() methods as normal in your code.
Thus you might to require this library, however I am not sure this is producing your "Error: 7 PERMISSION_DENIED error, but you might also try some solutions that have worked for some members of the community.
Perhaps the logging API is not enabled in your project. You'll get a permission denied error when attempting to use it in that case.
It's a couple levels in, but the guide you linked points to
https://github.com/googleapis/nodejs-logging#before-you-begin, which includes a step to "Enable the Cloud Logging API"
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
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.
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.