I am trying to write a firebase cloud function that essentially does 2 things for now:
1) gets triggered whenever a user comes online/offline
2) fetch all the conversations for this user.
The below code is able to perform Step 1, but returns Function returned undefined, expected Promise or value Error in the console.
I am not sure how to write Promises and this is my first Firebase function. Please assist.
exports.offlineHandler = functions.database.ref('/users/{userid}/status')
.onUpdate((change, context) => {
const status = change.after.val();
const userId = context.params.userid;
console.log('the user is now ', status, "with mobile no ", userId);
// fetch a users conversations list
if (status === "offline") {
console.log("offline exec start");
return fetchUserConversations(userId)
.then(results => {
console.log("offline exec end");
for (result in results) {
console.log("id is", result);
}
return results;
}).catch(err => {
console.error("An error has occurred.", err);
return err;
});
} else {
console.log("user came online");
}
return null;
});
function fetchUserConversations(userId) {
return admin.firestore().collection('users/${userId}/conversations').get()
.then(snapshot => {
var conversations = [];
snapshot.forEach(doc => {
console.log("This is conversation id => ", doc.id);
conversations.concat(doc.id);
});
return conversations;
//return conversations;
}).catch(err => {
console.error(err);
return err;
});
}
Syntactically, you seem to be just be missing a return before fetchUserConversations(userId).... So:
return fetchUserConversations(userId).then(result=>{
To learn more about promises in Cloud Functions, you'd do best to study the Firebase documentation on sync, async, and promises, and Doug Stevenson's excellent video series on using promises in Cloud Functions.
A more fundamental problem with your code is that it doesn't do anything with the conversations it fetches for the user. It seems that you're trying to return them from the Cloud Function, but Cloud Functions that are triggered by a database write cannot return anything. Such Functions are triggered by a database write, not by a user operation.
While in your case it was a user operation (coming back online) that triggered the Cloud Function, that's an indirect trigger and you can't return anything to the user from the Cloud Function.
You have two main options here:
Write the conversations somewhere in the database that the user then looks them up.
Change the Cloud Functions trigger type to be HTTPS or Callable, and then call this Cloud Function from your application code when the user goes back online.
Related
I have an application using Node.js/Express. Within this code I have the following promise designed to check if an email already exists in my (PostGres) database:
//queries.js
const checkEmail = function(mail) {
return new Promise(function(resolve, reject) {
pool.query('SELECT * FROM clients WHERE email = $1', [mail], function(error, results) {
if (error) {
reject(new Error('Client email NOT LOCATED in database!'));
} else {
resolve(results.rows[0]);
}
}) //pool.query
}); //new promise
}
In my 'main (server.js)' script file, I have a route which is called upon submission of a 'signup' form. When the post to this route is processed...I run the script above to check if the passed email address is already located in the database, along with various other 'hashing' routines:
My code is as follows:
//server.js
const db = require('./queries');
const traffic = require('./traffic');
const shortid = require('shortid');
...
app.post('/_join_up', function(req, res) {
if (!req.body) {
console.log('ERROR: req.body has NOT been returned...');
return res.sendStatus(400)
}
var newHash, newName;
var client = req.body.client_email;
var creds = req.body.client_pword;
var newToken = shortid.generate();
var firstname = req.body.client_alias;
db.sanitation(client, creds, firstname).then(
function(direction) {
console.log('USER-SUPPLIED DATA HAS PASSED INSPECTION');
}
).then(
db.checkEmail(client).then(
function(foundUser) {
console.log('HEY THERE IS ALREADY A USER WITH THAT EMAIL!', foundUser);
},
function(error) {
console.log('USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...');
}
)).then(
traffic.hashPassword(creds).then(
function(hashedPassword) {
console.log('PASSWORD HASHED');
newHash = hashedPassword;
},
function(error) {
console.log('UNABLE TO HASH PASSWORD...' + error);
}
)).then(
traffic.hashUsername(firstname).then(
function(hashedName) {
console.log('NAME HASHED');
newName = hashedName;
},
function(error) {
console.log('UNABLE TO HASH NAME...' + error);
}
)).then(
db.createUser(client, newName, newHash, newToken).then(
function(data) {
console.log('REGISTERED A NEW CLIENT JOIN...!!!');
res.redirect('/landing'); //route to 'landing' page...
},
function(error) {
console.log('UNABLE TO CREATE NEW USER...' + error);
}
))
.catch(function(error) {
console.log('THERE WAS AN ERROR IN THE SEQUENTIAL PROCESSING OF THE USER-SUPPLIED INFORMATION...' + error);
res.redirect('/');
});
}); //POST '_join_up' is used to register NEW clients...
My issue is the '.then' statements do not appear to run sequentially. I was under the impression such commands only run one after the other...with each running only when the previous has completed. This is based upon the logs which show the readout of the 'console.log' statements:
USER-SUPPLIED DATA HAS PASSED INSPECTION
PASSWORD HASHED
NAME HASHED
UNABLE TO CREATE NEW USER...Error: Unable to create new CLIENT JOIN!
USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...
As mentioned previously, I am under the impression the '.then' statements should run synchronously, therefore the last statement ("USER EMAIL NOT CURRENTLY IN DATABASE...THEREFORE IT IS OK...") should in fact be after the first...before the "PASSWORD HASHED" according to the layout of the '.then' statements. Is this normal behavior...or do I have an error in my code?
Sorry for my confusion however I find '.then' statements and promises to be somewhat confusing for some reason. I thank you in advance.
TLDR - You must pass a function reference to .then() so the promise infrastructure can call that function later. You are not doing that in several places in your code.
A more specific example from your code:
You have several structures like this:
.then(db.createUser().then())
This is incorrect. This tells the interpreter to run db.createUser() immediately and pass its return result (a promise) to .then(). .then() will completely IGNORE anything you pass is that is not a function reference and your promises will not be properly chained.
Instead, you must pass a function reference to .then() something like this (not sure what execution logic you actually want):
.then(() => { return db.createUser.then()})
Then main point here is that if you're going to sequence asynchronous operations, then you must chain their promises which means you must not execute the 2nd until the first calls the function you pass to .then(). You weren't passing a function to .then(), you were executing a function immediately and then passing a promise to .then(p) which was completely ignored by .then() and your function was executed before the parent promise resolved.
FYI, sequencing a bunch of asynchronous operations (which it appears you are trying to do here) can take advantage of await instead of .then() and end up with much simpler looking code.
I am trying to use the admin sdk to check for a user phone number. when i check for a number in the database, it displays the result, but when i input a number not in the database, it throws an internal error.
below is a sample code of the functions index.js
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.checkPhoneNumber = functions.https.onCall(async (data, context) => {
const phoneNumber = await admin.auth().getUserByPhoneNumber(data.phoneNumber);
return phoneNumber;
})
front-end.js
toPress = () => {
const getNumberInText = '+12321123232';
const checkPhone = Firebase.functions.httpsCallable('checkPhoneNumber');
checkPhone({ phoneNumber: getNumberInText }).then((result) => {
console.log(result);
}).catch((error) => {
console.log(error);
});
}
below is the error i am getting when i input a number thats not in the auth
- node_modules\#firebase\functions\dist\index.cjs.js:59:32 in HttpsErrorImpl
- node_modules\#firebase\functions\dist\index.cjs.js:155:30 in _errorForResponse
- ... 14 more stack frames from framework internals
As you will read in the documentation of Callable Cloud Functions:
The client receives an error if the server threw an error or if the resulting promise was rejected.
If the error returned by the function is of type function.https.HttpsError, then the client receives the error code, message, and details from the server error. Otherwise, the error contains the message INTERNAL and the code INTERNAL.
Since you don't specifically manage errors in your Callable Cloud Function, you receive an INTERNAL error.
So, if you want to get more details in your front-end, you need to handle the errors in your Cloud Function, as explained here in the doc.
For example, you could modify it as follows:
exports.checkPhoneNumber = functions.https.onCall(async (data, context) => {
try {
const phoneNumber = await admin.auth().getUserByPhoneNumber(data.phoneNumber);
return phoneNumber;
} catch (error) {
console.log(error.code);
if (error.code === 'auth/invalid-phone-number') {
throw new functions.https.HttpsError('not-found', 'No user found for this phone number');
}
}
})
We throw an error of type not-found (See all the possible Firebase Functions status codes here) in case the error code returned by the getUserByPhoneNumber() method is auth/invalid-phone-number (See all the possible error codes here).
You could refine this error handling code, by treating other errors returned by getUserByPhoneNumber() and sending to the client other specific status codes.
Here is a way I usually use to check if a field (ex. phone) exists in any of the documents in my collection.
For an example based on what you described here is a collection I have created:
The code to make a query for checking if a phone exists looks like this: (I'm using Node.Js)
let collref = db.collection('posts');
var phoneToCheck = '+123456789'
const phone1 = collref.where('phone', '==', phoneToCheck)
let query1 = phone1.get()
.then(snapshot => {
if (snapshot.empty) {
console.log('No matching documents.');
return;
}
snapshot.forEach(doc => {
console.log(doc.id, '=>', doc.data());
});
})
.catch(err => {
console.log('Error getting documents', err);
});
If a Document exists with that phone number the response is like this:
I no document has that phone number then the response is the following:
I am having few intents in my dialogflow agent for which I have written function using the inline editor of Dialogflow so in few intents, I am calling some APIS using Axios with some kind of logic and for this such intent, it is not able to prompt message on test console sometimes but easily printing logs in the console.
I am attaching some dummy code -:
function someintent(agent){
return new Promise((resolve, reject) => { // Tried using promises as well but didn't work
const parameter1 = agent.parameters.parameter1;
const parameter2 = agent.parameters.parameter2;
// Some of the code is just logic, Problem mainly occurs at line no 39
if (parameter1 != ''){
if (parameter1.toString().length !=9){
agent.add('Please enter valid 9 digit number');
agent.context.set({
'name':'someintent-followup',
'lifespan': 0
});
}
else{
if(isNaN(parameter1)){
agent.add('Please enter integer');
agent.context.set({
'name':'previous-followup'
});
}
/**
* Main area which creates problem is below code.
*
*/
else{
agent.add('What is the nature of your Business ?'); // This is not working sometimes
return axios.post('http://some-test-api/',
{"parameters1": parameter1}).then((result) => {
console.log(result.data);
console.log('Store data to api');
//agent.add('What is the nature of your Business ?'); // I tried here also which is also not working sometimes
});
}
}
}
else{
agent.add('What is the nature of your Business ?');
return axios.post('http://some-test-api/',
{"parameters2": parameter2}).then((result) => {
console.log(result.data);
console.log('Store data to api');
});
}
resolve(); // Resolving promise
}
As per my understanding, the issue is if the intent is having a quite big logic and that too with some API Call then it has timeout and callback function issue (maybe) which creates a problem of not printing response in the interface(test console)
I really required help with this. Thanks in advance.
It sounds like there are two potential issues you have:
The first is that it sounds like you're concerned that the async call with axios will take "too long". This may be a concern if the call plus your other processing takes more than a few seconds.
But what looks more likely is that how you're making your call with axios and using Promises is causing an issue.
With the code you're showing, you're returning the new Promise object, but in the execution code you're returning the axios Promise, so the call to resolve() never gets made. You should only be calling resolve() when the async operation has, itself, resolved.
But since axios.post() returns a Promise, you don't need to wrap it in your own Promise. You just need to return the Promise/then chain that is part of each axios call.
So your code might look something like (untested):
function someintent(agent){
const parameter1 = agent.parameters.parameter1;
const parameter2 = agent.parameters.parameter2;
if (parameter1 != ''){
if (parameter1.toString().length !=9){
agent.add('Please enter valid 9 digit number');
agent.context.set({
'name':'someintent-followup',
'lifespan': 0
});
}else{
if(isNaN(parameter1)){
agent.add('Please enter integer');
agent.context.set({
'name':'previous-followup'
});
}else{
return axios.post('http://some-test-api/',{"parameters1": parameter1})
.then((result) => {
console.log(result.data);
console.log('Store data to api');
agent.add('What is the nature of your Business ?');
});
}
}
}else{
return axios.post('http://some-test-api/',{"parameters2": parameter2})
.then((result) => {
console.log(result.data);
console.log('Store data to api');
agent.add('What is the nature of your Business ?');
});
}
}
I have a webhook to recieve facebook messenger events in a cloud function like so:
export const facebookMessengerHook = functions.https.onRequest(async (req: express.Request, res: express.Response) => {
console.log(req);
console.log(req.method);
console.log(req.body);
if (req.method == "POST") {
const body = req.body;
console.log(body);
// Checks this is an event from a page subscription
if (body.object === 'page') {
res.status(200).send('EVENT_RECEIVED');
// Iterates over each entry - there may be multiple if batched
for (const entry of body.entry) {
// will only ever contain one message, so we get index 0
const webhook_data = entry.messaging[0];
console.log(webhook_data);
try {
// v THAT PART HERE v
const user = await admin.firestore().collection('users')
.where('facebookMessengerId', '==', webhook_data.sender.id)
.get();
// ^ THAT PART HERE ^
console.log(user);
} catch (e) {
console.log('No user');
}
}
}
else {
// Returns a '404 Not Found' if event is not from a page subscription
res.sendStatus(404);
}
}
});
It does not log anything, unless I comment out the marked part in the snippet.
Can someone please explain to me why and how to fix this, because I need to make a call to firestore and I also need the console.log for debug purposes?
Thanks for any help!
The problem most probably comes from the fact that by doing
res.status(200).send('EVENT_RECEIVED');
you actually indicate to the Cloud Function platform that the Cloud Function can be terminated before the rest of the asynchronous work (the set of calls to the get() method) is done. See the following official video form more detail. In other words, the Cloud Function is terminated before the promises returned by the get() method are resolved.
So you should modify your code as follows:
//....
if (body.object === 'page') {
// Iterates over each entry - there may be multiple if batched
for (const entry of body.entry) {
// will only ever contain one message, so we get index 0
const webhook_data = entry.messaging[0];
console.log(webhook_data);
try {
const user = await admin.firestore().collection('users')
.where('facebookMessengerId', '==', webhook_data.sender.id)
.get();
console.log(user);
} catch (e) {
console.log('No user');
//Here throw an error to be catched at an upper level
}
}
res.status(200).send('EVENT_RECEIVED');
}
//....
Note that you may use Promise.all() since you issue a series of fetch to the database. But with your code it is impossible to confirm that, because it does not show the exact use of these fetches.
I am using DialogFlow V1 node.js webhook and I have a problem.
I have an intent that will connect to the Spotify API to get tracks for my chatbot:
const {DialogflowApp} = require('actions-on-google');
function spotifyIntent(app) {
//Get search parameters
[...]
return asyncFunction(query).then(function (message) {
this.app.ask(message);
return Promise.resolve();
}).catch(function (err) {
this.app.ask("Une erreur est apparue: "+err);
return Promise.resolve();
})
}
with my async function being:
function asyncFunction(query) {
spotifyApi.clientCredentialsGrant()
.then(function(data) {
// spotifyApi.setAccessToken(data.body['access_token']);
return spotifyApi.searchTracks(query);
}).then(function(data) {
const link = data.body.tracks.items[0].preview_url;
let speech = '<speak>Here is the first result found.<audio src="'+ link +'">I didn\'t found it</audio></speak>';
return Promise.resolve(speech);
}).catch(function(err) {
return Promise.reject(err);
});
}
A Promise is hidden in the call of clientCredentialsGrant() and searchTracks();
My intent is called by the classic map with action: intentFunction.
I read here that the ask method should work but it doesn't for me. I tried the version with the Promise and with a callback but when I simulate my code I always get the answer:
"message": "Failed to parse Dialogflow response into AppResponse, exception thrown with message: Empty speech response
I don't understand what I'm doing wrong here? I know the problem come from the request being async but it should work fine with the Promise or the callback because right now it returns instantly?
It returns instantly because, although asyncFunction() calls something that returns a Promise, and although the then() and catch() portions return a Promise... asyncFunction() itself does not return a promise. In fact, it doesn't return anything explicitly, so it returns undefined.
You probably want that code to be
function asyncFunction(query) {
return spotifyApi.clientCredentialsGrant()
.then(function(data) {
// spotifyApi.setAccessToken(data.body['access_token']);
return spotifyApi.searchTracks(query);
}).then(function(data) {
const link = data.body.tracks.items[0].preview_url;
let speech = '<speak>Here is the first result found.<audio src="'+ link +'">I didn\'t found it</audio></speak>';
return Promise.resolve(speech);
}).catch(function(err) {
return Promise.reject(err);
});
}
Note the change in the first line that adds the return statement.
This is a common pattern (and a frequently overlooked problem) when dealing with async functions that need to return a Promise.