How can I set data from firestore to a global variable - javascript

I am trying to fetch data from firestore and set it to a global variable but I'm getting undefined. Here is what I have;
let myScore;
auth.onAuthStateChanged((user) => {
let docRef = db.collection("players");
let user_email = user.email;
docRef.onSnapshot((players) => {
players.forEach((doc) => {
data = doc.data();
if (user_email == data.email) {
myScore = data.endless_score;
console.log(myScore); //This log the right score
}
});
});
});
console.log(myScore); // This logs undefined
How can I get myScore i.e. the second console log to output the score from firestore?

Okay, from what I see you are trying to get the current users when the user logs in but I think attaching a snapshot listener is not the optimal solution. Because first of it would count as a read for every player document there is. What I suggest you to do is this :
const myUser = (await (await firebase.firestore().collection("players").where("email","==",user.email).limit(1).get()).docs[0].data()
This Will Give You Your Current User's data then you can use it however you want by doing myUser.endless_score. It will also reduce the cost because you are only getting a single document where email is equal to current users email
also don't forget to make your arrow function async in order to use await :)

Calling onSnapshot creates a background-call to firestore. It run independently of the rest of your code. So your second console log is practically called immediatly after let myScore, meaning it's still undefined.
As well, onAuthChanged is an observer. It can be triggered at random, depending on when you sign in/out of firestore.
To the functionality you want, sort of, you'd need to rewrite the code to use async/await.
Defining a function() as 'async' and then calling 'await function()' in your code will make the code literally wait for the online call to finish before continuing.
read:
https://javascript.info/async-await

Related

Mongoose / mondoDB Use of expired sessions is not permitted

I'm trying to make a transaction with mongoose.
When I'm running the code, it looks like mongoose document remembers the session even after calling await session.endSession(). And later, when I call .save() on that document, I get error "Use of expired sessions is not permitted".
This is the demo (just a simplified example):
const MessageSchema = new mongoose.Schema({ text: String }, {strict: 'throw'});
const Message = mongoose.model('Message', MessageSchema);
const session = await mongoose.startSession();
let message;
await session.withTransaction(async () => {
message = (await Message.create([{text: 'My message'}], {session}))[0];
});
await session.endSession();
message.text = 'My message 2';
await message.save();
That final .save() is throwing that error. If you would log message.$session().hasEnded you would get true. My expectation was that, if session has ended, .save() would be smart not to use it.
What I want to achieve:
Create some documents with transaction, commit them, so they are in the database.
Later, use that same document, that is already in the database, to make some changes to it.
What am I doing wrong here? How can I prevent .save() from throwing an error and trying to use expired session?
There are no sections on endSession anywhere in the mongoose docs, but as you very well discovered it yourself, when this function is called, it only sets a flag to your session object rather than destroying it. If it was destroyed, you would have another error.
My expectation was that, if session has ended, .save() would be smart not to use it.
It is, in fact, smart enough not to use it, the framework only informs you that you are attempting an illegal instruction.
What I want to achieve: Create some documents with transaction, commit them, so they are in the database.
That's exactly what you do with your withTransaction call. This wrapper helps you create, commit and abort/retry in case something bad happened, so after that call and if all ended well you indeed successfully created a document in the database.
Later, use that same document, that is already in the database, to make some changes to it.
Depends on what you mean by "later". If it's part of the same endpoint (and I don't know why you would immediately modify that document rather than committing it rightfully in the first place), then as I said in the comments, moving the endSession call would likely fix the issue:
const MessageSchema = new mongoose.Schema({ text: String }, {strict: 'throw'});
const Message = mongoose.model('Message', MessageSchema);
const session = await mongoose.startSession();
let message;
await session.withTransaction(async () => {
message = (await Message.create([{text: 'My message'}], {session}))[0];
});
message.text = 'My message 2';
await message.save();
await session.endSession();
If it's part of another endpoint, then just make another transaction like you just did, except you modify the document instead of creating it. Or if you don't need transaction at all, use a method like findOneAndUpdate or findByIdAndUpdate. I reckon you seem to be familiar enough with JS and Mongoose to do that on your own.
What am I doing wrong here?
Basically, not much. You already understood that you can't call .save() after ending a session.
How can I prevent .save() from throwing an error and trying to use expired session?
You can't. It is merely a reminder that you are attempting an illegal operation. You can however try/catch the error and decide to do nothing about it in your catch clause, OR write a if statement checking for message.$session().hasEnded.

