How to know when user document loaded in Meteor Accounts - meteor

I understand that when writing code that depends on the collection being loaded into the client minimongo, that you should explicitly subscribe to the collection and pass in the appropriate callback for when it is finished loading.
My problem is that I store a lot of important subdocuments that my page needs to access in the users collection. I am using Meteor Accounts, and am trying to figure out a similar way to wait until the entire logged in user document is available. When using this to test:
console.log(Meteor.user());
the logged in case, it seems like it first registers an object with just the _id, and then sends the other fields later (I know I have to explicitly add other fields to publish from the server beyond email, etc.).
Is there a way for me to wait for the logged in user document to load completely before executing my code?
Thanks!

Deps.autorun (previously Meteor.autorun) reruns when something reactive changes, which might fit your use case:
Client js
Deps.autorun(function () {
if(Meteor.user() {
//Collection available
}
});
If you're using a subscription you can also use its callback. Have a read about it on the docs as you might have to customize it a bit, and remove the autopublish package as well as get your other collections set up to subscriptions
Server js:
Meteor.publish("userdata", function () {
//You might want to alter this depending on what you want to send down
return Meteor.users.find({}, {}});
});
Client js
Meteor.subscribe("userdata", function() {
//Collection available
});

Related

Meteor.users publish and subscribe do not match

I have removed the auto-publish package from my Meteor app and I have also created a publication on my server:
Meteor.publish("userData", function () {
return Meteor.users.find(
{_id: this.userId},
{fields: {'profile': 0}}
);
});
As you can see above I have set the profile to 0 which means I would like to exclude it. However...
On my client I have this code:
Meteor.subscribe("userData", (param) => {
console.log( Meteor.users.find(Meteor.userId()).fetch() )
})
and the output still includes the profile:
createdAt: Sat May 19 2018 11:16:25 GMT+0800 (+08) {}
emails: [{…}]
profile: {name: "Second Second"}
services: {password: {…}, resume: {…}}
username: "seconduser"
_id: "ESmRokNscFcBA9yN4"
__proto__: Object
length: 1
__proto__: Array(0)
What's the reason for this?
It is possible that some other subscription has subscribed to the profile field of the user.
You can find out if this is the case by looking at the information sent over the websocket.
Open the debugger,
find a "networking" tab,
find the websocket connection,
find the content or "frames".
There you can see which subs have been made and which updates the server publishes to a collection. See what it looks like without your sub; maybe the user doc is published already.
You see the profile field on the client since you have already edited and thus enabled this special field of the User object while creating or updating user. In order to secure this object from client-side modifications you can deny all writes from the client with the following server-side code.
// Deny all client-side updates to user documents
Meteor.users.deny({
update () { return true; },
});
So, even though the profile field is be available on the client within the Meteor.user() object, no modification can be made by the client.
If it is a custom data that you publish than you can control its exposition in your way. For example, let's assume that we introduce a new field customProfile into the user object, than with the following code, the customProfile will not be visible to the client.
Meteor.publish("userData", function () {
console.log('publishing userData with id', this.userId);
return Meteor.users.find(
{_id: this.userId},
{fields: {'customProfile': 0}}
);
});
You may find more information in the guide.
First of all, make sure whether you want to use Meteor.subscribe() or you want to use this.subscribe(). There is a lot of difference between them.
Meteor.subscribe() will keep subscription undestroyed when you change between screens/routes/UI.
this.subscribe() will have the scope of subscription till the life of Template exists. When you switch to other routes/path/UI, the subscription will be destroyed. This is used in a specific case when you have multiple kinds of subscription among consecutive transitions of the screen and problem occurs for unwanted data shown in UI despite filtering in Collection Query.
For more insight, click here.
Comming to your exact question, well when Meteor knows that you are a valid and logged in user, it sends entire Users specific collection fields _id, emails, profile, username on UI. So, it is recommended that you put only the required data into the User collection. Whether or not you make special kind of subscription to self-data, you will always be able to access your own data on UI, even on production build. You can check by putting console.log(Meteor.user()); in chrome console. This is how Meteor.user() is made, whether you like it or not. It was assumed by MDG (Meteor Development Group) that when the user has logged in, a user can fully access his/her own data at UI as it is safe and valid.
see below image for reference,

How can I make a Meteor publish method reactive to a server-side parameter?

