I'm trying to create a simple Firebase function using Firebase Auth that returns a user's uid if I send it a valid email address. No matter what I change I always get the same error:
Error fetching user data: TypeError [ERR_INVALID_ARG_TYPE]: The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received type function ([Function (anonymous)])
To call it I use: ...cloudfunctions.net/checkemail?email=wyn#wyn.com
I have tried wyn%40wyn.com as well. The user does exist.
I'm obviously doing something wrong. But what?
export const checkemail = functions
.region("deleted")
.https.onRequest(async (request, response) =>{
const email = String(request.query.email);
await admin.auth().getUserByEmail(email)
.then((userRecord)=> {
const data = userRecord.toJSON;
response.send(data);
})
.catch(function(error) {
response.send("Error fetching user data: "+ error);
});
});
toJSON is a method. Therefore it needs brackets: const data = userRecord.toJSON()
Related
I have a cloud functions that writes a user document, for example an access token
Then after that function is called and awaited I do a get to read the document.
However, the document read is old.
The cloud function writes the document like this:
await admin.firestore().collection('garminHealthAPITokens').doc(userID).set({
oauthToken: urlParams.get('oauth_token'),
oauthTokenSecret: urlParams.get('oauth_token_secret'),
state: crypto.randomBytes(20).toString('hex')
})
// Send the response wit hte prepeared stuff to the client and let him handle the state etc
res.send({
redirect_url: REQUEST_TOKEN_CONFIRMATION_URI,
})
Here is part of the code on the client side that:
const redirectURI = await this.userService.getCurrentUserGarminHealthAPIRedirectURI(); // Function updates token
// This doesn't return the written token of the above but rather an older (prev) one
const token = await this.userService.getGarminHealthAPITokenAsPromise(this.user);
Here is how I call the the AngularFire document get
public async getGarminHealthAPITokenAsPromise(user: User): Promise<{oauthToken: string, oauthTokenSecret: string, state: string}> {
return this.afs
.collection('garminHealthAPITokens')
.doc(user.uid)
.get({source: 'server'})
.pipe(catchError(error => {
return [];
}))
.pipe(take(1))
.pipe(map((doc) => doc.data()))
.toPromise();
}
There is an explicit get from the server there but the results are not fresh
What am I missing?
Don't know how to get responce from cloud function in flutter.
My cloud function
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.testDemo = functions.https.onRequest((request, response) => {
return response.status(200).json({msg:"Hello from Firebase!"});
});
My flutter code
///Getting an instance of the callable function:
try {
final HttpsCallable callable = CloudFunctions.instance.getHttpsCallable(
functionName: 'testDemo',);
///Calling the function with parameters:
dynamic resp = await callable.call();
print("this is responce from firebase $resp");
} on CloudFunctionsException catch (e) {
print('caught firebase functions exception');
print(e.code);
print(e.message);
print(e.details);
} catch (e) {
print('caught generic exception');
print(e);
}
flutter: caught firebase functions exception
flutter: INTERNAL
flutter: Response is missing data field.
flutter: null
Use
exports.testDemo = functions.https.onCall((data, context) => {
return {msg:"Hello from Firebase!"};
});
in cloud functions. Callable is different than Request
When you call the functions you need to add the parameters:
change:
// Calling a function without parameters is a different function!
dynamic resp = await callable.call();
to:
dynamic resp = await callable.call(
<String, dynamic>{
'YOUR_PARAMETER_NAME': 'YOUR_PARAMETER_VALUE',
},
);
as described here
then to print the response:
print(resp.data)
print(resp.data['msg'])
The Firebase Functions for flutter example here and here
You must explicity put the attribute "data" in the json of response.
Like:
response.send({
"status" : success,
"data" : "some... data"
});
you can convert to .onCall type function if you want to call function by it's name. aldobaie's answer
or for onRequest type function:
we need to call it as RESTful APIs.
While deploying to firebase, we'll get functions URL
or we can get function URL from dashboard of functions from firebase.
URL is in formate of:
https://us-central1-< projectName >.cloudfunctions.net/< functionName >
call it as any other RESTful API. plus point of using it as API is that data field in response isn't mandatory and freedom of response formate.
I have created a callable Cloud Function to read data from Firebase and send back the results to the client, however, only "null" is being returned to the client.
exports.user_get = functions.https.onCall((data, context) => {
if (context.auth && data) {
return admin.firestore().doc("users/" + context.auth.uid).get()
.then(function (doc) {
return { doc.data() };
})
.catch(function (error) {
console.log(error);
return error;
})
} return
});
I just reproduced your case connecting from a Cloud Function with a Firestore database and retriving data. As I can see you are trying to access the field in a wrong way when you are using "users/" + context.auth.uid, the method can't find the field so its returning a null value.
I just followed this Quickstart using a server client library documentation to populate a Firestore database and make a Get from it with node.js.
After that i followed this Deploying from GCP Console documentation in order to deploy a HTTP triggered Cloud Function with the following function
exports.helloWorld = (req, res) => {
firestore.collection('users').get()
.then((snapshot) => {
snapshot.forEach((doc) => {
console.log(doc.id, '=>', doc.data().born);
let ans = {
date : doc.data().born
};
res.status(200).send(ans);
});
})
And this is returning the desired field.
You can take a look of my entire example code here
This is because you are making a query from a database firestore, however the cloud support team has made it very cool to protect your applications from data leakages and so in a callable function as the name suggest you can only return data you passed to the same callable function through the data parameter and nothing else. if you try to access a database i suggest you use an onRequest Function and use the endpoint to get you data. that way you not only protect your database but avoid data and memory leakage.
examples of what you can return from a callable function
exports.sayHello = functions.https.onCall((data, context) => {
const name = data.name;
console.log(`hello ${name}`);
return `It was really fun working with you ${name}`;
});
first create a function in your index.js file and accept data through the data parameter but as i said you can only return data you passed through the data parameter.
now call the function
this is in the frontend code (attach an event listener to a button or something and trigger it
/* jsut say hello from firebase */
callButton.addEventListener('click', () => {
const sayHello = firebase.functions().httpsCallable('getAllUsers');
sayHello().then(resutls => {
console.log("users >>> ", resutls);
});
});
you can get your data using an onRequest like so
/* get users */
exports.getAllUsers = functions.https.onRequest((request, response) => {
cors(request, response, () => {
const data = admin.firestore().collection("users");
const users = [];
data.get().then((snapshot) => {
snapshot.docs.forEach((doc) => {
users.push(doc.data());
});
return response.status(200).send(users);
});
});
});
using a fetch() in your frontend code to get the response of the new onRequest function you can get the endpoint to the function in your firebase console dashboard.
but not that to hit the endpoint from your frontend code you need to add cors to your firebase cloud functions to allow access to the endpoint.
you can do that by just adding this line to the top of your index.js file of the firebase functions directory
const cors = require("cors")({origin: true});
Looking through the Firestore documentation, I see many examples of functions.firestore.document but I don't see any examples of functions.firestore.collection. Firestore syntax is
firebase.firestore().collection('...').doc('...')
I get an error message with
firebase.firestore().document('...')
Yet in Cloud Functions with this code:
exports.myFunction = functions.firestore.collection('...').doc('...').onUpdate(event => {
on deploy I get an error message:
TypeError: functions.firestore.collection is not a function
When I change the code to
exports.getWatsonTokenFirestore = functions.firestore.document('...').onUpdate(event => {
I don't get an error message on deploy.
Why does Cloud Functions appear to have a different data structure than Cloud Firestore?
Here's my full Cloud Function. My collection is User_Login_Event and my document is Toggle_Value:
exports.getWatsonTokenFS = functions.firestore.document('User_Login_Event/{Toggle_Value}').onUpdate(event => {
var username = 'TDK',
password = 'swordfish',
url = 'https://' + username + ':' + password + '#stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api';
request({url: url}, function (error, response, body) {
admin.firestore().collection('IBM_Watson_Token').document('Token_Value').update('token');
});
return 0; // prevents an error message "Function returned undefined, expected Promise or value"
});
The function deploys without error but when it executes I get this error message:
TypeError: firebase.firestore is not a function
I'm confused as firebase.firestore isn't in my Cloud Function. It's in my Angular front-end code in various places, without a problem. What is this error message referring to? I tried changing the line
admin.firestore().collection('IBM_Watson_Token').document('Token_Value').update('token');
to
firebase.firestore().collection('IBM_Watson_Token').document('Token_Value').update('token');
and to
console.log("getWatsonTokenFS response");
but I got the same error message.
Yes. You should format it as...
exports.getWatsonTokenFirestore = functions.firestore.document('myCollection/{documentId}').onUpdate(event => {
// code
});
collection and doc are methods within firebase.firestore. To access them via functions.firestore, you must use document.
You can see a full list of Classes for Cloud Firestore and the latest SDK for Cloud Functions for Firebase
Update
I've been working on your code. I've added in all of the dependencies and initialization, which I assume that you have in your code. I can't see where you're using any data from Firestore in your IBM Watson request and I can't see how you're writing any of the returned data back to Firestore. As I'm not familiar with your request method, I've commented it out, to give you what should be a working example of an update to Firestore and writes something back. I also edited some of your code to make it more readable and changed the Cloud Functions code to reflect v1.0.0, released today (I've been testing it for a while) :)
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp();
const firestore = admin.firestore();
exports.getWatsonTokenFS = functions.firestore
.document('User_Login_Event/{Toggle_Value}')
.onUpdate((snap, context) => {
let username = 'TDK';
let password = 'swordfish';
let url = `https://${username}:${password}#stream.watsonplatform.net/authorization/api/v1/token?url=https://stream.watsonplatform.net/speech-to-text/api`;
// request({url}, function (error, response, body) {
// firestore.doc(`${IBM_Watson_Token}/${Token_Value}`).update('token');
// });
return firestore.doc(`IBM_Watson_Token/Token_Value`).update('token')
.then(response => {
return Promise.resolve();
})
.catch(err => {
return Promise.reject(err);
});
});
Now that Firebase has updated firebase-admin to 5.12.0 and firebase-functions to 1.0.1 my test function is working. The function that Jason Berryman wrote is correct except for two lines. Jason wrote:
.onUpdate((snap, context) => {
That should be
.onUpdate((change, context) => {
Secondly, Jason wrote:
return firestore.doc(`IBM_Watson_Token/Token_Value`).update('token')
The corrected line is:
return firestore.collection('IBM_Watson_Token').doc('Token_Value').update({
token: 'newToken'
})
I made two changes in Jason's code. First, I changed the location syntax; more on this below. Second, update() requires an object as the argument.
To show the syntax for locations, I wrote a simple Cloud Function that triggers when a value at a location in Cloud Firestore changes, and then writes a new value to a different location in Cloud Firestore. I removed the line const firestore = admin.firestore(); to make the code more clear:
const admin = require('firebase-admin');
const functions = require('firebase-functions');
admin.initializeApp();
exports.testFunction = functions.firestore.document('triggerCollection/{documentID}').onUpdate((change, context) => {
return admin.firestore().collection('writeCollection').doc('documentID').update({
token: 'newValue'
})
.then(response => {
return Promise.resolve();
})
.catch(err => {
return Promise.reject(err);
});
});
Let's compare three syntaxes for locations in Cloud Firestore. First, in the browser I use this syntax:
firebase.firestore().collection('myCollection').doc('documentID')
Next, in a Cloud Function trigger I use this syntax:
functions.firestore.document('myCollection/{documentID}')
Third, in the Cloud Function return I use this syntax:
admin.firestore().collection('myCollection').doc('documentID')
The first and last lines are the same except that from the browser you call Firebase with firebase, when from the server you call Firebase using the firebase-admin Node package, here aliased to admin.
The middle line is different. It's calling Firebase using the firebase-functions Node package, here aliased to functions.
In other words, Firebase is called using different libraries, depending on whether you're calling from the browser or the server (e.g., a Cloud Function), and whether in a Cloud Function you're calling a trigger or a return.
Cloud functions is triggered based on events happening in Firebase example in realtime database, authentication.
Cloud firestore is triggered based on events happening in Firestore which uses the concept of documents and collections.
As explained here:
https://firebase.google.com/docs/functions/firestore-events
The cloud firestore triggers are used when there is a change in a document.
I had the same problem. I used following
const getReceiverDataPromise = admin.firestore().doc('users/' + receiverUID).get();
const getSenderDataPromise = admin.firestore().doc('users/' + senderUID).get();
return Promise.all([getReceiverDataPromise, getSenderDataPromise]).then(results => {
const receiver = results[0].data();
console.log("receiver: ", receiver);
const sender = results[1].data();
console.log("sender: ", sender);
});
I am trying to send a basic use-case of sending FCM message using Cloud Functions for Firebase. The function is timing out and the message never got send. Here is the function.
exports.sendNotification = functions.https.onRequest((req, res) => {
const keyword = req.query.keyword;
const username = req.query.username;
var payload = {
data: {
SearchKeyword: keyword,
user: username
}
};
const token = "real_fcm_token";
return admin.messaging().sendToDevice(token, payload);
});
How can I update the above code block to be able to send a data message to a device?
In addition to returning the Promise from sendToDevice(), you must also send HTTP status. For example:
res.status(200).send('Success');
const token = "real_fcm_token";
return admin.messaging().sendToDevice(token, payload);