I'm using this package to manage Facebook data as Collections in Meteor. Since a FacebookCollection can't be defined until the user is logged in with Facebook, this ticket describes a method to do so automatically; I've been trying it like so:
/lib/collections.js
Tracker.autorun(_.bind(function(){
var user = this.user;
if (user && user.services && user.services.facebook && !Friends){
Friends = FacebookCollections.getFriends("me",["id","name"],100);
}
}),this);
Using some log statements, I see this run only once, early on when user is still undefined, but never again. I suspect that this isn't being re-run because it's not referencing a reactive data source, i.e. the User collection, but I'm not quite sure what to do here to get this to work.
Note: the example in the ticket uses Meteor.user(), however, this gives the error:
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
Related
Its not at all clear in the documentation how to check if a freshly logged in user has logged into my web app previously. There is an isNewUser() call referenced here:
AdditionalUserInfo
What is not clear is how to get access to this call when using a firebase auth call and observer.
firebase.auth().onAuthStateChanged(authStateObserver);
The authStateObserver gets a user object after the person logs in using the provider specified. This user object cannot be used to call .isNewUser() so how does one get to the additionalUserInfo which has the call to .isNewUser() from inside this authStateObserver? This is in javascript in a web app.
firebase.auth().onAuthStateChanged only triggers with the FirebaseUser. You can't get additionalUserInfo or any credential eg. OAuth tokens associated with the result from that observer. You have to get it from the firebase.auth.UserCredential after the sign-in promise resolves.
As these results are only available once on sign-in and Auth does not refresh OAuth credentials or actively update underlying OAuth profiles, Firebase Auth opted not to provide them in onAuthStateChanged listener as it could mislead developers to think that the listener can be used to listen/get new credentials or additional user data when in reality this information is only available once on sign-in.
The observer will only observe changed to the FirebaseUser, eg. sign-in or sign-out events.
For isNewUser(), go into the FirebaseUser class, click on the child class, it should take you to class zzn (for Android & as of now). Search for "isNewUser", you will find a field tagged with that string. It is zzj:
#Field(
id = 10,
getter = "isNewUser"
)
private boolean zzj;
Now search for the getter of that field, in my case it is:
public final boolean zzj() {
return this.zzj;
}
So in your code base, this is the condition: ((zzn) firebaseUser).zzj()
All you have to do is follow the Zzzzz letters. Smh (painful naming).
Still on my meteor app, i'd like to now how to improve my data handling from minimongo.
Used to SQL / PHP, I'd like to know how to find() an object from my minimongo collection only one time, and access each of its properties with helpers, without having to re-access the collection each time.
Until now, what I did was something like that :
Template.profile.helpers({
name: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.name;
},
phone: function(e, tmpl){
return Meteor.users.FindOne({_id: Meteor.userId()}.profile.phone;
}
[...]
});
But it's getting boring and i guess there must be a more efficient way to deal with it, something where I could load my entire users information only one time, and then display with a helper taking one parameter, to display the data like that : {{data name}}, {{data phone}}
With only one helper like that :
Template.profile.helpers({
data: function(aString){
if (aString == "phone)
return [...].phone;
}
}
[...]
});
Of course, I can use a session value, but I'm not sure it's as relevant as I could do.
Another thing : how to end a Meteor session ? Because with PHP, the session ends at the closure of the browser, and cookies stay for a given duration, but with meteor session, i never have to reconnect as day after day, my logs seems to stay.
Would someone guide me through this or give me some good habits / tips ? I'm still reading the doc, but it's quite huge :S
Thanks you !
One thing to note here -- you're not actually sending a request to the server when you call Mongo.Collection.find() on the client -- that's getting handled by the miniMongo instance on the client, so it's not really that inefficient. Also, in this particular instance, the current user is always available as Meteor.user() -- no need to use Meteor.users.find({_id: Meteor.userId()}); (see docs for more info)
However, if you really wanted to cache that value, you could do something like this:
// JS
var data; // scoped so it's available to the whole file
Template.profile.onCreated({
data = Meteor.users.findOne({_id: Meteor.userId()}); // above scope makes this new definition available to the whole file
});
Then, to do what you're describing with the string arguments, you can do something like...
Template.profile.helpers({
data: function(aString) {
if (data) { return data[aString]; }
}
});
Perhaps a better option even is to send the Template a data context -- something like this in your HTML file:
{{> profile currentUser}} <!-- currentUser returns Meteor.user() -->
Which is directly available to your helpers as this:
Template.profile.helpers({
data: function(aString) {
return this[aString];
}
});
You can also pass data contexts through IronRouter if you're using that for your routing.
As for the Meteor session, the model is different than the model for PHP. In PHP, you store session information on the server, and every time you access the server, the browser (or client, more generally) sends along the session ID so it can look up anything pertaining to your session (see this question for a potentially better explanation). Meteor keeps track of clients that are connected to it, so the session will stay open as long as your browser is open. If you need to manipulate Meteor user sessions, take a look at this question.
I recommend finding the collection helpers package on Atmosphere. This will enable you to write currentUser.name in a template and it'll automatically return users name helper function returns.
There is no problem working like this. It doesn't matter if the function is called multiple times. It won't hurt your apps performance.
PHP and meteor sessions are different. A meteor session lasts for as long as browser window remains open or the page is refreshed. You are right that meteor sessions are not the way to go for your problem.
I have a Meteor method that returns all user accounts on my application
returnUsers: function(){
return Meteor.users.find().fetch();
}
I'm using new ReactiveVar to pass the return value of the Meteor method into my template helper:
Template.listViewTemplate.created = function (){
var self = this;
self.myAsyncValue = new ReactiveVar("Waiting for response from serv er...");
Meteor.call('returnUsers', function (err, users) {
if (err)
console.log(err);
else
self.myAsyncValue.set(users);
});
}
Template.listViewTemplate.helpers({
userCollection: function(){
return Template.instance().myAsyncValue.get();
}
});
But when I go to render the users into the view, I get a console error that reads
{{#each}} currently only accepts arrays
When I render without the #each iterator, using
<ul id='usersList'>
{{userCollection}}
</ul>
the output on my web-page accurately reflects the number of users (2), but reads
[object Object],[object Object]
I'm pretty sure that there is some funkiness going on here because I'm using a global Meteor collection (Meteor.users.find().fetch(), as opposed to having defined my own collection), but I'm not sure how to get around it.
I want to display a list of all users so the current user can click another user and share a document with them--not sure how to get around this.
You don't need to use a reactive variable for this. The function at Template.listViewTemplate.created is not container in an autorun, which means: It won't get recomputed.
The best approach for your scenario is: Use a variable to get the status ( loading, loaded, error) and another variable to save the array itself attach to self. Reactivity is cool but you should only use it when needed.
About:
[object Object],[object Object]
This is happening because you're not extracting any value form the object provided nor looping using {{#each}}.
Your solutions for listing users is dangerous and inefficient. You're sending to the client all the fields from the user collection, including login tokens.
The best approach is to create a subscription that send only the necessaries fields like: _id, info.firstName. You should also have some criteria to the list users and use pagination. Consider also a search feature for such purpose.
ReactiveVar doesn't like arrays. You could install the ReactiveArray package which should accomplish exactly what you want.
Update
Based on comment of mper
In the latest versions of Meteor you can put an array in a ReactiveVar.
Tested on
meteor#1.6.0
reactive-var#1.0.11
I have several remarks about your question:
Do not fetch
You don't need .fetch() on your method. When you call find() on collections, such as Meteor.users a cursor is returned. The template (and #each in particular) can iterate through cursors. Cursors are usually better because you don't load the entire collection into memory at once - fetch does.
Meteor collections are reactive
Meteor collections are already reactive, meaning that if they change, they will trigger changes on your templates as well. So, you don't need to use a ReactiveVar to wrap your collection.
Query your local database
You don't need to use a method to get the users and in fact, you shouldn't, because usually you want to make queries to the database stored locally, not make calls to the server. Just call Meteor.users.find() directly in your template helper. You can (and should) control what is available locally through subscriptions.
Use #each with else
You can use the following in your template:
{{#each userCollection}}
...
{{else}}
Waiting for response from server...
{{/each}}
If userCollection is empty, the template will render the else block, just like you wanted.
Summarizing
Delete your method and onCreated with everything inside, change whatever is inside your template helper to only return Meteor.users.find() and use {{#each userCollection}}...{{else}}Waiting for response from server...{{/else}}
By the way
In the latest versions of Meteor you can put an array in a ReactiveVar.
Template.onCreated(function(){}) only gets run once and meteor methods only run once
You need reactivity here.
Collections sre reactive meaning pub/sub.
You need to create a publish function that allows certain users to fetch other users in the database. So all uses with maybe if the currentUser has permission to read all user info. Id limit the fields too.
So this is my problem.
I can successfully login from my angularJS app using the auth factory i made that communicates to my pp rest API.
lets say Auth.login(user) -> (POST) myapi.com/user/login: the response is the user object that Auth saves locally. Thus Auth.getCurrentUser() returns local user object.
my rest API, i also have a myapi.com/user/get_loggedin_user which returns the current logged in user (using the php session). So if Auth.getCurrentUser should actually check if local user exists, if not do an ajax to myapi.com/user/get_loggedin_user and check if logged in before responding with null. One problem here is, ajax is annoying like this, you would then need to put in a success callback function and have all your code execute inside the success callback.
Now lets say im on the Angular App (website), mydomain.com/user/dashboard (already logged in), and then i refresh my browser. Now, when the page reloads, angular does not know my current user, so before it redirects me to mydomain/login, i want it to check if the user is logged in. i can obviously do a 1 time call within the controller, but is there a more easy way where i can register within a controller with some access restrictions (Eg: logged_in == true), and when you visit any page with logged in requirement, it checks local user (gets the user if does not exist), and redirects to login page if null, or display the page once it matches the requirements?
Different common page requirements: null, logged_in, admin, function: haveAccess(user, object).
NOTE: im using stateProvider
If I understood your question correctly, you are asking about how to check whether the user is logged in before the controller is invoked, and to avoid the check for a logged-in status in each controller that needs it.
If so, you should look into the resolve parameter - this exists both in $routerProvider and $stateProvide.
Essentially you could "resolve" your loggedInUser variable (by doing whatever you need to do via your MyAuth service.
Here's an example of what I mean with $routeProvider:
$routeProvider
.when("/someSecuredContent", {
templateUrl: 'someSecuredContent.html',
controller: 'SecuredController',
resolve: {
loggedInUser: function(MyAuth){
return MyAuth.loggedIn(); // MyAuth.loggedIn() should return a $q promise
}
}
});
Then in the controller, loggedInUser will be injected.
Here's a site with more examples.
Correct me if im wrong:
Do this within the Main Controller (make sure you inject the dependancies like rootScope, state, and your own Authfactory)
$rootScope.$on('$stateChangeStart', function (event, next, toParams) {
if (needToBeLoggedIn()) { //use the next object to read any data, and you can set on the state some flag
event.preventDefault()
MyAuth.loggedIn(function success(){ $state.go(next,toParams); }, function err (){/*send somewhere else*/});
}
})
Put logged_in = true to cookieStore in your login method after authentication as below.
$cookieStore.put('logged_in',true);
$rootScope.logged_in = true;
and in your Controller, do
$rootScope.logged_in = $cookieStore.get('logged_in');
Now you can use this logged_in variable anywhere in the UI to check if the user is logged in.
Make sure to use 'ngCookies' module in your app. and pass the $cookieStore dependency to your controller. You can even keep the user object itself similar to logged_in variable in cookies and retrieve it from cookies.
Make sure to do logged_in = false and clear other variables in cookies and set it to blank in your logout method.
I've been poking around in the Accounts packages, using a modified version of the ever-fabulous EventedMind Customizing Login screencast.
I modified it to use facebook instead of github, and I noticed something when trying to update user.profile information. Specifically, I'm looking for the right way/place to handle changes to user.profile.
Let's say, for example, that I authenticate as a FB user for the first time. When I do this, the CreateUser event will fire.
Using Accounts.onCreateUser(...), I can populate additional information from the FB graph into the profile, like so:
Accounts.onCreateUser(function(options,user){
var accessToken = user.services.facebook.accessToken,
result;
result = Meteor.http.get("https://graph.facebook.com/"+user.services.facebook.username, {
params: {
access_token:accessToken,
fields: ['picture', 'name','first_name','last_name','username','link','location','bio','relationship_status','email','timezone','locale']
}
});
if (result.error){
throw result.error;
}
user.profile = result.data; //lazily adding everything
return user;
});
This works just fine when the user is created. It's nice and clean.
But now let's say that some of the information changes. For example, let's say that the profile picture changes. If I log out and then back in to the meteor application, Accounts.onCreateUser(...) doesn't fire, because the user already exists. It's not being created again, it's being modified.
I need to update the user.profile on subsequent logins, or at least check for changes and then modify as needed. I'd ideally like to do this in similar fashion to .onCreateUser. Maybe with a .onModifyUser or something...
I can figure a couple of ways to do this using some checking and/or client-side code, but I'm wondering if there is an already-existing server hook that would be cleaner.
Any recommendations on the cleanest way to handle this situation?
Thanks in advance.
If you're manually calling the login functions you can pass a callback as the last parameter which will get called on the client after the login completes. See: http://docs.meteor.com/#meteor_loginwithpassword.
Meteor.loginWithFacebook({}, function (err) { /* make a Meteor method call here */ });
There are no documented server side callbacks at the moment.