I have a publication based on server-side user permissions. I want it to be reactive to changes in these permissions.
// SERVER CODE
Meteor.publish("my_publication", function(parent_id) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
ChildCollection.find({parent_id: parent_id, permissions: {$in: myPermissionsList}})
}
// CLIENT CODE
Tracker.autorun(function () {
Meteor.subscribe('my_publication', Session.get("my_parent_id"));
});
This properly returns all the elements of the "child" collection specified parent, as long as the parent says the user has at least one of the permissions in the child element's list. It does this without the user actually knowing what their permissions are, which is a requirement.
This behaves like one would expect in Meteor:
The subscription does automatically update if any of the returned ChildCollection elements are changed.
The subscription does automatically update if the client changes the "my_parent_id" Session variable, triggering the Tracker.autorun resubscribe.
The subscription does not automatically update if the permissions used to make the query (parent.permissionsDict[this.userId]) are changed.
We're looking for the best (highest performing) way to get an automatic update in the last case.
This article was a helpful, more detailed resource on the topic:
https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
My current understanding is that I need to utilize cursor.observeChanges() to react to changes in my permissions query. However, I am not sure how this fits into the rest of the Meteor publish/subscribe model--where would I call this, and how could the callback instruct Meteor to republish "my_publication"?
I believe https://atmospherejs.com/mrt/reactive-publish addresses this, but I feel like I should try to get a better grasp on core reactivity in meteor before turning to an external package. I also lack an understanding about the performance costs.
Any help would be greatly appreciated!
You can use the reactive-publish package (I am one of authors):
Meteor.publish("my_publication", function(parent_id) {
this.autorun(function (computation) {
//fetch our parent record and lookup this user's permissions
var parent = ParentCollection.findOne({_id: parent_id}, {fields: {permissionsDict: 1}});
var myPermissionsList = parent.permissionsDict[this.userId];
//use these permissions to make our query
return ChildCollection.find({parent_id: parent._id, permissions: {$in: myPermissionsList}});
});
}
It is important that you limit the fields you are interested in the parent document, otherwise autorun would rerun every time any field changes in the document, even if you do not care/use that field.

Using onResetPasswordLink, onEnrollmentLink, and onEmailVerificationLink methods properly in Meteor

I was wondering if someone would be kind enough to provide a meteorpad or code example of using one of the methods listed above properly in Meteor (with iron:router). I'm struggling to understand how exactly these methods interact with my app, and it seems these methods are new enough that there isn't much good documentation on how to use them correctly. Thanks!
http://docs.meteor.com/#/full/Accounts-onResetPasswordLink
Ok, so I am going to post what I ended up learning and doing here so others can use it as a reference. I'll do my best to explain what is happening as well.
As can be seen in the other comments, the 'done' function passed to the Accounts.on****Link callback was the main part that tripped me up. This function only does one thing - re-enables autoLogin. It's worth noting that the 'done' function/autoLogin is a part of one of the core 'accounts' packages, and cannot be modified. 'autoLogin' is used in one particular situation: User A tries to reset his or her pw on a computer where User B is currently logged in. If User A exits the reset password flow before submitting a new password, then User B will remain logged in. If User A completes the reset password flow, then User B is logged out and User A is logged in.
The pattern used to handle 'done' in the accounts-ui package, and what I ended up doing, assigns 'done' to a variable that can then be passed to your template event handler function, and run once your reset password logic is complete. This variable assignment needs to be done in the Accounts.on****Link callback, but the callback can be placed in any top-level client side code (just make sure you assign the scope of the variables correctly). I just put it at the start of my reset_password_template.js file (I've only done this for resetting passwords so far, but the pattern should be similar):
client/reset_password_template.js:
// set done as a variable to pass
var doneCallback;
Accounts.onResetPasswordLink(function(token, done) {
Session.set('resetPasswordToken', token); // pull token and place in a session variable, so it can be accessed later
doneCallback = done; // Assigning to variable
});
The other challenge of using these on****Link callbacks is understanding how your app 'knows' the callback has been fired, and what needs to be done by the app. Since iron:router is so tightly integrated with Meteor, it's easy to forget it is a separate package. It's important to keep in mind these callbacks were written to operate independently of iron:router. This means when the link sent to your email is clicked, your app is loaded at the root level ('/').
***Side note - There are some other answers here on StackOverflow that offer ways to integrate with iron:router, and load a specific route for each link. The problem for me with these patterns was that they seemed a bit hackish, and not in line with the 'meteor' way. More importantly, if the core Meteor team decides to alter the path of these registration links, these routes would break. I tried calling Router.go('path'); in the on****Link callback, but for some reason this didn't work in Chrome and Safari. I would love to have a way to handle specific routes for each of these emailed links, thus eliminating the need for constantly setting and clearing Session variables, but I couldn't think of a good solution that worked.
Anyways, as #stubailo described in his answer, your app is loaded (at the root level), and the callback is fired. Once the callback is fired, you have your session variable set. You can use this session variable to load the appropriate templates at the root level using the following pattern:
client/home.html (or your landing page template)
{{#unless resetPasswordToken}}
{{> home_template}}
{{else}}
{{> reset_password_template}}
{{/unless}}
With this, there are few things you need to take care of in your reset_password_template.js file, and home.js:
client/home.js
// checks if the 'resetPasswordToken' session variable is set and returns helper to home template
Template.home.helpers({
resetPasswordToken: function() {
return Session.get('resetPasswordToken');
}
});
client/reset_password_template.js
// if you have links in your template that navigate to other parts of your app, you need to reset your session variable before navigating away, you also need to call the doneCallback to re-enable autoLogin
Template.reset_password_template.rendered = function() {
var sessionReset = function() {
Session.set('resetPasswordToken', '');
if (doneCallback) {
doneCallback();
}
}
$("#link-1").click(function() {
sessionReset();
});
$('#link2').click(function() {
sessionReset();
});
}
Template.reset_password_template.events({
'submit #reset-password-form': function(e) {
e.preventDefault();
var new_password = $(e.target).find('#new-password').val(), confirm_password = $(e.target).find('#confirm-password').val();
// Validate passwords
if (isNotEmpty(new_password) && areValidPasswords(new_password, confirm_password)) {
Accounts.resetPassword(Session.get('resetPasswordToken'), new_password, function(error) {
if (error) {
if (error.message === 'Token expired [403]') {
Session.set('alert', 'Sorry, this link has expired.');
} else {
Session.set('alert', 'Sorry, there was a problem resetting your password.');
}
} else {
Session.set('alert', 'Your password has been changed.'); // This doesn't show. Display on next page
Session.set('resetPasswordToken', '');
// Call done before navigating away from here
if (doneCallback) {
doneCallback();
}
Router.go('web-app');
}
});
}
return false;
}
});
Hopefully this is helpful for others who are trying to build their own custom auth forms. The packages mentioned in the other answers are great for many cases, but sometimes you need additional customization that isn't available via a package.
I wrote this method, so hopefully I can give a good example of how to use it.
It's meant to be in conjunction with Accounts.sendResetPasswordEmail and Accounts.resetPassword (http://docs.meteor.com/#/full/accounts_sendresetpasswordemail and http://docs.meteor.com/#/full/accounts_resetpassword).
Basically, let's say you want to implement your own accounts UI system instead of using the accounts-ui package or similar. If you want to have a password reset system, you need three things:
A way to send an email with a password reset link
A way to know when the user has clicked the reset link
A method to actually reset the password
Here is how the flow should work:
The user clicks a link on your page that says "Reset password"
You find out which user that is (possibly by having them enter their email address), and call Accounts.sendResetPasswordEmail
The user clicks the reset password link in the email they just received
Your app is loaded and registers a callback with Accounts.onResetPasswordLink
The callback is called because the URL has a special fragment in it with the password reset token
This callback can display a special UI element that asks the user to input their new password
The app calls Accounts.resetPassword with the token and the new password
Now the user is logged in and they have a new password
This is a little complicated because it is the most advanced and custom flow possible. If you don't want to mess around with all of these callbacks and methods, I would recommend using one of the existing accounts UI packages, for example accounts-ui or https://atmospherejs.com/ian/accounts-ui-bootstrap-3
For some example code, take a look at the code for the accounts-ui package: https://github.com/meteor/meteor/blob/devel/packages/accounts-ui-unstyled/login_buttons_dialogs.js
Per the documentation:
You can construct your own user interface using the functions below, or use the accounts-ui package to include a turn-key user interface for password-based sign-in.
Therefore, those callback are for rolling your own custom solution. However, I would recommend using one of the following packages below, with accounts-entry being my preferred solution:
Use a combination of accounts-password and accounts-ui
Or use https://atmospherejs.com/joshowens/accounts-entry, especially if you want OAuth integrations such as Facebook, Twitter, etc. For handling email verification with this package, please see this Github issue.
It's been a year since this question but I just came up with the same problem.
Following your solution, what I found is that you could use the Session variable within the router and the onAfterAction hook to achieve the same, but using routes:
Router.route('/', {
name: 'homepage',
action: function() {
if (Session.get('resetPasswordToken')) {
this.redirect('resetPassword', {token: Session.get('resetPasswordToken')});
} else {
this.render('home');
}
}
});
Router.route('/password/reset/:token', {
name: 'resetPassword',
action: function () {
this.render('resetPassword');
},
data: function() {
return {token: this.params.token};
},
onAfterAction: function () {
Session.set('resetPasswordToken', '');
}
});
Of course, you will need also:
Accounts.onResetPasswordLink(function(token, done){
Session.set('resetPasswordToken', token);
doneResetPassword = done;
});

Meteor publication with calculations

I have 2 collections: Meteor.users and Projecs.
Users collection have field "projects" which contains array of user's project's ids.
"projects" : [
"jut6MHx6a7kSALPEP",
"XuJNvq7KTRheK6dSZ"
]
Also I have a publication for user's projects:
Meteor.publish('projects', function() {
var userProjects = Meteor.users.findOne(this.userId).projects;
return Projects.find({_id: {$in: userProjects}});
});
Everything works fine, but when I add new project (and update users ("projects" field) who are in this project) reactive publication doesn't works. Projects page doesn't contains recently added project. It works only when I refresh page.
Subscription made in router:
waitOn: function() {
return [
Meteor.subscribe('projects')
]
},
What should I do with this publication? Thanks a lot.
This is happening because Meteor.users is not reactive. I don't know what the reason behind but I saw many developers, specially developers who try to get famous by publish really cool articles about their awesome application, exposing the tokens.
So if some idiot publish the Meteor.users to the browser, it's a security flaw. It would be even worst if it was reactive because the token would be updated in realtime. Maybe this a block to newbie who don't really know that they're doing. Just my opinion about this decision.
This collection is design to be used for managing users and after the login, it makes no sense to use to store data, as it is designed.
Yea, this is a known "problem". Publish functions aren't reactive, so Meteor.users.findOne(this.userId).projects will only be evaluated when the client subscribes. You'll find a lot of information about this if you search for "meteor reactive joins", for example https://www.discovermeteor.com/blog/reactive-joins-in-meteor/
In your case, the clients will always have access to their array of project ids, right? Then the simplest solution would probably be to do something like this on the client:
Tracker.autorun(function(){
var user = Meteor.user()
if(user){
Meteor.subscribe(user.projects)
}
})
So, when the client notices that the array of project ids has changed, it renews the subscription (I'm unsure if passing user.projects to the subscribe call is necessary, but I'm a bit afraid that the subscription isn't is renewed if it's called with the same arguments as before).
Using the reactive-publish package (I am one of authors) you can do:
Meteor.publish('projects', function () {
this.autorun(function (computation) {
var userProjects = Meteor.users.findOne(this.userId, {fields: {projects: 1}}).projects;
return Projects.find({_id: {$in: userProjects}});
});
});
Just be careful to limit the first query only to projects so that autorun is not rerun for changes in other fields.

Meteor, get all users on a specific page

We are building a chat application and are currently working on a system to see all the users in a given room.
We have a Mongo Document set up with an array of active_users where we will push and pull user names to in order to keep track of the online users. We have come to the conclusion that realizing a user has connected to a given room is fairly simple. All we need to do is in the router, when a user accesses the page, we push that user's name into the document.
Now the tricky part is realizing when that user has left that given page? Obviously jQuery isn't a reliable option, so how do we know when a user's connection to a specific page is broken?
You could do this:
Meteor.publish("page", function() {
this._session.socket.on("close", function() {
//Change your active users here
});
});
and for your page that you track
Meteor.subscribe('page');
I use this in the analytics package on atmosphere
There's an Atmosphere package called Presence that does exactly what you need.
Some extra details from the README about keeping track of custom states...
State functions
If you want to track more than just users' online state, you can set a custom state function. (The default state function returns just 'online'):
// Setup the state function on the client
Presence.state = function() {
return {
online: true,
currentRoomId: Session.get('currentRoomId')
};
}
Now we can simply query the collection to find all other users that share the same currentRoomId
Presences.find({ state: { online: true, currentRoomId: Session.get('currentRoomId') } })
Of course, presence will call your function reactively, so everyone will know as soon as things change.
Meteor has connection hooks so you can run a function when the user disconnects from the server. Setting the onClose() callback inside a method called by the client will allow you to close the userId in the function.
Code on the server could be like this:
Meteor.methods({
joinRoom: function( roomId ){
var self = this;
Rooms.update( {roomId: roomId}, {$push:{userId: self.userId}});
self.connection.onClose( function(){
Rooms.update( {roomId: roomId}, {$pull:{userId: self.userId}})
});
}
});

Resources