Firebase Cloud Functions with Cloud Firestore trouble - javascript

I have used this Firebase Database code in a previous project:
const getDeviceUser = admin.database().ref(`/users/${notification.to}/`).once('value');
I am now trying to convert it for Firestore. I am basically trying to get my users fcm's when a notification is being sent. I have tried many things, but haven't seen the new way to accomplish this.
EDIT: here is my code.
exports.sendFavoriteNotification = functions.firestore.document('users/{userUid}/notifications/{notificationId}').onCreate(event => {
const notification = event.data.data();
const user = event.params.userUid;
const getDeviceUser = admin.database().ref(`/users/${notification.to}/`).once('value');
// Get the follower profile.
const getProfilePromise = admin.auth().getUser(notification.sender);
return Promise.all([getDeviceUser, getProfilePromise]).then(results => {
const tokensSnapshot = results[0];
const liker = results[1];
// Check if there are any device tokens.
if (!tokensSnapshot.hasChildren()) {
return console.log('There are no notification tokens to send to.');
}
//console.log('There are', tokensSnapshot.numChildren(), 'tokens to send notifications to.');
console.log('Fetched follower profile', liker);
// Notification details.
const payload = {
notification : {
title : 'You have a new like!',
body : `${liker.displayName} just liked your photo.`,
badge: '1',
sound: 'default'
}
};
// Listing all tokens.
var tokens = admin.firestore.ref(`/users/${notification.to}/`).get('fcm');
// Send notifications to all tokens.
admin.messaging().sendToDevice(tokens.data(), payload);
return admin.messaging().sendToDevice(tokens, payload).then(response => {
// For each message check if there was an error.
const tokensToRemove = [];
response.results.forEach((result, index) => {
const error = result.error;
if (error) {
console.error('Failure sending notification to', tokens[index], error);
// Cleanup the tokens who are not registered anymore.
if (error.code === 'messaging/invalid-registration-token' ||
error.code === 'messaging/registration-token-not-registered') {
tokensToRemove.push(tokensSnapshot.update({
fcm: FieldValue.delete()
}));
}
}
});
return Promise.all(tokensToRemove);
});
});
});

Hope this will help. This is my code after 2 days of trying to learn how to convert from realtime database to firestore. It is based on a firebase project: https://github.com/MahmoudAlyuDeen/FirebaseIM
let functions = require('firebase-functions');
let admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotificationToFirestone = functions.firestore.document('/notifications/{pushId}')
.onCreate(event => {
const pushId = event.data.id;
const message = event.data.data();
const senderUid = message.from;
const receiverUid = message.to;
const db = admin.firestore();
if (senderUid === receiverUid) {
console.log('pushId: '+ pushId);
return db.collection('notifications').doc(pushId).delete();;
} else {
const ref = db.collection('users').doc(receiverUid);
const query = new Promise(
function (resolve, reject) {
ref.get()
.then(doc => {
if (!doc.exists) {
console.log('No such document!');
reject(new Error('No such document!'));
} else {
console.log('Document data:', doc.data().instanceId);
resolve(doc.data().instanceId);
}
})
.catch(err => {
console.log('Error getting document', err);
reject(err);
});
});
const getSenderUidPromise = admin.auth().getUser(senderUid);
return Promise.all([query, getSenderUidPromise]).then(results => {
//console.log('instanceId = Result[0]: ' + results[0]);
//console.log('sender = Result[1]: ' + results[1]);
const instanceId = results[0];
const sender = results[1];
//console.log('notifying ' + receiverUid + ' about ' + message.body + ' from ' + senderUid);
//console.log('instanceId este' + instanceId);
const payload = {
notification: {
title: sender.displayName,
body: message.body,
icon: sender.photoURL
}
};
admin.messaging().sendToDevice(instanceId, payload)
.then(function (response) {
console.log("Message sent: ", response);
})
.catch(function (error) {
console.log("Error sending message: ", error);
});
});
}
});

Related

How to emit events from BullMQ queue to only the user that is the owner of the job

