How to handle multiple tabs using in firebase using realtime database - javascript

I am creating a multiplayer game website. I am using three features of firebase.
Firebase authentication
Firestore
Real time database
The data which is permanent is stored in firestore. Like profile image, username etc. The data in firestore is stored in collection users and the key is same as the authentication id which we get from user.uid
The second data is temporary data which contains the chat messages and current game situation and player turn etc. This data is stored in real time database.
There are two base objects in real time data base. One is rooms and other is users. When a user logs in to website the permanent data is taken from the firestore and placed with temporary data(because we might need to display the permanent data again and again). The function I am using to get permanent data and create combination with temp data is
//'uid' is the permanent id which is used in firestore and also in authentication.
export const addUser = async (uid: string) => {
//gets the permanent data from firestore
const data = await getUserData(uid);
//Set it to realtime database my adding one more temp prop
return await dbUsers.child(uid).set({...data, messages: []});
};
Till now everything is fine problem comes when I have to remove the user on disconnection. I used t
export const removeUser = async (uid: string) => {
return await dbUsers.child(uid).remove();
};
The above way doesn't work for multiple tabs. Consider if user had opened multiple tabs and he just closed one server then realtime database will consider it logged out.
Do I need to create realtime data on the basis of another id using push() method. Kindly guide me to correct path.

If I understand correctly you're trying to track the user's online status using Firebase's onDisconnect handlers. To do this:
You write a value for the user's UID when they connect.
You then delete that value using an onDisconnect handler.
This indeed will not work when the user opens the app in multiple locations (tabs, browsers, or devices). The reason is that a user can be online in multiple locations, and your code and data structure needs to cater for this.
The idiomatic approach is the one outlined in the sample presence app in the Firebase documentation, and works with a data structure like this:
"OnlineUsers": {
"uidOfUser1": {
"-LKeyOfConnection1": true,
"-LKeyOfConnection2": true
},
"uidOfUser2": {
"-LKeyOfConnection3": true,
"-LKeyOfConnection4": true
}
}
In this structure, if a user has two open connections (on different tabs, browsers, devices), they have two nodes under their UID, each with its own onDisconnect handler. When both connections are closed, with connection keys disappear, and thus their /OnlineUsers/$uid node also disappears automatically.
So to detect if a user is online in the above structure, you'd check if there is a node under /OnlineUsers with their UID.

Related

Firebase generating two different uid's for the same user?

Currently working on a React project that utilizes Firebase to store users. To make the user, I use createUserWithEmailAndPassword(). When the user is created in Firebase, it stores the user under an id that looks like -N4xrZ...
However, when I make a call to get the user's UID (through userCredential.user.uid), it returns a much longer one that looks like a typical UID generated by Firebase and that isn't the same as the user's parent id. Any ideas as to what this may be?
It stores the user under an id that looks like -N4xrZ...
That's the expected behavior since you're using push(). This function generates a unique ID each time is called.
When I make a call to get the user's UID (through userCredential.user.uid), it returns a much longer one that looks like a typical UID generated by Firebase and that isn't the same as the user's parent id.
The UID that comes from the authentication process, it's different than the one that is generated by push(). It's actually the same, each time you launch your app. So when you write data to the database, stop using push() and use the UID:
firebase.database().ref('users/' + userCredential.user.uid).set({
username: name,
email: email,
profile_picture : imageUrl
});

When to fetch data from API via Vuex again to keep the data up to date

I'm fetching data from an API and want to achieve it via Vuex. So my users store module is holding and managing all users.
To fetch all users I have a fetch action which calls the API via Axios and passes the users array from the response to the mutation.
I have a view component at /users which renders a list of users. Within that component I call
async mounted() {
await this.fetch();
}
to initialize the store. After that, the store getter will return the users array and the list gets rendered. I know that I can also fetch one item by id via getter
getById: state => id => state.users.find(user => user.id === id)
When navigating to a user detail page /users/:id I can make use of it, extract the userId via this.$router.currentRoute.params.id and render the user details. But what if I load that route directly? Then the store initialized with an empty users array and it won't find a single user.
So should I fetch all users again in the mounted hook of that user details view? I think calling the axios service directly would be an anti pattern because the resources say I should use Vuex as an interface between the component and the API.
Is there a best practise when to fetch all users from the api? Because let's image you navigate to another page which loads all users in a select component, should I fetch them from the getter? Maybe it will load a user that doesn't exist anymore. But polling all users every X seconds would be bad too...
What is the correct way to update the store data to make it accessible for all components in the entire application?
I don't agree that Vuex should be used to store an entire tables worth of user data in the manner you described. This sounds like a security concern to me. Remember that Vuex is a client-side store; meaning all your user data would be visible to any client using your application. You should never store sensitive data client-side.
Type this into your browser console, and you'll see what I mean.
document.getElementsByTagName('a')[0].__vue__.$store.state
Vuex should be used for storing non-sensitive data of an individual user required to drive their session.
If you are storing all user data for the purpose of listing accounts so that they can be managed by an admin or something, then I would instead, make that API call on demand when you load whatever view you have which lists your user accounts. This way you will always have the most up-to-date data, without the need for constantly polling your API.
I would paginate that data, loading only the first 5 records (to keep the API call initially light), then provide your admins the ability to search for accounts; that searching should be done server side, through your API.
Example:
methods: {
fetchUsers(limit, page, search) {
const thisIns = this;
if (search) {
this.userQuery = `/users/find?search=${search}`
} else {
this.userQuery = `/users/find?limit=${limit}&page=${page}`
}
thisIns.$http.get(this.userQuery)
.then(async (response) => {
console.log(response.data)
})
.catch(function (error) {
console.log(error)
});
}
},
created() {
this.fetchUsers(5, 1)
},
You can then call your fetchUsers() method as needed, such as when performing a search for user(s).
Since you are using Axios, you can also consider implementing a caching mechanism, axios-cache-adapter, so that your API calls are cached for some period of time you define. This would reduce the number of API calls hitting your server.
I also don't consider this to be scaleable. Remember that an application user will have to wait for this payload when they first load your app. This also has performance implications; as your user data grows over time, you'll be consuming more and more of the devices RAM.

