I'm trying to send back simple value from firebase but error appearing like this
mycode is :
exports.getTotalPrice = functions.https.onRequest((req, res) => {
admin.database().ref('carresult').once('value').then(function(snapshot) {
var totalPrice = snapshot.val().price;
res.status(200).send(totalPrice);
});
});
ps. In error 65000 is the value I need it to send back.
The Express documentation for res.send([body]) indicates:
The body parameter can be a Buffer object, a String, an object, or an
Array
In your database, /carresult/price is likely stored as a number, making totalPrice an invalid parameter to send(). Your options are to store it as a String convert it to a String before passing to send(), or leave it a number and send it back as a property of an object: send({price: totalPrice}).
exports.getTotalPrice = functions.https.onRequest((req, res) => {
admin.database().ref('carresult').once('value').then(function(snapshot) {
var totalPrice = snapshot.val().price;
res.status(200).send(String(totalPrice)); // <= ADDED String()
});
});
Also note that performing a database read (asynchronous) in an HTTPS function is risky, as Frank van Puffelen explains in this answer:
Note that this is a tricky pattern. The call to the database happens
asynchronously and may take some time to complete. While waiting for
that, the HTTP function may time out and be terminated by the Google
Cloud Functions system...As a general rule I'd recommend using a Firebase Database SDK or its REST API to access the database and not rely on a HTTP function as middleware.
Related
I am trying to make an API GET request, using React Query's useInfiniteQuery hook, that uses data from a Next Auth session token in the query string.
I have a callback in /api/auth/[...nextauth.ts] to send extra userData to my session token.
There are two relevant pages on the client side. Let's call them /pages/index.tsx and /hooks/useApiData.ts. This is what they look like, for all intents and purposes:
// pages/index.tsx
export default function Page() {
const {data, fetchNextPage, isLoading, isError} = useCourseData()
if (isLoading) return <main />
return <main>
<InfiniteScroller fetchMore={fetchNextPage}>
{data?.pages?.map(page => page?.results?.map(item: string => item))}
</InfiniteScroller>
</main>
}
// hooks/useApiData.ts
async function fetchPage(pageParam: string) {
const response = await fetch(pageParam)
return await response.json()
}
export default function useApiData() {
const {data: session} = useSession()
const init = `/api?userData=${session?.user?.userData}`
return useInfiniteQuery('query',
({pageParam = init}) => fetchPage(pageParam),
{getNextPageParam: prevPage => prevPage.next ?? undefined}
)
}
My initial request gets sent to the API as /api?userData=undefined. The extra data is definitely making its way into the token.
I can place the data from my session in the DOM via the render function of /pages/index.tsx, so I figure the problem is something to do with custom hooks running before the session context is ready, or something like that... I don't understand the mechanics of hooks well enough to figure that out.
I've been looking for answers for a long time, and I'm surprised not to have found a single person with the same issue. These are not unpopular packages and I guess a lot of people are using them in conjunction to achieve what I'm attempting here, so I figure I must be doing something especially dumb. But what?!
How can I get the data from my Next Auth session into my React Query request? And for bonus points, why is the session data not available when the request is sent in my custom hook?
When a user triggers a function there’s a POST request going away to a partner. Within the body I need to include a unique endpoint callbackURL with an Id so they can send me status updates linked with a specific user. How can I accomplish that? I know how to setup static endpoints, but not create new ones for every request.
As Doug said in his comment above, you don't need a new URL (i.e. a new endpoint) for each different id. You can deploy only one HTTP Cloud Function (which exposes one endpoint) and, in the Cloud Function, you extract the value of id from the Request object with its originalUrl property, as follows:
exports.myWebhook = functions.https.onRequest((req, res) => {
const urlArray = req.originalUrl.split('/');
console.log(urlArray);
console.log(urlArray[1]);
const id = urlArray[1];
//Do whatever you need with id
//.....
//If you want to test that it works with a browser, you can send it back as a response to the browser
res.send(urlArray[1]);
});
You then call this Cloud Function with the following URI:
https://us-central1-yourprojectname.cloudfunctions.net/myWebhook/id/callback
Note that it is also possible to extract values from the Request body, see https://firebase.google.com/docs/functions/http-events?authuser=0#read_values_from_the_request.
I'm using firebase admin sdk in my cloud functions and I'm getting error randomly in some executions when trying to get a user by uid .
let userRecord = await admin.auth().getUser(userId);
The error details are:
{"error":{"code":400,"message":"TOO_MANY_ATTEMPTS_TRY_LATER",
"errors":[{ "message":"TOO_MANY_ATTEMPTS_TRY_LATER",
"domain":"global","reason":"invalid"}]
}
}
My cloud function executes on a real time database write and can be triggered for multiple users. In total I have 4 auth function calls in one execution first is the above one, second call is to again get user by uid or email, third call is generateEmailVerificationLink and the last call is generatePasswordResetLink.
I have checked the rate limits in documentation for auth but there is no mention of rate limit for these operation. Also the error TOO_MANY_ATTEMPTS_TRY_LATER was only mentioned in REST API for sign up with email password.
If this error is due to rate limit what should I change to prevent this error given these 4 calls are necessary for the operation needed on database write?.
EDIT:
I have identified the actual call which is throwing too many attempts error. The calls auth().generateEmailVerificationLink() and auth().generatePasswordResetLink() throw this error when called too many times.
I called these two in loop with 100 iterations and waited for the promises. The first executions finishes without any errors i.e. 200 requests. But starting second execution as soon as the first one ends will throw the error of too many attempts. So I think these two calls have limit. Now I'm trying to reduce these calls and reuse the link information. Other calls like getUserByEmail works fine.
let promises = [];
let auth = admin.auth();
let hrstart = process.hrtime()
for (let i = 0; i < 100; i++) {
promises.push(auth.getUserByEmail("user email"));
promises.push(auth.generateEmailVerificationLink("user email", {url: `https://app.firebaseapp.com/path`}));
promises.push(auth.generatePasswordResetLink("user email", {url: `https://app.firebaseapp.com/path`}));
}
Promise.all(promises)
.then(value => {
let hrend = process.hrtime(hrstart);
console.log(hrend);
// console.log(value)
});
The error was specifically in the operation auth.createEmailLink. This function has following limit: 20QPS/I.P address where QPS is (query per second). This limit can be increased by submitting the use case to Firebase.
I got this information from firebase support after submitting my issue.
Link to my github issue: https://github.com/firebase/firebase-admin-node/issues/458
I was way under 20QPS but was receiving this exception. In fact, it would always throw the TOO_MANY_ATTEMPTS_TRY_LATER exception on the 2nd attempt.
It turned out to be usage of FirebaseAuth.DefaultInstance instead of instantiating a static instance thusly:
In class definition:
private readonly FirebaseApp _firebase;
In class constructor:
_firebase = FirebaseAdmin.FirebaseApp.Create();
In function:
var auth = FirebaseAuth.GetAuth(_firebase);
var actionCodeSettings = new ActionCodeSettings()
{
...
};
var link = await auth.GenerateEmailVerificationLinkAsync(email, actionCodeSettings);
return link;
In addition to the answer mentioned in https://stackoverflow.com/a/54782967/5515861, I want to add another solution if you found this issue while trying to create custom email verification.
Inspired by the response in this GitHub isssue https://github.com/firebase/firebase-admin-node/issues/458#issuecomment-933161448 .
I am also seeing this issue. I have not ran admin.auth().generateEmailVerificationLink in over 24hrs (from anywhere else or any user at all) and called it just now only one time (while deployed in the prod functions environment) and got this 400 TOO_MANY_ATTEMPTS_TRY_LATER error ...
But, the client did also call the Firebase.auth.currentUser.sendEmailVerification() method around same time (obviously different IP).
Could that be the issue?
My solution to this issue is by adding a retry. e.g.
exports.sendWelcomeEmail = functions.runWith({failurePolicy: true}).auth.user().onCreate(async (user) => {
functions.logger.log("Running email...");
const email = user.email;
const displayName = user.displayName;
const link = await auth.generateEmailVerificationLink(email, {
url: 'https://mpj.io',
});
await sendWelcomeEmail(email, displayName, link);
});
The .runWith({failurePolicy: true}) is key.
It s giving you an error because your cloud functions/backend call the generateEmailVerificationLink while at the same time the default behaviour of the Firebase is also doing the same and it is counted as 20QPS. It some weird Google Rate Limit accounting rule. So my solution is just to add a retry.
The Downside is, it is calling twice, so if the call is billable, it might be billable twice.
I have a Realtime DB in Firebase and have setup an agent in Google Cloud's Dialogflow. This agent agent is fetching data about bus route names. The end user is asked for a bus number and the agent should get relevant info based on that route number. I can call the database but only for a set bus number.
So for example below I can pull in bus info for 100 based on having the snapshot.child set to 100. But I want the snapshot.child to change based on the askBus parameter from Dialogflow. Any suggestions?
function handleBus(agent) {
const bus = agent.parameters.bus;
agent.add(`Thank you...`);
return admin.database().ref('Routes').once("value").then((snapshot) => {
var routeInfo = snapshot.child('100/route_desc').val();
var routeName = snapshot.child('100/route_long_name').val();
agent.add(`Bus info is ` + routeInfo + ' and is called ' + routeName);
In general, the best way to handle this is to reference the node of the bus number as part of setting up the path to the query. Getting it once you have the result is certainly possible, but means you're pulling in a lot more data than you need to for each query.
But there are a few ways to do this.
The one most similar to how you're doing it now is to generate a string that includes the route number. This example shows how to do it using a back-quote, which is available in the most recent JavaScript, or you can just do string concatenation:
function handleBus(agent) {
const bus = agent.parameters.bus;
agent.add(`Thank you...`);
return admin.database().ref('Routes').once("value").then((snapshot) => {
var routeInfo = snapshot.child(`${bus}/route_desc`).val();
var routeName = snapshot.child(`${bus}/route_long_name`).val();
agent.add(`Bus info is ` + routeInfo + ' and is called ' + routeName);
But if you're just looking for the information from that route, you can setup the reference to the database to include the route, get the entire result and its value, and then treat this as a JavaScript object.
function handleBus(agent) {
const bus = agent.parameters.bus;
agent.add(`Thank you...`);
return admin.database().ref('Routes').child(bus).once("value").then((snapshot) => {
var route = snapshot.val();
var routeInfo = route['route_desc'];
var routeName = route['route_long_name'];
agent.add(`Bus info is ` + routeInfo + ' and is called ' + routeName);
As an aside, I want to point out that you're using Promises perfectly. That is a trap many people fall into, and you've done a good job querying the value through a Promise, handling it as part of Promise fulfillment, and returning a Promise in your handler.
In the webhook use async call to firebase to fetch the bus information.
Fetch the parameter value.
Access Firebase DB.
Fetch information based on parameter using async call.
Use promise to reply back with the correct response. See this for responding via promise.
Promise would be used inside your Firebase function when it fetches the DB information.
I use a Cloud Function to generate a short unique URL on a record on the 'onWrite' event, and save it. This works well, but when I save a record from my Ember app using EmberFire, I do get a model back as an argument to a callback, but the URL of this model is undefined. Is there a way to return this back to the client? Or do I need to query the record to get the generated URL?
This is how my Cloud Function code looks:
exports.generateUrl = functions.database.ref('/tournaments/{tid}')
.onWrite(event => {
if (event.data.previous.exists()) {
return;
}
if (!event.data.exists()) {
return;
}
const url = shortid.generate();
return event.data.ref.update({ url });
});
Here is my component that saves data through form submission. I'm using an add-on called ember-changeset to handle some validations, but this shouldn't be related to the issue.
export default Ember.Component.extend({
submit(e) {
e.preventDefault();
let snapshot = this.changeset.snapshot();
return this.changeset
.cast(Object.keys(this.get('schema')))
.validate()
.then(() => {
if (this.changeset.get('isValid')) {
return this.changeset
.save()
.then((result) => {
// Here, result.get('url') is undefined.
})
}
})
}
});
If you have a function that writes new data back to a location in the database after a write, you'll have to keep listening to that location on the client in order to get that data back. Don't use a one-time read (once()), use a persistent listener (on()), and in that listener, make sure you're getting the URL or whatever you expect to be generated by the function. Then remove that listener if you don't need it any more.
(Sorry, I don't know Ember or what abstractions it provides around Realtime Database - I'm giving you the plain JavaScript API methods you'd use on a reference.)