Meteor, get all users on a specific page - meteor

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}})
});
}
});

Related

How to Reproduce Meteor.user() Client-Server effect for a different Collection?

Just how Meteor.user() method is available on Client & Server for the "current user" I would love to reproduce this kind of functionality for different custom collections. For example, my app uses a "clouds" collection as a type of room for a group of users to be in. Obviously there are various cloud instances and I don't always want to be passing the cloudId into every single meteor method. Ideally I could have like a Meteor.cloud() function that would give me the current cloud on the client and server.
My thoughts on approaching this:
What I have been doing thus far is piggy-backing off of Meteor.user() by storing a currentCloudId property inside the user profile and setting that on a route beforeAction. However this limits the user to only being in 1 cloud at a time.
Using the Meteor.connection ID somehow to keep a map of connectionIds to cloudIds. This would work great in theory....however it seems that Meteor connection IDs cannot be heavily relied on as they might change during reconnects or other random scenarios. Also you would have to then "manange" that collection of "cloudConnections" and remove old stale ones and such.
Im using Iron Router....and if it were possible to get the current route data on the server that would also solve my problem but I am not sure how to access that on the server?
--- Basically I would love for a simple straight forward way to mimic Meteor.user() behavior for other collections.
Thanks for all your help :)
You can just create a function inside /lib that looks something like this:
getUserClouds = function getUserClouds () {
return Clouds.find({ $elemMatch: { $eq: Meteor.userId() } })
}
This will work both on the client and on the server. But it will always return a Cursor pointing to 0 docs. So you'll need a publication:
Meteor.publish('userClouds', function () {
return Clouds.find({ $elemMatch: { $eq: this.userId } })
})

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.users information from collection helpers

I'm trying to get some information via the collection-helpers package for non-logged in users and I'm obviously missing something fundamental here as I'm getting nowhere.
I have a relationship set up what is happily returning the profile.name element for the owner of a document, as long as that happens to coincide with the logged in user, but, I'm getting nothing back for non-logged in users (because of the security on the client side).
I've added a new publication on both client and server as
// User Profile
Meteor.publish("userProfile", function() {
return Meteor.users.find({_id: this.userId},
{fields: {'profile': 1}});
});
and have subscribed to this publication in the js associated with the page I'm trying to display it in
// Don't need this to be reactive, so
Meteor.subscribe("userProfile");
but am still not getting access to the profile data in the document with
<h4>Posted by: {{projOwner.profile.name}}</h4>
where projOwner looks like
projectDocs.helpers({
projOwner: function() {
console.log(this.owner._id);
var owner = Meteor.users.findOne(this.owner._id);
//console.log("Owner is: " +owner);
return owner;
}
});
What am I doing wrong??
In a publish function, this.userId is always the id of the currently logged in user. The profile of the current user is automatically published so that function doesn't do anything useful.
The real problem here is you need to get the correct subset of users published to the client. Maybe that's the project owner of the document you are looking at, maybe it's a all of the users in a group, etc. Without knowing more about your problem it's hard to say.
An easy place to start with is just publishing all of the users to make sure your code works, and then try reducing the set. Remember that publish functions can take arguments, so you could pass in, for example, the id of a project and then publish the owner like so:
Meteor.publish('projectOwner', function(projectId) {
check(projectId, String);
var project = Projects.findOne(projectId);
return Meteor.users.find(project.owner, {
fields: {'profile': 1}
});
});

Accounts.createUser create users, but only from the server and not allow the client to create user

first excuse my writing, I'm using google translator.
What I want is to create users meteor, but only from the server and not allow the client to create users.
try putting
Accounts.validateNewUser (function () {
return false;
});
but it denies me create users to the server (which I do not want)
thank you very much in advance.
You can use Accounts.config for that purpose. Just anywhere in your server code do
Accounts.config({
forbidClientAccountCreation: true,
});
One way to do this would be to add some kind of un-guessable string to the profile key when you create a user on the server, check for that key in the validateNewUser function and then remove it immediately using observe on an appropriate cursor.
On the server you would have:
Accounts.validateNewUser(function(user) {
return (user.profile && user.profile.createUserKey === [KEY]);
});
Meteor.users.find().observe({
added: function(user) {
Meteor.users.update(user, {$unset: {'profile.createUserKey': true}});
}
});
And then your server-side createUser line looks like this (along with any other data you want to pass in the profile):
Accounts.createUser({username: [USERNAME], password: [PASSWORD], profile: {createUserKey: [KEY]}});
Assuming you don't publish the validation function anywhere (like Github), a client will have no way of knowing what the key is and would thus be unable to create a new user. If you don't trust yourself not to push it to Github or similar, store the key in an unpublished collection in your MongoDB and then pull it out on server start-up - that's what I do.
NOTE I think it's slightly unsatisfactory that I'm using a cursor here as it would be far better to remove the key in an onCreateUser callback, but this seems to be called before validateNewUser, so it's no use here. The alternatives are using your own modified version of the createUser function in the Accounts package, which probably isn't too tough but is a little involved to go into here, or just leaving the key in the user document and making sure you don't publish it.

How to know when user document loaded in Meteor Accounts

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
});

Resources