Cloud Firestore: Retrieve dynamic field keys with Javascript - javascript

I am sending a notification using a device token with Cloud Firestore and Cloud Function. Now the system will store the device token in which user logs in. Since a user may have multiple device or may logged in using different device, all I want is to send this notification to those device using the stored device token. This is how a users document looks like, I store the tokens as a nested object.
{
name: "Frank Kemerut",
device_tokens: { 23qweq: "LG G6", Os23pk: "Samsung S6", asd231: "Samsung S9" },
age: 12
}
Now I want to iterate and get all key and value then send a notification to those device using the collected tokens. How am I going to perform this? Is this the best approach?

OK here is a cloud function that will send a notification to all user devices when a firestore event triggered. Assuming you have the user id in that trigger from the event object or another way. The function will use that id to get the user document from the database depending on how you stored it then get the notification token and send it to all of his devices in the device_tokens map
export const sendEventNotification = functions.firestore.document('events/${eventId}')
.onCreate((data, context) => {
const userId = "someId"
//Get the user document to get the notification tokens.
return firestore.doc(`users/${userId}`).get().then((user) => {
//dummy notification payload
const payload = {
data: {
event: JSON.stringify(data.data())
}
}
//The device tokens mapped to device name.
const device_tokens = user.data().device_tokens
//Array of notification tokens that we will send a notification to.
const promises = []
Object.keys(device_tokens).forEach(token => {
promises.push(admin.messaging().sendToDevice(token, payload))
})
return Promise.all(promises)
}).catch((error) => {
console.log(`Failed to send user notification. Error: ${error}`)
return null
})
})

Related

How do I fetch user information to display in a feed of posts using firebase storage solutions?

I'm building a forum-style application where users post content that displays on a global feed. I want to display information about the user in posts (photoURL, displayName) similar to Twitter.
I have firebase v9 using the authentication and firestore for the posts. The reason I want to reference the auth is that I can catch changes to the user's information as it happens, this way the feed is up to date.
I save the user's unique ID with the post so I am able to reference who to display. I can successfully reference the post title and description with doc.title & doc.description however I get stuck when retrieving user information. I'm trying doc.UserID.displayName for the display name but I know this is incorrect. I can't find anything in the docs for this specific use case, is this something that I can do with just firestore and auth?
Do I need to create a reference to the auth storage with doc.UserID?
Here is the code:
// add a new post
addPostForm.addEventListener('submit', (e) => {
e.preventDefault();
onAuthStateChanged(auth, (user) => {
const colRef = collection(db, 'Posts');
console.log(hiddenURL.value);
addDoc(colRef, {
UserID: user.uid,
beatURL: hiddenURL.value,
title: addPostForm.postTitle.value,
description: addPostForm.postDescription.value,
})
.then(() => {
console.log("Document written with ID: ", doc.id);
addPostModal.classList.remove('open');
addPostForm.querySelector('.error').textContent = "";
})
.catch(error => {
addPostForm.querySelector('.error').textContent = error.message;
alert(error);
})
})
});
export const initApp = async () => {
initFirebaseAuth;
const posts = await collection(db, 'Posts');
// render data to the page
return renderPosts(posts);
};
const renderPosts = (posts) => {
const main = document.getElementById("feed");
onSnapshot(posts, (snapshot) => {
let cardsArray = []
snapshot.docs.forEach((doc, user) => {
cardsArray.push({ ...doc.data(), id: doc.id })
name.textContent = `${doc.UserID.displayName}`; // users display name
avatar.src = doc.UserID.photoURL; //user's image
description.textContent = `${post.description}`;
title.textContent = `${post.title}`;
});
console.log(cardsArray);
});
};
There are two cases and approaches at first sight:
1. Your users profiles are only available in the Auth Service
In this case, via the JS SDK, a user X cannot "query" the Auth profile of a user Y.
This means that you need to save the author's displayName together with the author uid when the post is created.
2. Your users profiles are also available in a users collection (a common pattern)
In this case, when you display a post, you could fetch the user's document to get the author's displayName.
However, in the NoSQL world, you should not be afraid to duplicate data and denormalize your data model. When designing your data-model you should think about it from a query perspective, trying to minimize the number of queries for a given screen/use case. So approach #1 is recommended, even if you maintain a user's collection.
In case of changes in the user's profile, in order to synchronyse the post documents and user's data a common approach is to use a set of Cloud Functions (which are executed in the back-end) to update the post documents. The link between the posts and the users profile being the user's uid.