My usecase of socket is not for chats, it's specifically to tell the front-end what BullMQ queue events happened, with a progress bar and telling when a job is done or failed.
Currently when I'm emitting events, it's going for all users, I tried to use the socket.to(socket.id).emit({ myEvent: example }) but didn't work at all.
I'm storing session on Redis.
Socket
this.redisClient = new Redis(`${process.env.REDIS_URL}`);
this.sessionStore = new RedisSessionStore(this.redisClient);
this.pubClient = this.redisClient;
this.subClient = this.pubClient.duplicate();
this.instance.adapter(createAdapter(this.pubClient, this.subClient));
} catch (err) {
console.log("Error on Socket Controller: ", err.message);
}
this.instance.use(async (socket, next) => {
const sessionID = socket.handshake.auth.sessionID;
if (sessionID) {
const session = await this.sessionStore.findSession(sessionID);
if (session) {
socket.sessionID = sessionID;
socket.userID = session.userID;
socket.username = session.username;
return next();
}
}
const username = socket.handshake.auth.username;
if (!username) {
return next(new Error("invalid username"));
}
socket.sessionID = uuidv4();
socket.userID = uuidv4();
socket.username = username;
console.log(socket.sessionID, socket.userID, socket.username);
next();
});
this.instance.on("connection", async (socket) => {
// Assign socket to the class
this.socket = this.socket == null ? socket : this.socket;
let connectedUsersCount =
Object.keys(this.instance.sockets.sockets).length + 1;
let oneUserLeft = connectedUsersCount - 1;
console.log(`New client connected`, connectedUsersCount);
try {
this.sessionStore.saveSession(this.socket.sessionID, {
userID: this.socket.userID,
username: this.socket.username,
connected: true
});
// emit session details
this.socket.emit("session", {
sessionID: this.socket.sessionID,
userID: this.socket.userID
});
// join the "userID" room
this.socket.join(this.socket.userID);
const users = [];
const sessions = await this.sessionStore.findAllSessions();
sessions.forEach((session) => {
users.push({
userID: session.userID,
username: session.username,
connected: session.connected
});
});
this.socket.emit("users", users);
// notify existing users
this.socket.broadcast.emit("user connected", {
userID: this.socket.userID,
username: this.socket.username,
connected: true,
messages: []
});
integrationQueueEvents.on("progress", async (job: any) => {
try {
console.log("Job Progressing", job);
const payload = {
status: true,
data: job.data,
jobId: job.jobId,
to: this.socket.userID
};
console.log("integration progress payload: ", payload);
this.socket.emit("integrationProgress", payload);
} catch (error) {
console.log(error);
}
// this.socket.to(this.socket.id).emit("integrationProgress", payload);
});
Session Store
findSession(id) {
return this.redisClient
.hmget(`session:${id}`, "userID", "username", "connected")
.then(mapSession);
}
saveSession(id, { userID, username, connected }) {
this.redisClient
.multi()
.hset(
`session:${id}`,
"userID",
userID,
"username",
username,
"connected",
connected
)
.expire(`session:${id}`, SESSION_TTL)
.exec();
}
async findAllSessions() {
const keys = new Set();
let nextIndex = 0;
do {
const [nextIndexAsStr, results] = await this.redisClient.scan(
nextIndex,
"MATCH",
"session:*",
"COUNT",
"100"
);
nextIndex = parseInt(nextIndexAsStr, 10);
results.forEach((s) => keys.add(s));
} while (nextIndex !== 0);
const commands = [];
keys.forEach((key) => {
commands.push(["hmget", key, "userID", "username", "connected"]);
});
return await this.redisClient
.multi(commands)
.exec()
.then((results) => {
return results
.map(([err, session]) => (err ? undefined : mapSession(session)))
.filter((v) => !!v);
});
}

Get value of variable outside API function call [duplicate]

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 10 months ago.
I am trying to make a chat application project with abusive text detection. I found code for the chat application online and want to add text detection using Perspective API. The API has several attributes for toxicity, threat etc. I am able to set the attributes inside the API function but I am unable to access them outside it.
Here is the relevant code:-
const sendMessage = asyncHandler(async (req, res) => {
const { content, chatId } = req.body;
let toxicity, insult, profanity, threat;
if (!content || !chatId) {
console.log("Invalid data passed into request");
return res.sendStatus(400);
}
let newMessage = {
sender: req.user._id,
content: content,
chat: chatId,
toxicity: toxicity,
insult: insult,
profanity: profanity,
threat: threat,
};
let inputText = newMessage.content;
// Perspective API
google
.discoverAPI(process.env.DISCOVERY_URL)
.then((client) => {
const analyzeRequest = {
comment: {
text: inputText,
},
requestedAttributes: {
TOXICITY: {},
INSULT: {},
PROFANITY: {},
THREAT: {},
},
};
client.comments.analyze(
{
key: process.env.API_KEY,
resource: analyzeRequest,
},
(err, response) => {
if (err) throw err;
// console.log(JSON.stringify(response.data, null, 2));
toxicity = (response.data.attributeScores.TOXICITY.summaryScore.value * 100).toFixed(2);
insult = (response.data.attributeScores.INSULT.summaryScore.value * 100).toFixed(2);
profanity = (response.data.attributeScores.PROFANITY.summaryScore.value * 100).toFixed(2);
threat = (response.data.attributeScores.THREAT.summaryScore.value * 100).toFixed(2);
newMessage.toxicity = toxicity;
newMessage.insult = insult;
newMessage.profanity = profanity;
newMessage.threat = threat;
console.log("1-" + newMessage.toxicity); // This returns the desired output
}
);
})
.catch((err) => {
throw err;
});
//
console.log("2-" + newMessage.toxicity); // This returns undefined
try {
let message = await Message.create(newMessage);
message = await message.populate("sender", "name profilePic");
message = await message.populate("chat");
message = await User.populate(message, {
path: "chat.users",
select: "name profilePic email",
});
await Chat.findByIdAndUpdate(req.body.chatId, {
latestMessage: message,
});
res.json(message);
} catch (error) {
res.status(400);
throw new Error(error.message);
}
});
I want newMessage to be updated after the API call. After coming across this post, I found that console.log("2-" + newMessage.toxicity) executes before console.log("1-" + newMessage.toxicity). I tried using callbacks and async/await but couldn't make it work.
The console.log("2-" + newMessage.toxicity) is outside the google.discoverAPI call so it execute instantly.
you can try something like this
const sendMessage = asyncHandler(async (req, res) => {
const { content, chatId } = req.body;
let toxicity, insult, profanity, threat;
if (!content || !chatId) {
console.log("Invalid data passed into request");
return res.sendStatus(400);
}
let newMessage = {
sender: req.user._id,
content: content,
chat: chatId,
toxicity: toxicity,
insult: insult,
profanity: profanity,
threat: threat,
};
let inputText = newMessage.content;
// Perspective API
const client = await google
.discoverAPI(process.env.DISCOVERY_URL)
const analyzeRequest = {
comment: {
text: inputText,
},
requestedAttributes: {
TOXICITY: {},
INSULT: {},
PROFANITY: {},
THREAT: {},
},
};
await new Promise((resolve, reject) => {
client.comments.analyze(
{
key: process.env.API_KEY,
resource: analyzeRequest,
},
(err, response) => {
if (err) {
reject(err)
}
// console.log(JSON.stringify(response.data, null, 2));
toxicity = (response.data.attributeScores.TOXICITY.summaryScore.value * 100).toFixed(2);
insult = (response.data.attributeScores.INSULT.summaryScore.value * 100).toFixed(2);
profanity = (response.data.attributeScores.PROFANITY.summaryScore.value * 100).toFixed(2);
threat = (response.data.attributeScores.THREAT.summaryScore.value * 100).toFixed(2);
newMessage.toxicity = toxicity;
newMessage.insult = insult;
newMessage.profanity = profanity;
newMessage.threat = threat;
console.log("1-" + newMessage.toxicity);
resolve()
}
);
})
.catch((err) => {
throw err;
});
//
console.log("2-" + newMessage.toxicity); // This returns undefined
try {
let message = await Message.create(newMessage);
message = await message.populate("sender", "name profilePic");
message = await message.populate("chat");
message = await User.populate(message, {
path: "chat.users",
select: "name profilePic email",
});
await Chat.findByIdAndUpdate(req.body.chatId, {
latestMessage: message,
});
res.json(message);
} catch (error) {
res.status(400);
throw new Error(error.message);
}
});

Firebase Function - pass in function context

Below is my firebase function:
const admin = require('firebase-admin');
const firebase_tools = require('firebase-tools');
const functions = require('firebase-functions');
admin.initializeApp();
exports.deleteUser = functions
.runWith({
timeoutSeconds: 540,
memory: '2GB'
})
.https.onCall((data, context) => {
const userId = context.auth.uid;
var promises = [];
// DELETE DATA
var paths = ['users/' + userId, 'messages/' + userId, 'chat/' + userId, 'like/' + userId];
paths.forEach((path) => {
promises.push(
recursiveDelete(path).then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE FILES
const bucket = admin.storage().bucket();
var image_paths = ["avatar/" + userId, "avatar2/" + userId, "avatar3/" + userId];
image_paths.forEach((path) => {
promises.push(
bucket.file(path).delete().then( () => {
return 'success';
}
).catch( (error) => {
console.log('Error deleting user data: ', error);
})
);
});
// DELETE USER
promises.push(
admin.auth().deleteUser(userId)
.then( () => {
console.log('Successfully deleted user');
return true;
})
.catch((error) => {
console.log('Error deleting user:', error);
})
);
return Promise.all(promises).then(() => {
return true;
}).catch(er => {
console.error('...', er);
});
});
function recursiveDelete(path, context) {
return firebase_tools.firestore
.delete(path, {
project: process.env.GCLOUD_PROJECT,
recursive: true,
yes: true,
token: functions.config().fb.token
})
.then(() => {
return {
path: path
}
}).catch( (error) => {
console.log('error: ', error);
return error;
});
}
// [END recursive_delete_function]
When calling this function, how can I pass in the context.auth.id?
Below is what i've tried:
async function deleteAccount(userId) {
const deleteUser = firebase.functions().httpsCallable("deleteUser");
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
}
But im getting the following error:
Unhandled error TypeError: Cannot read property 'uid' of undefined
I know the the context.auth.id is available server side but In this instance I need a way i can pass it in.
You don't have to pass user's UID in callable cloud function. The user must be logged in with Firebase authentication and Firebase SDKs will take care of the rest.
Can you try logging current user in deleteAccount function before calling cloud function just to ensure user is logged in? Also context.auth.uid is UID of user that is calling the function. If you want to access the userId that you are passing in the function, refactor the code as shown below.
The deleteUser() function would take only 1 parameter that's the data you want to pass in Cloud functions.
// not deleteUser({}, { userId })
deleteUser({ userId }).then((result) => {
console.log(result.data);
});
When you are explicitly passing any data in Cloud function, that can be access from data and not context:
const { userId } = data;

firebase node js TypeError: Cannot read property 'toId' of undefined at exports

Hello new to node Js
in my firebase cloud function logs
it says it cannot read property toId
I am printing out console.log(event) and the wild card toId shows the user's UID as I inspected , so what am I doing wrong?
here is my code:
exports.sendMessageNotification = functions.database.ref('/user-messages-notification/{toId}/{fromId}/{message}')
.onCreate((snap, context) => {
console.log('This is the event:',event)
var toId = context.params.toId
var fromId = context.params.fromId
console.log('User: ' + toId + ' recieved a message from ' + fromId);
var userRef = db.collection('Users').doc(toId);
var getDoc = userRef.get()
.then(doc => {
if (!doc.exists) {
return console.log('No such document!');
} else {
var User = doc.data();
return console.log('Document data:', doc.data());
}
})
.catch(err => {
//catch error does not need 'return console'
console.log('Error getting document', err);
});
})

Time delay in child_added listener

I want to add a new team member if team member not exists in the Firebase database. However I see a time delay while reading the existing entries. The below code returns null for the variable teammembertKey. Therefore I see a new key in database every time I re-login into the system. Can someone help me to solve this issue?
checkIfUserExists = function (teammemberData) {
return firebase.database().ref().child('/leaders/' + firebase.auth().currentUser.uid)
.once("value", function (snapshot) {
console.log(snapshot.exists());
return Promise.resolve({
teammemberData,
userExists: snapshot.exists(),
});
});
};
$scope.submit = function () {
var teammembertKey = null;
// A teammember entry.
// A teammember entry.
var teammemberData = {
uid: firebase.auth().currentUser.uid,
email: firebase.auth().currentUser.email,
displayName: firebase.auth().currentUser.displayName,
photoURL: firebase.auth().currentUser.photoURL
};
const p = checkIfUserExists(teammemberData);
p.then((snapshot, userExists) => {
if (userExists) {
teammembertKey = snapshot.key;
// update user
} else {
// go create a user
console.log('i');
}
})
.catch(err => {
console.warn('Error signing in.', err);
});
if (teammembertKey == null) {
// Get a key for a new team member.
teammembertKey = firebase.auth().currentUser.uid; //firebase.database().ref().push().key;
// Write the new member's data simultaneously.
var updates = {};
updates['/leaders/' + teammembertKey] = teammemberData;
const promise = firebase.database().ref().update(updates);
promise
.then(e => { })
.catch(e => {
console.log(e);
})
}
};
Here is what you need.
$scope.submit = function () {
var teammembertKey = firebase.auth().currentUser.uid;
var teammemberData = {
uid: firebase.auth().currentUser.uid,
email: firebase.auth().currentUser.email,
displayName: firebase.auth().currentUser.displayName,
photoURL: firebase.auth().currentUser.photoURL
};
firebase.database().ref().child('/leaders/' + teammembertKey).once("value")
.then(funciton(snap) {
if(!snap.val()) {
var updates = {};
updates['/leaders/' + teammembertKey] = teammemberData;
const promise = firebase.database().ref().update(updates);
promise
.then(e => { })
.catch(e => {
console.log(e);
})
}
})
.catch(err => {
console.warn('Error signing in.', err);
});
};

Categories