I am attempting to retrieve the boolean child (notificationsOn) of an object stored as a Firestore document to see if the rest of a function should be executed.
The overall function works to completion without this portion, but adding the portion from let threadDoc to the if statement presents a "threadDoc.get is not a function" error. I think my syntax is wrong but I don't know how, as a similar function works in a later part of the function:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp();
exports.sendDMNotification =functions.firestore.document('/dm_threads/{thread_id}/messages/{message_id}').onCreate((snapshot, context) => {
const newMessage = snapshot.data();
const senderName = newMessage.authorName;
const senderID = newMessage.authorUID;
const messageText = newMessage.message;
const recipientID = newMessage.recipientUID;
var notificationsOn = null;
let deviceTokenQuery = admin.firestore().collection(`/users/${recipientID}/device_tokens/`);
var idsToBeSorted = [senderID, recipientID];
idsToBeSorted.sort();
var threadID = idsToBeSorted[0] + idsToBeSorted[1];
console.log(recipientID);
console.log(threadID);
let threadDoc = admin.firestore().document(`users/${recipientID}/threads/${threadID}/`);
return threadDoc.get().then(doc => {
let notificationsOn = doc.data.notificationsOn;
console.log(notificationsOn);
if (notificationsOn !== false){
return deviceTokenQuery.get().then(querySnapshot => {
let tokenShapshot = querySnapshot.docs;
const notificationPromises = tokenShapshot.map(doc => {
let token_id = doc.data().tokenID;
const payload = {
data: {
title: senderName,
body: messageText,
senderID: senderID,
senderName: senderName
}
};
return admin.messaging().sendToDevice(token_id, payload).then(response => {
console.log("Notification sent: ", response);
})
.catch(error => {
console.log("Error sending message: ", error);
});
});
return Promise.all(notificationPromises);
});
}
return;
});
});
admin.firestore().document() was supposed to be admin.firestore().collection(...).doc(...)
This fixed my problem
I think you meant to say admin.firestore() instead of functions.firestore.
Related
I develop in iOS and this is the first time I'm coding in Typescript. I made my first Cloud Function that triggers when a new chat message is sent in RTDB and notifies the members that receive the message.
When I make the call to Firestore to get tokens of the user devices (userTokens) I get no errors and have the correct path, but the data returned from those promises don't show anything. When I log the "tokenList" it just says ["0"]. I think the error is when I push the promise to a list or resolve them, but I haven't managed to fix it.
The code:
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
admin.initializeApp()
export const newLastMessageDetected = functions.database
.ref('/ChatMessages/{chatID}/{messageID}/')
.onCreate(async (snap, context) => {
const values = snap.val()
const chatID = context.params.chatID
const messageID = context.params.messageID
const message = values.message
const fromID = values.fromID
const fromName = values.fromName
console.log( `LastMessage changed with chatID: ${chatID} and messageID ${messageID} `)
console.log( `Last message: ${message} by fromID: ${fromID} and by name ${fromName}`)
const payload = {
notification: {
title: fromName,
body: message
}
}
let membersSnapshotRef = admin.database().ref('/Members/' + chatID + '/')
return membersSnapshotRef.once('value')
.then(dataSnapshot => {
const promises = []
console.log('*** GOT SNAPSHOT ***')
dataSnapshot.forEach((element) => {
if (element.key != fromID && element.val() === true) {
const p = admin.firestore().collection('userTokens').doc(`${element.key}`).collection('devices').get()
console.log('*** GOT PROMISE ***')
console.log(`*** The recipientID: ${element.key} ***`)
console.log(`${p}`)
promises.push(p)
}
})
return Promise.all(promises).then(snapshot => {
console.log('*** GOT RETURNED PROMISES ***')
const tokenList = []
const data = snapshot.keys()
for (const token in data) {
console.log(`${token}`)
tokenList.push(token)
}
console.log(`${tokenList}`)
return admin.messaging().sendToDevice(tokenList, payload).then(result => {
console.log("Notification sent!");
return null;
})
})
.catch(error => {
console.log(`${error}`)
})
})
})
When you use Promise.all(), the result of the promise it returns is always going to be an array.
Promise.all(promises).then(snapshot => {
// snapshot is an array of results with one element for each of the promises
})
You need to iterate that array to find the results of all the promises you stored in the promises array. snapshot.keys() does not iterate that array - it is just giving you a list of numbers that are the indexes of those array. Try using snapshot.forEach() instead.
You might want to review some documentation for promise.all.
I actually really messed up because I tried to retrieve the data on the query;
didn't realize that the first loop was on the retrieved queries, so I had to do another on the documents retrieved. The device tokens are each of the documentIDs with the timestamp stored as the data.
The working code:
import * as functions from "firebase-functions"
import * as admin from "firebase-admin"
admin.initializeApp()
export const newLastMessageDetected = functions.database
.ref('/ChatMessages/{chatID}/{messageID}/')
.onCreate(async (snap, context) => {
const values = snap.val()
const chatID = context.params.chatID
const messageID = context.params.messageID
const message = values.message
const fromID = values.fromID
const fromName = values.fromName
console.log( `LastMessage changed with chatID: ${chatID} and messageID ${messageID} `)
console.log( `Last message: ${message} by fromID: ${fromID} and by name ${fromName}`)
const payload = {
notification: {
title: fromName,
body: message
}
}
let membersSnapshotRef = admin.database().ref('/Members/' + chatID + '/')
return membersSnapshotRef.once('value')
.then(dataSnapshot => {
const promises = []
// const docIDS = []
console.log('*** GOT SNAPSHOT ***')
dataSnapshot.forEach((element) => {
if (element.key != fromID && element.val() === true) {
const doc = admin.firestore().collection('userTokens').doc(`${element.key}`).collection('devices')
const p = doc.get()
// const docID = doc.id
console.log('*** GOT PROMISE ***')
console.log(`*** The recipientID: ${element.key} ***`)
// console.log(`*** The docID: ${docID} ***`)
promises.push(p)
// docIDS.push(docID)
}
})
return Promise.all(promises)
})
.then(async querySnapshot => {
console.log('*** GOT RETURNED PROMISES ***')
const tokenList = []
querySnapshot.forEach(snap => { // first here
console.log(`${snap.id} *** `)
console.log(`${snap} *** `)
snap.forEach(doc => { // then here
console.log(`${doc.id}`)
tokenList.push(doc.id)
})
})
await admin.messaging().sendToDevice(tokenList, payload)
console.log("Notification sent!")
return null
})
.catch(error => {
console.log(`${error}`)
})
})
I want the array length, but the function returns [object Promise]. I send a correct email and I just need know if it's already in collection.
const res = require('express/lib/response');
async function emailUnique(email){
var query = { email: email };
const response = await dbo.collection('users').find(query).toArray();
const tot = response.length;
return tot;
}
const emailValidate = function (email) {
if (email.indexOf('#') === -1 || email.indexOf('.') < 0) {
message = 'Invalid entries. Try again.';
}else{
let quantos = emailUnique(email);
message = quantos;
}
return message;
};
module.exports = emailValidate;
Thanks, Its a simplified answer:
emailValidate.mod.js
const res = require('express/lib/response');
function emailValidate(email) {
return new Promise((resolve) => { // return a promise
var query = { email: email };
dbo.collection('users').find(query).toArray((_err, result) => {
resolve(result.length); //the returned value
});
});
}
module.exports = emailValidate;
user.js
const mod_emailValidate = require('./emailValidate.mod');
exports.getUsers = (req, res) => {
const { name } = req.body;
const { email } = req.body;
const { password } = req.body;
async function run() { // make an async function
let data = await mod_emailValidate(email); //call the function with await
console.log(`Retornou: ${data}`);
}
run();
res.status(400).send({ message: 'Fired' });
};
I have checked tons of similiar problems all data, I am so confused. This is returning the same result whether the phone number is in the DB or not. Other versions with async and promises have been crashing the app. Please help
How can I get a value from Firebase realtime DB using the admin SDK and use that value to determine the output. Also it seems abnormally slow at times for some reason
auth.controller.js
const validate = require('../utils/validate');
const { getItem, setItem } = require('../utils/firebase');
const { generatePin } = require('../utils/auth');
const argon2 = require('argon2');
const { error } = require('console');
const { networkInterfaces } = require('os');
const exp = require('constants');
exports.connect = async function (req, res) {
const { phone } = req.body;
if (!phone) {
res.status(400).json({ message: 'Error, phone number is invalid or not registered'})
} else {
if(!validate.phoneNumber(phone)) {
res.status(400).json({ message: 'Error, phone number is invalid or not registered' })
} else {
const result = await generatePin();
item = await setItem('clients', 'phone', phone, 'hash', result.hash)
console.log(item)
if(!item) {
res.status(200).json({ message: 'Success', pin: result.pin})
} else {
res.status(400).json({ message: 'Error, phone number is invalid or not registered' })
}
var currentTime = Date.now();
var expiryTime = currentTime + 60;
setItem('clients', 'phone', phone, 'hashExpiry', expiryTime)
}
}
firebase.js
const { on } = require("events");
var admin = require("firebase-admin");
// Import Admin SDK
const { getDatabase } = require('firebase-admin/database');
const { type } = require("os");
var serviceAccount = require("../fedex-3a42e-firebase-adminsdk-r96f1-7249eaf87b.json");
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://fedex-3a42e-default-rtdb.firebaseio.com/"
});
const db = getDatabase()
function getItem(itemRef, child, val) {
const dbRef = db.ref(itemRef);
dbRef.orderByChild(child).equalTo(val).on("value", (data) => {
return data.val();
});
}
async function setItem(itemRef, child, val, key, pushedVal) {
const value = await getItem(itemRef, child, val);
console.log('val', value)
if(value) {
finallySetItem(value, itemRef, pushedVal);
return true
} else {
return false
}
}
function finallySetItem(data, itemRef, pushedVal) {
console.log(data)
if(data) {
var itemKey = Object.keys(data)[0];
console.log(itemKey)
const dbRef = db.ref(itemRef + '/' + itemKey + '/' + key);
dbRef.set(pushedVal);
}
}
module.exports = { getItem, setItem }
This won't work:
function getItem(itemRef, child, val) {
const dbRef = db.ref(itemRef);
dbRef.orderByChild(child).equalTo(val).on("value", (data) => {
return data.val();
});
}
You're passing your callback to on(), and on() won't do anything with the value you return in there.
More likely you want to use once() and return the value asynchronously from there:
async function getItem(itemRef, child, val) {
const dbRef = db.ref(itemRef);
const data = await dbRef.orderByChild(child).equalTo(val).once("value");
return data.val();
}
I am trying to retrieve the device token from my users collection in cloud firestore, inside of an onCreate. d.get("token"); below returns an undefined.
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp(functions.config().firebase);
const fcm = admin.messaging();
exports.senddevices = functions.firestore
.document("notification/{id}")
.onCreate((snap, context) => {
const name = snap.get("name");
const subject = snap.get("subject");
return admin.firestore()
.doc("users/{id}")
.get()
.then((d) => {
if (!d.empty) {
const payload = {
notification: {
title: "from " + name,
body: "subject " + subject,
sound: "default",
},
};
d.
const token = d.get("token");
return fcm.sendToDevice(token, payload);
} else {
console.log("User not found");
}
});
});
You need to read the value of ID from the path provided in .document() method and then use it in .doc() method to fetch the document as shown:
exports.senddevices = functions.firestore
.document("notification/{id}")
.onCreate((snap, context) => {
const name = snap.get("name");
const subject = snap.get("subject");
const id = context.params.id
// ^^ reading value of ID
return admin.firestore()
.doc("users/"+id) // <-- Using ID,
//or .doc(`users/${id}`)
.get()
.then((d) => {
if (d.exists) {
const payload = {
notification: {
title: "from " + name,
body: "subject " + subject,
sound: "default",
},
};
const token = d.data().token;
return fcm.sendToDevice(token, payload);
} else {
console.log("User not found");
}
});
});
Do notice you are fetching a single document so you should use d.exists to check if document exists instead of .empty which is used on a QuerySnapshot. Also to read value of token field you should first use .data() method to get contents of that document as an object.
I came to know that context expires in 15 minutes but is there any way to solve it manually i.e by storing the previous conversation in dB so can we handle that session expiring issue or else the whole conversation(output context) under that session ID will get clear and need to start from the first.
exports.fulfillmenttext = functions.https.onRequest((req,res) =>{
const answer1 = req.body.Text;
console.log("Text said by the user",answer1);
const uid = answer1.substring(0,28);
console.log("uid1 is",uid);
const answer = answer1.substring(28);
console.log("answer is",answer);
const sessionId = uid;
var count,questvalue;
runSample();
async function runSample(projectId = 'xxxxxxx') {
const languageCode = 'en-US';
const credentials = {
client_email: 'xxxxxxxxxx',
private_key: 'xxxxxxxxx'
};
//Instantiate a DialogFlow client.
const dialogflow = require('dialogflow');
const sessionClient = new dialogflow.SessionsClient({
projectId,
credentials,
});
// Define session path
const sessionPath = sessionClient.sessionPath(projectId, sessionId);
// The text query request.
const request = {
session: sessionPath,
queryInput: {
text: {
text: answer,
languageCode,
},
},
};
const responses = await sessionClient.detectIntent(request);
console.log('Detected intent');
const result = responses[0].queryResult;
let action = result.action;
console.log("action is"+action);
console.log(` Query: ${result.queryText}`);
console.log(` Response: ${result.fulfillmentText}`);
if (result.intent) {
const question = result.fulfillmentText;
console.log("question is",question);
const actionHandlers = {
'early': () => {
console.log('earlyaction1', action);
let name1 = JSON.stringify(result.parameters.fields.Name.stringValue);
name1 = name1.toString().replace(/"/g,"");
var data1 = {
Name: name1
};
var setDoc1 = admin.firestore().collection('User').doc(uid).collection("Popop").doc(uid).collection('Answers').doc('Earlyyears').update(data1);
},
'family': () => {
console.log('familyaction1', action);
let mname1 = JSON.stringify(result.parameters.fields.M_Name.stringValue);
let mname_string = mname1.toString().replace(/"/g,"");
var data20 = {
MName: mname_string
};
var setDoc20 = admin.firestore().collection('User').doc(uid).collection("Popop").doc(uid).collection('Answers').doc('Family').update(data20);
}
};
if (action === 'early') {
console.log('1');
actionHandlers[action]();
}
else if (action === 'family') {
console.log('2');
actionHandlers[action]();
}
res.status(200).send({"question":result.fulfillmentText,"action":action});
} else {
console.log(` No intent matched.`);
res.status(400).send({"action":"empty"});
}
}
});
I stumbled upon this problem as well. My solution was to save the userID and save the contexts to Firestore.
UPDATE:
This is how I stored Dialogflow's contexts in Firestore:
function saveContexts(userId, contexts) {
let UID = userId;
//get all contexts + parameters
if (contexts === undefined) {
console.log("contexts are undefined! returning");
return false;
}
db.collection("user-contexts-prod").doc(UID).set({
dateCreated: new Date(),
contexts: JSON.stringify(contexts)
})
.then(function () {
console.log("success!");
return true;
})
.catch(function (error) {
console.log("error writing document..", error);
return false;
});
}
Retrieving user contexts:
async function getContexts(userId) {
let UID = userId;
let docRef = db.collection("user-contexts-prod").doc(UID);
return docRef.get()
.then(res => {
if (res.exists) {
let contexts = JSON.parse(res.data().contexts);
console.log("<><> parsed contexts <><>: ");
console.log(contexts);
return contexts;
} else {
console.log(" UID DOES NOT EXIST!");
return false;
}
})
}
You can set the contexts again by looping over them and using the contextClient to create new contexts. Or use this method to loop through the contexts and find the one you need:
contexts.forEach(function(context) {
if (context.name === 'projects/{DIALOGFLOWPROJECTID}/agent/sessions/' + senderId + '/contexts/{CONTEXTNAME}') {
sessionData = context.parameters;
// all data that you saved in CONTEXTNAME is now available in the sessionData variable
}
});
Original answer:
Whenever a user started talking that didn't have any active contexts I check if I had the userID stored in my Database. If this user existed in my DB I retrieved the user information with all his data like this:
knownUser = await db.isKnownUser(senderId);
if (knownUser) {
//knownUser
console.log("Known user");
let userData = db.getUserDataById(senderId)
//initialize contexts with data you need
payload = returningUser_useSameData();
messenger.send(payload, senderId);
dashbot.logBotMessage(payload.toString, sessionId, intentName);
break;
} else {
//newUser
console.log("new user");
createContext('await_fillInTogether', '', sessionPath, sessionId, 1);
createContext('session', '', sessionPath, sessionId, 500);
payload = fillInTogetherNewUser();
messenger.send(payload, senderId);
dashbot.logBotMessage(payload.toString, sessionId, intentName);
break;
}