Sending FCM messages to web apps through firebase cloud functions

Is it possible to send FCM notifications through Firebase Cloud Functions, when a Firestore data field changes, but for a website, not an app. There is lots of guidance out there for Android and iOS but nothing for simply web apps, outside of sending notifications from the Firebase Console).
I've been trying to find out how to trigger a notification from Cloud Functions but can't find anything useful.
As an example, my database has the following structure:
Collection: users
Documents: documents named using userID
Data Fields: Fields 1 through 5. Field 5 stores the FCM Token. Field 1 stores their status (online, offline, offline pending messages).
I would like to ensure that when Data Field 1 changes (to 'offline pending messages), that the relevant user gets notified (based on the Doc ID).
Edit: adding code below for reference
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/users/{doc}/{Hears}')
.onUpdate(async (change, context) => {
const db = admin.firestore();
db.collection('users').doc(context.params.userId) // get userId
.get()
.then(doc => {
//this is intended to get the FCM token stored in the user's document
const fcmToken = doc.data().usrTkn;
// Notification details
const payload = {
notification: {
title: 'You have a new message.',
body: 'Open your app'
}
};
})
//This should send a notification to the user's device when web app is not in focus.
//FCM is set up in service worker
const response = await admin.messaging().sendToDevice(fcmToken, payload);
console.log(response);
});
Sending messages to a web app is no different from sending it to a native mobile app, so the sending part of guidance you've found is equally applicable. The Firebase documentation even contains an example of sending notifications on a Realtime Database trigger, and doing the same for Firestore would not be much different.
If you're having a specific problem sending messages, I recommend showing what you tried, and what isn't working about it.
Update: your code doesn't work (no matter what sort of device you send the notification to), because you're not handling the asynchronous nature of get() in your code.
The simplest way to fix that is to use await there too, just like you do when calling sendToDevice. So:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/users/{doc}/{Hears}')
.onUpdate(async (change, context) => {
const db = admin.firestore();
const doc = await db.collection('users').doc(context.params.userId).get();
const fcmToken = doc.data().usrTkn;
const payload = {
notification: {
title: 'You have a new message.',
body: 'Open your app'
}
};
const response = await admin.messaging().sendToDevice(fcmToken, payload);
console.log(response);
})
I highly recommend spending some time on learning about asynchronous calls, closures, async/await, and how to debug something like this by adding logging.

How to delete authenticated user from firebase in angular

when clicking on a button i called a function,
onDelete(id:string){ this.db.collection('Students').doc(id).delete(); }
Here, id is a name of document that i want to delete, db is a property of type AngularFireStore, 'Students' is a name of collection.
Structure of document:
enter image description here
In the above image, collection name is Students, under which multiple documents exist, since document name must be unique so i given that name a number of type string which acts as id. In every document, there is email field, i want to delete that email from authentication when i delete the same document.
code to sign up users:
this.afAuth.auth.createUserWithEmailAndPassword(email:string,password:string).then(res=>{})
If you want to delete a user existing in Firebase authentication you have two possibilities:
1/ Using the JavaScript SDK (since your app is made with angular)
You call the delete() method, as follows:
const user = firebase.auth().currentUser;
user.delete()
.then(() => {
//....
})
.catch(err => {
if (err.code === "auth/requires-recent-login") {
//Re-authenticate the user
} else {
//....
}
})
Note however, that this method "requires the user to have recently signed in. If this requirement isn't met, ask the user to authenticate again and then call firebase.User.reauthenticateWithCredential". An error with the auth/requires-recent-login code is "thrown if the user's last sign-in time does not meet the security threshold".
So, only the logged-in user can call this method from a front-end, in order to delete his/her own account.
2/ Using the Admin SDK
You can use the Admin SDK's deleteUser() method, for example within a Cloud Function.
In this case, there is no need to have the user logged-in since this is executed in the back-end and it is therefore possible to delete any user.
For example, you could have a Callable Cloud Function triggered by an admin user.
Another possibility, is to trigger a Cloud Function upon the Firestore user's document deletion.
Update based on your Question update:
I understand that you want to delete the user record in the Auth service upon deletion. For that you can write a Cloud Function as follows:
exports.deleteUser = functions.firestore
.document('Students/{studentID}')
.onDelete((snap, context) => {
const deletedValue = snap.data();
const userEmail = deletedValue.Email;
return admin.auth().getUserByEmail(userEmail)
.then(userRecord => {
const userID = userRecord.uid;
return admin.auth().deleteUser(userID)
})
.catch(error => {
console.log(error.message);
return null;
})
});

admin.auth().currentUser; returning undefined in Cloud Function

I am trying to create a function that, when a device is registered in the app, will attach this device uid to the uid of the signed-in user who registered the device (this is in another firestore collection that is automatically created when a user registers).
Here is my code:
exports.addDeviceToUser = functions.firestore.document('device-names/{device}').onUpdate((change, context) => {
const currentUser = admin.auth().currentUser;
const deviceName = context.params.device;
var usersRef = db.collection('users');
var queryRef = usersRef.where('uid', '==', currentUser.uid);
if (authVar.exists) {
return queryRef.update({sensors: deviceName}).then((writeResult => {
return console.log('Device attached');
}));
} else {return console.log('Device attachment failed, user not signed in');}
});
I am consistently getting this error: "TypeError: Cannot read property 'uid' of undefined." Obviously I am not able to access the auth information of the current user. Why?
The Admin SDK doesn't have a sense of current user. When you say admin.auth(), you're getting back an Auth object. As you can see from the API docs, there is no currentUser property on it. Only the Firebase client SDK has a sense of current user, because you use that to get the user logged in.
If you need the client app to tell Cloud Functions code work with the user's identity, you have to send it an ID token from the client, and verify it on the server. Then the server can know who the end user is, and perform actions on their behalf. Typically you do this with an HTTP type trigger. Callable functions transmit this data automatically between the client and server, but you can do it manually yourself using code that works like this sample.
Right now, Firestore triggers don't have immediate access to the end user that made a change in the database. However, if you use the Auth UID of the user as the key of the document, and protect that document with security rules, you can at least infer the UID of the user based on the changes they make to the document by pulling it out of the id of the document that changed.
Because, by design, Cloud Functions executes on the back end and do not hold any information on which user was authenticated when adding/modifying the data in the database.
When writing the data in the 'device-names/{device}' document (from your app), you could include an extra piece of data which is the uid of the current user.

Send notification using Cloud Functions for Firebase to specific user

I'm using Cloud Functions for Firebase to send notifications to the user. I'm able to get the notification but the problem is that everyone is getting the notification, I'm trying to send the notification to a particular user. I'm saving user's device id in Firebase's database and then send that particular person the notification. Here is my code:
To save user's data, which is actually working fine:
DatabaseReference root = FirebaseDatabase.getInstance().getReference();
DatabaseReference groupsRef = root.child("users").child(Settings.Secure
.getString(ctx.getContentResolver(), Settings.Secure.ANDROID_ID));
groupsRef.child("isLogin").setValue(2);
In first activity subscribing to the topic:
FirebaseMessaging.getInstance().subscribeToTopic("android");
And finally javascript code(something I know very little about):
var functions = require('firebase-functions');
var admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
exports.sendNotification = functions.database.ref('/users')
.onWrite(event => {
var eventSnapshot = event.data;
var str = "This is notification"
console.log(str);
var topic = "android";
var payload = {
data: {
isLogin: eventSnapshot.child("975af90b767584c5").child("isLogin").val()
}
};
return admin.messaging().sendToTopic(topic, payload)
.then(function (response) {
// See the MessagingTopicResponse reference documentation for the
// contents of response.
console.log("Successfully sent message:", response);
})
.catch(function (error) {
console.log("Error sending message:", error);
});
});
Here instead of "975af90b767584c5" which is hardcoded right now, I want to send device id, which I don't know how to do in javascript. Or if there is any other way.
Any help is appreciated, thanks.
First in app, get User FCM token by
String Token = FirebaseInstanceId.getInstance().getToken();
Now, send this token to your server and save it with other user details in Database.
Then when you want to send Notification to specific use, fetch that user's FCM token from Database via user id or something else.If you want to send notification to multiple user then fetch multiple user FCM token from database and put it in arrayList.
Now for main part, call fcm endpoint with your KEY, notification content
and most important: token or token array.
In your case, do not use sendToTopic, use send to: Token/Array
You can google for java script syntax, but this is main logic.For more info:
https://firebase.google.com/docs/cloud-messaging/admin/send-messages

Categories

Resources