Firebase onDisconnect not deleting user name on disconnection - reactjs

Perhaps I am mis-using onDisonnect(), but I looked at the example code on the firebase.blog and am doing my best.
When a user submits a user name, I call the code below, which adds the username to a firebase db. Then on disconnection, I want the username to be deleted from the db. This would mean that the db would only show users that are connected to the app at that moment in time.
I am doing it this way so I can then call the data and then map through the array to display currently logged-in users.
I have made two attempts in deleting the name, which you can see in the code below under con.onDisconnect().remove();, neither of which work the way I need. That said, if I log in once again from the same computer, the first user name replaces the second user name!
Here is my code
setName = e => {
e.preventDefault()
let name = this.state.name;
let connectedRef = firebase.database().ref('.info/connected');
connectedRef.on('value', function (snap) {
if (snap.val() === true) {
// Connected
let con = myConnectionsRef.push();
myConnectionsRef.set({
name
})
// On disconnect
con.onDisconnect().remove();
myConnectionsRef.orderByChild('name').equalTo(name).once('child_added', function (snapshot) {
snapshot.ref.remove();
// var nameRef = firebase.database().ref('users/'+name);
// nameRef.remove()
})
}
});
Where am I going wrong? Is there a better way to use onDisconnect? From the example on the fb forum, it isn't clear where I would put that block of code, hence why I am attempting to do it this way.
Thanks.

If I understand correctly what is your goal, you don't need to do
myConnectionsRef.orderByChild('name').equalTo(name).once('child_added', function (snapshot) {
snapshot.ref.remove();
// var nameRef = firebase.database().ref('users/'+name);
// nameRef.remove()
})
as the onDisconnect().remove() call will take care of that.
Also, as explained in the blog article you refer to (as well as shown in the doc):
The onDisconnect() call shall be before the call to set() itself. This is to
avoid a race condition where you set the user's presence to true and
the client disconnects before the onDisconnect() operation takes
effect, leaving a ghost user.
So the following code should do the trick:
setName = e => {
e.preventDefault()
let name = this.state.name;
const connectedRef = firebase.database().ref('.info/connected');
const usersRef = firebase.database().ref('users');
connectedRef.on('value', function (snap) {
if (snap.val() === true) {
// Connected
const con = usersRef.child(name); //Here we define a Reference
// When I disconnect, remove the data at the Database location corresponding to the Reference defined above
con.onDisconnect().remove();
// Add this name to the list of users
con.set(true); //Here we write data (true) to the Database location corresponding to the Reference defined above
}
});
The users node will display the list of connected users by name, as follows:
- users
- James: true
- Renaud: true

Related

Get user to set role ID

Im trying to setup a system where the user can change the role ID for staff but every time i restart my bot the ID resets back. Here is my code:
this is estaff.js
const roles = require("./roles");
module.exports = {
name: "estaff",
callback: ({ message, args }) => {
if (args.length === 0)
message.channel.send(`Current estaff role is <#&${roles.EstaffRole}>`);
else {
roles.EstaffRole = args[0];
message.channel.send("Successfully changed estaff role");
}
},
};
this is the roles.js file:
const roles = {
HstaffRole: "956109121550168114",
GstaffRole: "956112990116134953",
EstaffRole: "932558453203943434",
};
module.exports = roles;
I think you are misunderstanding how data is stored in this case.
This is not permanent storage, for a problem like this then you would require a database/storing system.
To help you understand, what you currently are doing is:
const roles = require("./roles");
This will get you a reference to the array in roles.js.
Assuming the call has the necessary arguments, you then
roles.EstaffRole = args[0];
This is going to change the array in roles, but not the files in roles.js.
Perhaps you could run this while looking at your roles.js and realise that you are not really changing anything, it will only work as long as your server/program is running.

Get a list of users in a socket.io chat

I'm currently working on a socket.io based chat application.
I found a LOT of tutorials on how to manage messages, but I'm struggling with the users list.
I'd like to display a list of connected users, with a few twists:
When a user joins the chat, he has to pick a name
Once he has picked a name, he can talk, and see a list with the names of other users
I'm struggling with this last part: for the messages, its quite easy, I can fetch the existing messages in my database. But how can I retrieve and display the list of connected users ?
I tried with the following pieceo f cade, in my react application:
const [players, setPlayers] = useState([])
const [step, setStep] = useState('lobby')
const socket = socketIOClient(ENDPOINT);
useEffect(() => {
socket.emit('data', {
type: 'joinedRoom',
})
socket.on("FromAPI", data => {
if (data.type === 'newPlayer') {
setPlayers([{
name: data.payload.name,
}])
}
if (data.type === 'joinedRoom') {
socket.emit('data', {
type: 'getPlayers',
players,
})
}
if (data.type === 'getPlayers') {
if (data.players && data.players.length) {
setPlayers([...players, data.players])
}
}
});
But this doesn't work: for any new user, the players state fragment is initialized as an empty array.
I'm fairly new to websockets in general, any help about how to handle data-sharing would be greatly appreciated :)
Are you using nodejs on your backend?,if so than u can either create a users arrry,and push new user into tht array when ever a new user is connected.thn u can access tht anytime,dont forget to delete user whn disconnected

Saving roles from mentioned user

I am trying to make a tempmute command, I followed a tutorial online which worked... But my own server has users with multiple roles, and these roles allow them to talk even when they receive the "muted" role.
Is there any way to save all the roles from a mentioned user and then to remove and add those roles?
I already tried to make a new let variable
let roleHistory = tomute.member.roles;
and then adding and removing them with:
await(tomute.removerole(roleHistory));
tomute.addRole(roleHistory);
But that didn't work
module.exports.run = async (bot, message, args) => {
let tomute = message.guild.member(message.mentions.users.first() || message.guild.members.get(args[0]));
if(!tomute) return message.reply("Couldn't find user.");
if(tomute.hasPermission("MANAGE_MESSAGES")) return message.reply("Can't mute them!");
let muterole = message.guild.roles.find(`name`, "muted");
if(!muterole){
try{
muterole = await message.guild.createRole({
name: "muted",
color: "#000000",
permissions:[]
})
message.guild.channels.forEach(async (channel, id) => {
await channel.overwritePermissions(muterole, {
SEND_MESSAGES: false,
ADD_REACTIONS: false
});
});
}catch(e){
console.log(e.stack);
}
}
let mutetime = args[1];
if(!mutetime) return message.reply("You didn't specify a time!");
await(tomute.addRole(muterole.id));
message.reply(`<#${tomute.id}> has been muted for ${ms(ms(mutetime))}`);
setTimeout(function(){
tomute.removeRole(muterole.id);
message.channel.send(`<#${tomute.id}> has been unmuted!`);
}, ms(mutetime));
}
I want the bot to take the roles away, tempmute the user and giving the roles back after the Timeout.
Your attempt is on the right track, but you missed a small detail. A Guild Member has a method addRole and removeRole which you used. However, these methods are meant for adding/removing a single role.
When you first fetch the user roles with let roleHistory = tomute.member.roles;, it returns a Collection of roles. If you then attempt to use removeRole(roleHistory) it attempts to remove a single role equal to the complete collection (which doesn't exist obviously).
To make it work, you need the methods addRoles and removeRoles which adds/removes an entire collection. So your code would be:
let roleHistory = tomute.roles;
// Removing all the roles
await(tomute.removeRoles(roleHistory));
// Adding all the roles
tomute.addRoles(roleHistory);
P.s. Since your tomute variable is already a user you need to change your code to fetch the roles from let roleHistory = tomute.member.roles; to let roleHistory = tomute.roles;

Return a firebase database reference as an array in a cloud function

Below is the firebase database reference returned as an array in my AngularJS scope:
var ref = firebase.database().ref("users").child(user.uid).child("week1");
$firebaseArray(ref);
However when I tried writing the same code in my index.js file for a database Cloud Function, there was an error message:
ReferenceError: $firebaseArray is not defined at /user_code/index.js:22:18
Is there a way to make a Firebase reference ref return as an array in my index.js Cloud Functions file since $firebaseArray is not defined outside the AngularJS scope?
Below is an illustration of the database:
users: {
user uid (generated by push) : {
deviceToken : "tokenID",
name : "Display Name"
},
anotherUserID : {
deviceToken : "tokenID",
name : "Display Name"
},
Players: {
player1: John,
Player2: ken,
}
Is there a way for a change in the Players database node to trigger a function in the users node (for each user):
exports.update = functions.database.ref('/Player')
.onWrite(event=>{
console.log(event.data);
var ref = admin.database().ref('/users/'+ user.uid+ '/week1');
ref.set(10);
return;
});
My issue was accessing the user.uid (created by the push() method) for each user.
In Cloud Functions, you can use the Firebase Admin SDK to save and retrieve data from the database. To initialize the Admin SDK, you can use environment configuration:
const functions = require('firebase-functions');
const admin = require('firebase-admin');
admin.initializeApp(functions.config().firebase);
You can then attach a value listener to your database reference, utilizing and returning a JavaScript Promise to keep the function alive and chain your logic:
const ref = admin.database().ref("/users").child(user.uid).child("week1");
return ref.once("value").then(snapshot => {
const values = snapshot.val();
// Do something with the snapshot
});
The values variable here will be assigned an array of the values at the location (see val()), so could be used in place of $firebaseArray.
If you need to obtain a reference to the node that triggered your database function, you can use the event parameter from the function definition:
let ref = event.data.ref;
This is useful if you need to obtain the specific location in the database that triggered your function. The event.data variable is a DeltaSnapshot instance for database functions.
Similarly, you can use event.params to obtain any of the variables specified in a database trigger definition, for example {uid} in the below definition can be obtain using event.params.uid:
exports.example = functions.database.ref('/users/{uid}')
.onWrite(event => {
const uid = event.params.uid;
// ...
});
The method you take here depends on what work you need your function to perform, how it's triggered and what variables or values you need access to.
Remember that your function can freely read and write data anywhere in the database using the Admin SDK too.
In your newest example, it would be hard to match a node at /players to /users because there isn't a value with the user's UID. So you would need to change the /players child nodes to include further data, something like:
players : {
player1 : {
name: "John",
uid: "anotherUserID"
}
}
You could then extract this uid using event.data.child('uid').val() from your function (where your function is triggered by children under this node using /players/{playerId}):
exports.update = functions.database.ref('/players/{playerId}')
.onWrite(event=>{
console.log(event.data);
const uid = event.data.child('uid').val();
var ref = admin.database().ref('/users/' + uid + '/week1');
ref.set(10);
return;
});

Caching in React

In my react App I have a input element. The search query should be memoized, which means that if the user has previously searched for 'John' and the API has provided me valid results for that query, then next time when the user types 'Joh', there should be suggestion for the user with the previously memoized values(in this case 'John' would be suggested).
I am new to react and am trying caching for the first time.I read a few articles but couldn't implement the desired functionality.
You don't clarify which API you're using nor which stack; the solution would vary somewhat depending on if you are using XHR requests or something over GraphQL.
For an asynchronous XHR request to some backend API, I would do something like the example below.
Query the API for the search term
_queryUserXHR = (searchTxt) => {
jQuery.ajax({
type: "GET",
url: url,
data: searchTxt,
success: (data) => {
this.setState({previousQueries: this.state.previousQueries.concat([searchTxt])
}
});
}
You would run this function whenever you want to do the check against your API. If the API can find the search string you query, then insert that data into a local state array variable (previousQueries in my example).
You can either return the data to be inserted from the database if there are unknowns to your view (e.g database id). Above I just insert the searchTxt which is what we send in to the function based on what the user typed in the input-field. The choice is yours here.
Get suggestions for previously searched terms
I would start by adding an input field that runs a function on the onKeyPress event:
<input type="text" onKeyPress={this._getSuggestions} />
then the function would be something like:
_getSuggestions = (e) => {
let inputValue = e.target.value;
let {previousQueries} = this.state;
let results = [];
previousQueries.forEach((q) => {
if (q.toString().indexOf(inputValue)>-1) {
result.push(a);
}
}
this.setState({suggestions: results});
}
Then you can output this.state.suggestions somewhere and add behavior there. Perhaps some keyboard navigation or something. There are many different ways to implement how the results are displayed and how you would select one.
Note: I haven't tested the code above
I guess you have somewhere a function that queries the server, such as
const queryServer = function(queryString) {
/* access the server */
}
The trick would be to memorize this core function only, so that your UI thinks its actually accessing the server.
In javascript it is very easy to implement your own memorization decorator, but you could use existing ones. For example, lru-memoize looks popular on npm. You use it this way:
const memoize = require('lru-memoize')
const queryServer_memoized = memoize(100)(queryServer)
This code keeps in memory the last 100 request results. Next, in your code, you call queryServer_memoized instead of queryServer.
You can create a memoization function:
const memo = (callback) => {
// We will save the key-value pairs in the following variable. It will be our cache storage
const cache = new Map();
return (...args) => {
// The key will be used to identify the different arguments combination. Same arguments means same key
const key = JSON.stringify(args);
// If the cache storage has the key we are looking for, return the previously stored value
if (cache.has(key)) return cache.get(key);
// If the key is new, call the function (in this case fetch)
const value = callback(...args);
// And save the new key-value pair to the cache
cache.set(key, value);
return value;
};
};
const memoizedFetch = memo(fetch);
This memo function will act like a key-value cache. If the params (in our case the URL) of the function (fetch) are the same, the function will not be executed. Instead, the previous result will be returned.
So you can just use this memoized version memoizedFetch in your useEffect to make sure network request are not repeated for that particular petition.
For example you can do:
// Place this outside your react element
const memoizedFetchJson = memo((...args) => fetch(...args).then(res => res.json()));
useEffect(() => {
memoizedFetchJson(`https://pokeapi.co/api/v2/pokemon/${pokemon}/`)
.then(response => {
setPokemonData(response);
})
.catch(error => {
console.error(error);
});
}, [pokemon]);
Demo integrated in React

Resources