firebase.firestore() .set() not firing first time - javascript

I'm not a dev, I'm just learning for my own interest as a hobby.
For my own learning I'm building real world project rather than just following the usual online tutorials.
I'm building a simple vue.js app with vuex and using firebase for auth and db.
I have a method that should just take a value and .set() a new document in a collection with a single piece of data:
HTML
<template>
<div>
<v-btn #click="testing('1', '2')">Testing</v-btn>
</div>
</template>
SCRIPT
methods: {
testing(id, val) {
console.log('entered testing:', id, val)
firebase.firestore().collection('users').doc(id)
.set({
key: val
})
.then(result => {
console.log('created in firestore', result)
})
.catch(error => {
console.error('failed to create in firestore', error)
})
}
...
The problem is that after refreshing the browser the first time I click the button the method runs.
I see the console log 'entered testing' with the correct id and val but the firestore call doesn't appear to run.
Nothing in the console and no network requests.
The second time I click it I see the .then() console log 'created in firestore: undefined' and what looks like 3 network requests, and the doc and data is set correctly!
Every time I click the button after that I see the correct console log, a single network request and the doc and data is set correctly.
Other info...
I have security rules set up on the db but only basic allow if auth != null
The user is logged in when I try this.
I'm not ruling out that there might be something somewhere else in my app which is causing this but there's nothing obvious and I've stripped things right back to debug this. I'm trying to set it up so all firebase requests are done in the store only, with each component just passing it the values it needs, but to debug this I've moved all the logic (including importing firestore) into a single component.
What is going on?
Is there something obvious I'm missing?

Related

Does Firebase Firestore keep a local copy of snapshot

I am using firebase firestore for my chat room application.
I have a function to send messages to firebase as
const sendMessageHandler = message => {
if (message) {
firestore()
.collection(`ChatRooms/${roomId}/messages`)
.doc(moment().format('YYYY-MM-DD-HH-mm-sssss'))
.set({
message: Encrypt(message),
userId: userId,
});
}
};
and I am fetching messages into flatlist only from firestore as
// this messages const is getting data only from firestore
const [messages, setMessage] = useState([]);
firestore()
.collection(`ChatRooms/${roomId}/messages`)
.onSnapshot(
querySnapshot => {
const messages = [];
querySnapshot.forEach(dataSnapshot => {
messages.push({
id: dataSnapshot.id,
message: dataSnapshot.data().message,
userId: dataSnapshot.data().userId,
});
});
setMessage(messages.reverse());
setLoading(false);
},
error => {
console.log('error : ', error);
},
);
// And now I am using this messages somewhere in return function of a component as :
<FlatList
data={messages}
renderItem={flatListItemRenderer}
inverted={true}
/>
Notice I am fetching only from firestore and not locally.
Now I turned off the internet and tried sending messages so this happens
The data has not been updated to firestore (of course because of no internet), but the flatlist has been updated with the new message !!
The only possibility I can think of is that the setter methods of firestore is storing data in both local and remote database and the getter method is first getting snapshot from local database and then remote database.
So the question is does #react-native-firebase/firestore keep a local snapshot also and updates both local and remote snapshot of data whenever we change something?
Github Link
Edit :
Firestore docs says
Firestore provides out of the box support for offline capabilities. When reading and writing data, Firestore uses a local database which synchronizes automatically with the server.This functionality is enabled by default, however it can be disabled if you need it to be disabled
I tried turning off persistence, but this property is related to storing data offline not the state. i.e, now when my app loads it fetch all the data directly from server(previously fetching from storage and server both), but the flatlist still updates with the new message(maybe it is storing some state like useState also???)
The Firestore SDK keeps a local copy of:
All data that you have an active listener for.
All pending writes.
In addition, if offline persistence is enabled, it also keeps a local copy of data that your code has recently read.
When you make a write operation, the SDK:
Writes your operation to its queue of pending writes.
Fires an event for any local listeners.
Tries to synchronize the pending write with the server.
Steps 1 and 2 always happen, regardless of whether you are online or offline. Only step 3 won't complete immediately when you are offline.

Firebase firestore: how to query relevant documents and then update each of them

I am developing a web app on Firebase/firestore, in which users can sign in and write their own posts. The data are stored in the following way:
-User information are stored as under collection('user').doc('uid').
-Information about posts the user has written are stored in collection('post').doc('postid'), and the doc has 'userinfo' and 'uid' fields. The 'userinfo' field contains exact copy of what is stored in 'uid' doc, just in object format.
Here are the operations that I want to do:
When the user changes the data, the changes are reflected in the document.
Look for the all the posts that the user has written based on 'uid' data, and then update userinfo in those data.
The last part is tricky for me. The Firebase documentations cover situations where the references are pretty much static, i.e. you know the exact path to write/update. What I am trying to do is look for a set of documents that is not necessarily static, and then update each of them.
Here is the code I wrote for this effort. The first part works without any problem. Of course, the second part doesn't work. :) What would be the code to do the do the second part?
const update = () => {
//This part is for updating user information. This works without any problem.
firebase.firestore().collection('user').doc(user.uid).update({
username: username1,
nickname: nickname1,
intro: intro1
})
.then(()=>{
//This part is for updating all of the document that the user has written based on 'uid' value. This doesn't work.
//Below code is probably way off, but it shows where I am going and what I am trying to do.
firebase.firestore().collection('post').where('uid','==',user.uid).get()
.then((querysnapshot)=>{
querysnapshot.forEach((doc)=>{
let ref=firebase.firestore().collection('post').doc(doc.id);
ref.update({
userinfo: {nickname:nickname1,username:username1,intro:intro1}
})
})
})
}).then(()=>{
alert("Successfully updated!");
window.location.href='/'+username1;
}).catch((error)=>{
alert("Error!");
})
}
Thanks a lot in advance!
What's the error that you get running this code? It seems on the right track for me.
But despite that, here are some suggestions to deal with this kind of update:
Don't do the second part on the client side, do it on the server side with a Firestore Trigger (create a onUpdate trigger in the user collection in your case): https://firebase.google.com/docs/functions/firestore-events.
The problem of doing in the client side, is because if the user closes the page/browser or the site goes offline in the middle of the update, you will have inconsistent data.
You don't need to recreate the DocumentReference after getting the query result, the docs returned already have a .ref that you can call .ref.update() directly.
EDIT: If you want to keep your original code (updating on client side), the problem of the navigation occurring before all the updates to conclude is because ref.update() returns a promise.
So the update queue is asynchronous being performed on database when the client navigates away.
To solve this, I would use a Promise.all() to wait all updates being completed.
firebase.firestore().collection('post').where('uid','==',user.uid).get()
.then((querysnapshot)=>{
const promises = [];
querysnapshot.forEach((doc)=>{
promises.push(doc.ref.update({
userinfo: {nickname:nickname1,username:username1,intro:intro1}
});
});
Promise.all(promises).then(()=>{window.location.href='/'+username1;});
});
Or using the await syntax (I think it's easier to maintain and understand):
const querysnapshot = await firebase.firestore().collection('post').where('uid','==',user.uid).get();
const promises = [];
querysnapshot.forEach((doc)=>{
promises.push(doc.ref.update({
userinfo: {nickname:nickname1,username:username1,intro:intro1}
});
});
await Promise.all(promises);
window.location.href='/'+username1;

Firebase custom claim how to set?

I'm struggling with firebase custom claims.
I have tested a lot of approaches nothing works. Obviously, I miss something important in the concept itself.
So I'm back to the root. This script from the google example should apply customs rule on a newly created user
exports.processSignUp = functions.auth.user().onCreate(event => {
const user = event.data; // The Firebase user.
const customClaims = {
param: true,
accessLevel: 9
};
// Set custom user claims on this newly created user.
return admin.auth().setCustomUserClaims(user.uid, customClaims)
});
Then on a client, I check the result with
firebase.auth().currentUser.getIdTokenResult()
.then((idTokenResult) => {
// Confirm the user is an Admin.
console.log(idTokenResult.claims)
if (!!idTokenResult.claims.param) {
// Show admin UI.
console.log("param")
} else {
// Show regular user UI.
console.log("no param")
}
})
.catch((error) => {
console.log(error);
});
Everything just a raw copy-paste still doesn't work. I've tested both from the local machine(there could be troubles with cors?) and deployed
This is a race situation. If the Function end first then, you will get the updated data.
The getIdTokenResult method does force refresh but if the custom claim is not ready then, it is pointless.
You need to set another data control structure to trigger the force refresh on the client. By example a real-time listener to the rtd;
root.child(`permissions/${uid}`).on..
And the logic inside the listener would be: if the value for that node exists and is a number greater than some threshold, then trigger the user auth refresh
During that time the ui can reflect a loading state if there is no datasnapshot or the not admin view if the datasnapshot exists but is a lower permission level.
In Functions you have to set the node after the claim is set:
..setCustomUserClaims(..).then(
ref.setValue(9)
);
I have a more detailed example on pastebin
The claims on the client are populated when the client gets an ID token from the server. The ID token is valid for an hour, after which the SDK automatically refreshes it.
By the time the Cloud Functions auth.user().onCreate gets called, the client has already gotten the ID token for the new user. This means that it can take up to an hour before the client sees the updated claims.
If you want the client to get the custom claims before that, you can force it to refresh the token. But in this video our security experts recommend (that you consider) using a different storage mechanism for claims that you want to be applied straight away.

MeteorJS - No user system, how to filter data at the client end?

The title might sound strange, but I have a website that will query some data in a Mongo collection. However, there is no user system (no logins, etc). Everyone is an anonymouse user.
The issue is that I need to query some data on the Mongo collection based on the input text boxes the user gives. Hence I cannot use this.userId to insert a row of specifications, and the server end reads this specifications, and sends the data to the client.
Hence:
// Code ran at the server
if (Meteor.isServer)
{
Meteor.publish("comments", function ()
{
return comments.find();
});
}
// Code ran at the client
if (Meteor.isClient)
{
Template.body.helpers
(
{
comments: function ()
{
return comments.find()
// Add code to try to parse out the data that we don't want here
}
}
);
}
It seems possible that at the user end I filter some data based on some user input. However, it seems that if I use return comments.find() the server will be sending a lot of data to the client, then the client would take the job of cleaning the data.
By a lot of data, there shouldn't be much (10,000 rows), but let's assume that there are a million rows, what should I do?
I'm very new to MeteorJS, just completed the tutorial, any advice is appreciated!
My advice is to read the docs, in particular the section on Publish and Subscribe.
By changing the signature of your publish function above to one that takes an argument, you can filter the collection on the server, and limiting the data transferred to what is required.
Meteor.publish("comments", function (postId)
{
return comments.find({post_id: postId});
});
Then on the client you will need a subscribe call that passes a value for the argument.
Meteor.subscribe("comments", postId)
Ensure you have removed the autopublish package, or it will ignore this filtering.

Ionic Push User identification issue

I'm having this issue where I register for Push using $ionicPush.register() But I'm not doing user identification at any point using $ionicUser(like the docs suggest). So, I'm only calling $ionicPush.register() and I wait for that promise to resolve and I'm storing the device_token on my server, eg.
$ionicPush.register({
onNotification: function(notification) {
//....
}
}).then(function(deviceToken) {
//Save device-token to my server
UserService.saveIosDeviceToken(user.id, deviceToken)
.then(function(user) {
return user;
})
.catch(function(error) {
console.log(error);
});
});
I have noticed that regardless of calling $ionicUser.identify(...) or not, an $ionicUser will be created (which you can see in the Dashboard). Now the issue that I'm having is that I'm not always getting a device token. ie. I end up with some(not all) Ionic Users with no device tokens (therefore no device token I can store on my server), eg.
Do you guys know what's going on? I'm reading the FAQ here and it says: > "If you are using $ionicPush.register() without passing a user object, you should make sure you are waiting for the $ionicPush.identify() promise to complete." -- Could this be the likely cause? and How do I pass a user to $ionicPush.register()?
Let me know what you guys think, I really need to know what I'm doing wrong.
Regards,
-J

Categories