How do I cleanup data after Firebase .unAuth()? - firebase

I'm using Firebase in a website (and it's awesome).
Via several .on('child_added') and .on('value') callbacks I'm populating a local store that is bound to my UI.
When a user signs out, I want to clean their data out of my local store. What is the recommended way to react to a user signing out with Firebase? Ideally I would like to pass a callback to .unauth() to do a cleanup. But there's no such callback.
My current solution is a bit messy...
I listen with onAuth().
When onAuth triggered with a non-null value I set a variable isLoggedIn = true
When onAuth is triggered with null and isLoggedId === true then I perform a cleanup.
I don't want to do a clean up every time onAuth is called with null because it does this on page load.

If the user triggers the logout, that means your code is calling unauth(). That would also be the moment to clean up:
ref.unauth();
cleanupData();
But if the user gets signed out for another reason (i.e. their session expiring), then cleaning up in the null part of onAuth() makes sense. If you're worried about the initial null, you could wrap this in a check:
var previousAuthData = null;
ref.onAuth(function(authData) {
if (authData) {
...
}
else if (previousAuthData != null) {
cleanupData();
}
previousAuthData = authData;
}

Related

(React native) Can't get data out of the Firebase database method

i trying to get the data from my database, in componentWillMount(), it works fine with this :
var userData = null
firebase.database().ref('/users/' + user.uid).once('value').then(function(snapshot) {
userData = snapshot.val()
console.log(userData)
});
But it only works in the method only, i tried to asign the value to a variable but i can't get it outside even with this.setstate.
I am really lost it looks easy but i don't know how ...
Thanks for help.
once() is asynchronous and returns immediately with a promise. The promise does not block your code when you attach a then callback to it. userData won't be populated in the callback from the promise until after the database query completes, and you have no guarantee when that will be. If your code tries to access userData before it's finally populated, it will still have its original null value.
Well, what's happening here is,
your firebase method is taking a time to get data and because everything is asynchronous here, javascript will not wait until your firebase method is executed.
Immediate next line of firebase.database().... will be executed before this completes.
So you might need to set your data into state using setState or you can use a class variable instead of a local variable.
do this,
constructor(props){
super(props);
this.state={
data:null //either do this
}
this.localData=null; // or this
}
firebase.database().ref('/users/' + user.uid).once('value').then(function(snapshot) {
this.setState({data:snapshot.val()});
this.localData = snapshot.val();
console.log(this.localData)
});

Fetch data for record edit page

I have a page that lets you edit user data. I'm using FlowRouter for the routing and it can be found on the route /employees/:id.
I need to update the detail form when data changes on the server and leave the route if it was deleted by other client.
I decided to use Tracker.autorun which informs me whenever the data changes. The previous user info is stored on the template so it's easy to tell if the record was deleted.
Template.UpdateEmployee.onCreated(function () {
const self = this;
self.subscribe('user', FlowRouter.getParam('id'));
self.autorun(function () {
const _id = FlowRouter.getParam('id');
const user = Meteor.users.findOne({_id});
if(!user && self.user)
FlowRouter.go('/employees');
self.user = user;
if(!user)
return;
user.email = user.emails[0].address;
$('.ui.form').form('set values',user);
});
});
And lastly in the onRendered callback I'm checking if the data was set on template as I believe not doing so could lead to data being available before the template is rendered and hence values wouldn't get set properly. Is this correct?
Template.UpdateEmployee.onRendered(function () {
if(this.user){
user.email = user.emails[0].address;
$('.ui.form').form('set values',user);
}
});
Are there any pitfalls to this solution?
I can see a couple drawbacks inherently. The first one is doing a find query on the client. Typically you would want to return data from the server using Meteor publish and subscribe.
The second is you are passing the key to find the data over the URL. This can be spoofed by other users for them to find that users data.
Lastly if you are doing a find on the user object, I assume you might be storing data there. This is generally bad practice. If you need to store user data with their profile, it's best to create a new collection and publish/subscribe what you need.

Invoke value event handler only on value change in Firebase

Is there a way to invoke the value event handler only on value change in Firebase? If I just add the value event handler without any special code, it will be called once on reading the initial value and then on any value change. I want to avoid invoking the event handler on reading the initial value.
One of the use cases where I need this functionality is that I am working on an app where user can add tasks for some other users. The other users can either accept or reject a task. There is a server which is monitoring these tasks on Firebase and it sends a push notification to the creator of the task whenever a user accepts or rejects a task i.e. whenever there is a change in the status of task.
I have tried two solutions.
Solution 1:
Have a map and add an entry to the map when the value event handler is called if it is not already present and if the the entry is present, then do the work required on value change. This solution works but I need this in several different cases and I have to create this map in all such cases and I didn't find it to be a convenient solution.
Solution 2:
Add the value event handler like this
ref.on('value', (snapshot) => {
console.log('Initial call ', this.initialCall);
if(!this.initialCall) {
// Do work
} else {
this.initialCall = false;
}
}, {initialCall: true}) // {initialCall: true} is the context which is provided as this in the event handler
Here my thinking was that I can check if the event handler is called for the initial value by checking if this.initialCall is set to true. If it is, then set the this.initialCall to false and then in the subsequent invocations, this.initialCall should be false and I can do the work required for a value change. Using this solution what I observed was that the this.initialCall was undefined the first time the handler is called and then it was set to true and then it was true for all value change event handler invocations for all the tasks, not just the one for which it was set to true.
With regards to solution 2, I am not a JS expert and it is possible that due to some gap in my JS knowledge, I am doing something wrong due to which it doesn't work as I expected.
Please let me know if you have an idea/solution which can be used to call the value event handler only on value change.
A value event in Firebase is invoked immediately with the current value and then whenever the value changes. If you only care abut when the value changes, you can simply ignore the initial event:
var isInitialValue = true;
ref.on('value', function(snapshot) {
if (isInitialValue) {
isInitialValue = false;
}
else {
// TODO: handle subsequent changes
}
});

How to prevent a client race condition between Meteor.userId() and subscription updates that depend on userId?

I am seeing a repeatable issue where a user authenticates ("logs in") with a Meteor server, and then a client subscription that depends on userId is updated (and dependent UI templates reactively update) before Meteor.userId() registers the successful login.
For example, in this code snippet, the assert will throw:
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function () {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
});
}
if (Meteor.isClient) {
var sub = Meteor.subscribe('mineOrPublic');
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
Analogous to the added function above, if I write a template helper that checks Meteor.userId(), it will see a value of null, even when it is invoked with a data context of a document with an owner.
There is apparently a race condition between Meteor collection Pub/Sub and the Account userId update mechanisms. It seems to me that Meteor.userId() should always be updated before any subscriptions update based on a change in this.userId in a server publish function, but for some reason the opposite usually seems to be true (that is, the assert in the code above will usually throw).
The reason I care is because I have packages that depend on obtaining a valid Meteor Authentication token (using Accounts._storedLoginToken()) on the client for use in securing HTTP requests for files stored on the Meteor server. And the authentication token isn't correct until Meteor.userId() is. So the flow of events usually goes something like this:
User logs in
Publish function on server reruns based on the change in this.userId.
Client begins receiving new documents corresponding to the change in userId.
UI Template reactively updates to add DOM elements driven by new documents
Some of the DOM elements are <img> tags with src= values that depend on the data context.
HTTP requests are triggered and ultimately fail with 403 (forbidden) errors because the required authentication cookie hasn't been set yet.
Meteor.userId() finally updates on the client, and code reactively runs to set the authentication cookie
Helpers in the template that depend on a session variable set in the cookie update code are rerun, but the DOM doesn't change, because the URLs in the <img> tags don't change.
Because the DOM doesn't change, the tags don't retry their failed attempts to load the images.
Everything settles down, and the user has to manually reload the page to get their images to appear.
I've come up with two possible approaches to work around this issue:
In the template helper that generates the URL for the <img> tag, always append a dummy query string such as: "?time=" + new Date().getTime(). This causes the DOM to change every time the helper is called and fixes the problem, but it screws-up browser caching and if not coordinated will cause some assets to unnecessarily load multiple times, etc.
In every template helper that touches document data add a test of:
if (this.owner && this.owner !== Meteor.userId()) {
// Perhaps Meteor.loggingIn() could be used above?
// Invalid state, output placeholder
} else {
// Valid state, output proper value for template
}
I really hope someone knows of a less kludgy way to work around this. Alternatively, if consensus arises that this is a bug and Meteor's behavior is incorrect in this respect. I will happily file an issue on Github. I mostly really enjoy working with Meteor, but this is the kind of gritty annoyance that grinds in the gears of "it just works".
Thanks for any and all insights.
After trying lots of things, this variation on the example code in the OP seems to consistently solve the race condition, and I find this an acceptable resolution, unlike my initial attempted workarounds.
I still feel that this kind of logic should be unnecessary and welcome other approaches or opinions on whether Meteor's behavior in the OP sample code is correct or erroneous. If consensus emerges in the comments that Meteor's behavior is wrong, I will create an issue on Github for this.
Thanks for any additional feedback or alternative solutions.
var coll = new Meteor.Collection("test");
if (Meteor.isServer) {
Meteor.publish('mineOrPublic', function (clientUserId) {
if (this.userId === clientUserId) {
// Publish public records and those owned by subscribing user
return coll.find({owner: { $in: [ this.userId, null ]}});
} else {
// Don't return user owned docs unless client sub matches
return coll.find({owner: null});
}
});
}
if (Meteor.isClient) {
Deps.autorun(function () {
// Resubscribe anytime userId changes
var sub = Meteor.subscribe('mineOrPublic', Meteor.userId());
});
var cursor = coll.find({});
cursor.observe({
added: function (doc) {
if (doc.owner) {
// This should always be true?!
assert(doc.owner === Meteor.userId());
}
}
});
}
This code works by giving the server publish function the information it needs to recognize when it is running ahead of the client's own login state, thereby breaking the race condition.
I think this is something that Meteor should do automatically: clients should not see documents based on changes to this.userId in a publish function until after the client Meteor.userId() has been updated.
Do others agree?
I tried with this code that works on server too. In association with FileCollection package.
if (Meteor.isServer) {
CurrentUserId = null;
Meteor.publish(null, function() {
CurrentUserId = this.userId;
});
}
....
OrgFiles.allow({
read: function (userId, file) {
if (CurrentUserId !== file.metadata.owner) {
return false;
} else {
return true;
}
}
...

Show a loader during the user log in process

I'm using the Accounts API to manage users. My app first tries to log in a user using their credentials, and in case that results in an error, it creates a new user account using the input credentials.
// Log the user in
Meteor.loginWithPassword(username, token, function(error) {
if(error) { // create a new user account, log them in and show the page
Accounts.createUser({
username: username,
email: username + '#example.com',
password: token,
profile: {name: username}
}, showThePage);
}
else { // show the page
//showThePage();
window.location.reload();
}
});
But this code block executes only when the user was previously logged out from their browser, and if that is the case it takes 2-3 seconds for Meteor to log the user in using loginWithPassword. As I'm using v0.5.0, there is no Meteor.loggingIn(), and the only thing I have is Meteor.userLoaded(). Meteor, for some reason, performs the login operation twice -- once by loading a placeholder user (that has only its userId property set) and again by loading the actual user. This makes userLoaded() return true twice, because of which my loader image doesn't work as expected.
Also notice that in the else block inside loginWithPassword, I'm doing a window reload. I've a function showThePage() which contains all the template data & event binding code. That function retrieves data using the username of the logged in user. Now because when that function in else block executes there isn't a real user logged in (remember meteor takes time to log the user in), no data gets fetched.
Is there a workaround for this problem?
First of all Meteor.userLoaded goes away after you upgrade beyond 0.5.0. You should check if Meteor.userId() === null to know if the user login has completed, which works in 0.5.0 and beyond. It may get called multiple times, as you have noted, but only when it has a real value has the login completed.
If you really can't update to 0.5.1, use a session variable to store loggingIn in between the call to loginWithPassword and the callback.
Session.set('loggingIn',true);
Meteor.loginWithPassword(...
Session.set('loggingIn',false);
});
Then, use the Session.get('loggingIn') call where appropriate.
Want to adapt userLoaded() instead?
var userLoadedTimes = 0; // can't use session variable because it would mess up reactive context on increments
Session.set('loggingIn',false);
Meteor.autorun(function () {
var userLoaded = Meteor.userLoaded(); // reactive.
if (userLoaded)
userLoadedTimes++;
if ((userLoadedTimes % 2 == 0) && (userLoadedTimes != 0))
Session.set('loggingIn',true);
else
Session.set('loggingIn',false);
});
What's that modulo doing there? Well if userLoaded has called a reactive context twice for some reason, you have actually logged in. So we check if userLoadedTimes is a multiple of two/even. All other times, i.e., when userLoadedTimes is odd (userLoadedTimes % 2 == 1), we're looking at the fake user... which means we're still loading the real user!
If this doesn't work, apply the even/odd logic to the first solution using session variable changes on the callback, in the event Meteor calls the loginWithPassword callback twice.

Resources