firebase realtime database, child_changed if user is part of conversation

This is my database structure:
I want new messages to generate a notification.
How do i query this data to ensure the member is part of the conversation and then pass just the child_added of the new message.
This is what i have so far but it will return the entire conversation when a message is added.
const notificationChecker = database.ref("chats")
.orderByChild(`members/${userId}`)
.equalTo(true);
notificationChecker.on('child_changed', snapshot => {
console.log(snapshot.val());
});
Since you're listening on chats, the result of your query will be child nodes chats. There is no way to change that behavior and it is one of the many reasons why experienced Firebasers recommend working with separate top-level lists for each entity type in your app.
It seems like you want the new messages for the current user in all conversations that they are part of. That is a three step process:
Determine the current user
Find what conversations they're part of.
You'll typically do this by having a separate top-level list user-conversations, and then the UID as a key under that, and the conversation IDs as keys (with value true) under that. See https://stackoverflow.com/a/41528908/209103
Find the new messages in each conversation.
The best way to do this is to know what the last message is that the user saw in each conversation. In linear chat apps, you could model this by storing the key of the last chat message the user saw under /user-conversations/$uid/$conversationid, instead of the true we used above.
If that is not possible, you can simply load the newest message for each conversation (conversationRef.orderByKey().limitToLast(1)) and then start listening from there (conversationRef.orderByKey().startAt(keyOfLatestMessage)).
Either way, you will need to do this for each conversation that the user is part of.

Firebase Database query for http trigger

I'm trying to query my firebase realtime database for the list of User objects. I have a favourites field and in it I'm storing a list of id's of favourited users. How would I go about writing a http endpoint with cloud functions so that it returns a json list of User objects corresponding to these ids in database?
Thanks a lot.
You can configure your development environment for FCF by following this. Once you've initialized your project, write a function inside functions/index.js like this one:
exports.getFavouriteUsers = functions.https.onRequest((req, res) => {
var favouriteUsers = admin.database.ref('users/favourites').val();
res.status(200).send(favouriteUsers);
});
Finally deploy and you'll be able to make requests to the endpoint shown in the console.
Look at this documentation for HTTP triggers

How should I manage in-memory data in Node?

I have a simple app built using Node, Express, and Socket.io on the server side. My page queries my API when it needs to retrieve data that will not change, and uses WebSockets for getting live updates from the server for dynamic data. The app allows a single person, the "Supervisor", to send questions to any number of "Users" (unauthenticated) and view their answers as they trickle in. The Users send their data to the server using a POST request, and it is streamed to the Supervisor over a WebSocket. The server stores user data in a simple array, and uses an ES6 map of the items in the array (users) to objects containing each their questions and answers, like this:
class User {}
let users = [], qa = new Map();
io.on('connection', socket => {
let user = new User(socket.id);
users.push(user);
qa.set(user, {});
socket.on('question-answered', ({id, answer}) => {
let questionData = qa.get(user);
questionData[id] = answer;
qa.set(user, questionData);
});
});
This is obviously a very primitive way of handling data, but I don't see the need for additional complexity. The data doesn't need to persist across server crashes or restarts (the user's questions and answers are also stored in localStorage), and MongoDB and even Redis just seem like overkill for this kind of data.
So my question is, am I going about this the right way? Are there any points I'm missing? I just want a simple way to store data in memory and be able to access it through client-side GET requests and socket.io. Thank you for any help.
If an array and a map provide you the type of access you need to the data and you don't need crash persistence and you have an appropriate amount of memory to hold the amount of data, then you're done.
There is no need for more than that unless your needs (query, persistence, performance, multi-user, crash recovery, backup, etc...) require something more complicated. A simple cliche applies here: If it ain't broke, it don't need fixing.

Categories

Resources