Firebase Realtime Database - Determine if user has access to path

I have updated my Firebase Realtime Database access rules, and have noticed some clients now tries to access paths they do not have access to. This is ok - but my problem is that my code stops after being unable to read a restricted node.
I see below error in my console, and then loading of subsequent data stops:
permission_denied at /notes/no-access-node
I begin by collecting access nodes from /access_notes/uid and continue to read all data from /notes/noteId.
My code for collecting notes from the database below:
//*** SUBSCRIPTION */
database.ref(`access_notes/${uid}`).on('value', (myNotAccessSnaps) => {
let subscrPromises = []
let collectedNots = {}
// Collect all categories we have access to
myNotAccessSnaps.forEach((accessSnap) => {
const noteId = accessSnap.key
subscrPromises.push(
database.ref(`notes/${noteId}`)
.once('value', (notSnap)=>{
const notData = notSnap.val()
const note = { id: notSnap.key, ...notData}
collectedNotes[note.id] = note
},
(error) => {
console.warn('Note does not exist or no access', error)
})
)
})
Promise.all(subscrPromises)
.then(() => {
const notesArray = Object.values(collectedNotes)
...
})
.catch((error) => { console.error(error); return Promise.resolve(true) })
I do not want the client to halt on permission_denied!
Is there a way to see if the user has access to a node /notes/no_access_note without raising an error?
Kind regards /K
I do not want the client to halt on permission_denied!
You're using Promise.all, which MDN documents as:
Promise.all() will reject immediately upon any of the input promises rejecting.
You may want to look at Promise.allSettled(), which MDN documents as:
[Promise.allSettled()] is typically used when you have multiple asynchronous tasks that are not dependent on one another to complete successfully, or you'd always like to know the result of each promise.
Is there a way to see if the user has access to a node /notes/no_access_note without raising an error?
As far as I know the SDK always logs data access permissions errors and this cannot be suppressed.
Trying to access data that the user doesn't have access to is considered a programming error in Firebase. In normal operation you code should ensure that it never encounters such an error.
This means that your data access should follow the happy path of accessing data it knows it has access to. So you store the list of the notes the user has access to, and then from that list access each individual note.
So in your situation I'd recommend finding out why you're trying to read a note the user doesn't have access to, instead of trying to hide the message from the console.

Get guild members and filter them (Discord.js)

i'm new to Discord.js, and trying to make server info command like on the picture below. I'm stuck in the moment where i need to get all guild members and filter them by current status and bot property. I read https://discordjs.guide/popular-topics/common-questions.html and it says that i need to fetch all guild members:
msg.guild.members.fetch().then(fetchedMembers => {
const totalOnline = fetchedMembers.filter(member => member.presence.status === 'online');
msg.channel.send(`There are currently ${totalOnline.size} members online in this guild!`);
});
My command is sent as an embed, and i'm adding filtered members count into a variable, and then this value inserting into a embed field. If i send embed to a channel right inside then() block, it's working, embed fields are added correctly. But i need to add another information about guild, such as channels count, owner, region etc. If i create fields out of then(), instead of the count i get undefined.
P.S. sorry for my poor English, i'm not a native speaker
The reason you are getting undefined is because fetch() is an asynchronous function, meaning it is completed in the background, and once it is completed, the callback then is called.
You can't put it outside the then() block because then your code will execute before fetch() finished and updated the variable with a result, in addition to that, the totalOnline variable is created inside that block, so it wouldn't work outside of it either way, since it's a local variable inside that scope.
This article might make it easier to understand: https://www.freecodecamp.org/news/async-await-javascript-tutorial/
You can use the additional needed information inside that scope.
For example:
msg.guild.members.fetch().then(fetchedMembers => {
const totalOnline = fetchedMembers.filter(member => member.presence.status === 'online');
msg.channel.send(`There are currently ${totalOnline.size} members online in ${msg.guild.name}!`);
});
What you could do instead of using fetch() is just assign a variable to the member collection.
// v12
let allmembers = message.guild.members.cache;
// v11
let allmembers = message.guild.members;
Once you have that, you can filter it and put it into an embed or message etc.
const totalOnline = allmembers.filter(member => member.presence.status === 'online');
message.channel.send(`There are currently ${totalOnline.size} members online in ${message.guild.name}!`);
Answer for discord.js v13:
let members = (await guild.members.fetch())
.filter(m => m._roles.includes(ROLE_ID));
let member_ids = members.map(m => m.user.id);
let member_count = members.size();

How to fetch a message in a channel gotten via guild.channels.get

I'm working on improvements for my reaction based cart tracker for discord shops (ie in game items etc)
I'm having an issue where after getting a channel by id, i can't use the fetchMessage function on the variable i saved the channel to to get a specific message. i need this to automatically update the cart message ive already sent upon the user reacting to add an item.
heres my current code:
var cartchannel = messageReaction.message.guild.channels.get(curcart.orderchannelid)
var cartmsg = cartchannel.fetchMessage(curcart.cartmsgid)
channel.fetchMessage() is an asynchronous function. It means you need to use await or .then(). For example with await:
var cartchannel = messageReaction.message.guild.channels.get(curcart.orderchannelid)
var cartmsg = await cartchannel.fetchMessage(curcart.cartmsgid)
console.log(cartmsg) // Discord.Message object
or with .then():
var cartchannel = messageReaction.message.guild.channels.get(curcart.orderchannelid)
cartchannel.fetchMessage(curcart.cartmsgid).then((cartmsg) => {
console.log(cartmsg) // Discord.Message object
});
If you use the await way, you need to be in an async function.

trying to fix the problems arising from asynchronous code in javascript

I am new in database systems and what I am trying to do is to check whether the e-mail entered by the user during login exists in the database or not. I use Firebase Databse. So, the code I have written is this:
function login(){
var e_mail = document.getElementById("e-mail").value;
rootRef = firebase.database().ref();
rootRef.orderByChild("E_mail").on("child_added", function(snapshot){
lst.push(snapshot.val().E_mail);
//console.log(lst);
})
console.log(lst);
}
let lst = [];
login_btn.onclick = function() {login()};
I want to fetch all e-mails from the database, add them in the list and then loop through that list. Maybe this is not the best way, but that's what I'm working on. I could also just say if (snapshot.val().E_mail == e_mail){alert("there is such a user");}but the problem I have encountered and want to deal with is not that, it's the "callback" function inside login function. When I console the list in the outer function it shows an empty list as it does not run the inner function until it is done with the outer one. I understand this. But how can I avoid or fix this. I want to get the full list of e-mails to be able to loop through it then. Also, I don't know how to end the "loop" in Firebase, because it is sort of looping when it gets the e-mails. So I would like to stop at the moment when it finds a matching e-mail.
You're downloading all users to see if one name exists already. That is a waste of bandwidth.
Instead you should use a query to match the email you're looking for, and only read that node:
rootRef.orderByChild("E_mail").equalTo(e_mail).once("value", function(snapshot){
if (snapshot.exists()) {
// A node with the requested email already exists
}
})
In general, if you need to process all nodes, you'll want to use a value event, which executes for all matching nodes at once. So to get all users from the database, add them to a list, and then do something with that list:
rootRef.orderByChild("E_mail").once("value", function(snapshot){
var list = [];
snapshot.forEach(function(childSnapshot) {
list.push(childSnapshot.val());
});
console.log(list); // this will display the populated array
})
Note that you won't be able to access the list outside of the callback. Even if you declare the variable outside of the callback, it will only be properly populated inside the callback. See Xufox' comment for a link explaining why that is.

Categories

Resources