Meteor: Publish function requires a page refresh after user logs in - meteor

I have a meteor app and I'm calling all the publish functions at once in the iron router configuration like below. I only want to return the subscriptions once the user is logged in, so I check Meteor.userId():
Router.configure({
layoutTemplate: 'layout',
loadingTemplate: 'loading',
notFoundTemplate: '404',
waitOn: function () {
if (Meteor.userId()) {
return [Meteor.subscribe('users'),
Meteor.subscribe('company'),
Meteor.subscribe('projects'),
Meteor.subscribe('columns'),
Meteor.subscribe('cards'),
Meteor.subscribe('contents'),
Meteor.subscribe('labels'),
Meteor.subscribe('notifications')];
}
}
});
The publish functions have all the same structure, dependent on user.companyId, like this:
Meteor.publish('cards', function () {
if (this.userId) {
const user = Meteor.users.findOne({ _id: this.userId, companyId: { $exists: true } });
if (user) {
return Cards.find({ companyId: user.companyId });
}
} else {
this.ready();
}
});
My problem is, when the user registers, the account is created and the companyId is saved to the user, but when they now login, the only way for the data to show up is to refresh the browser. I want it to be reactive.

From the meteor guide:
On the client, if anything in a reactive function changes, the whole
function will re-run, and the results are fairly intuitive.
On the server however, the reactivity is limited to the behavior of
the cursors you return from your publish functions. You’ll see any
changes to the data that matches their queries, but their queries
will never change.
You can indeed use reywood:publish-composite as suggested, but for your simple case I think reactive-publish would be much easier to get up and running.
Install the package, and just wrap your publication in a this.autorun:
Meteor.publish('cards', function () {
this.autorun( function() {
if (this.userId) {
const user = Meteor.users.findOne({ _id: this.userId, companyId: { $exists: true } });
if (user) {
return Cards.find({ companyId: user.companyId });
}
} else {
this.ready();
}
});
});

Related

Login page lets user get inside the app even if the authentication failed

I've made a simple app with phone authentication (sms).
My problem splits to two, the first part is that the verification code (sms) is always wrong somehow (I do get it, however it doesn't pass the confirmation), and the second part (as stated in the title) is that the user can still access the main activities even if authentication failed.
the function is invoked via a button.
the function is :
signIn(){
const appVerifier = this.recaptchaVerifier;
const phoneNumberString = "+972" + this.phoneNumber.substring(1,10);
firebase.auth().signInWithPhoneNumber(phoneNumberString, appVerifier)
.then( confirmationResult => {
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
let prompt = this.alertCtrl.create({
title: 'Enter the Confirmation code',
inputs: [{ name: 'confirmationCode', placeholder: 'Confirmation Code' }],
buttons: [
{ text: 'Cancel',
handler: data => { console.log('Cancel clicked'); }
},
{ text: 'Send',
handler: data => {
confirmationResult.confirm(data.confirmationCode)
.then(function (result) {
// User signed in successfully.
this.uid = result.user.uid
this.addUser(this.fullName, this.uid);
console.log(result.user);
// ...
}).catch(function (error) {
console.log("Invalid code") // always getting here
});
}
}
]
});
prompt.present();
}).catch(function (error) {
console.log("SMS not sent")
});
}
UPDATE (app.component)
the decision is made in the constructor of app.component.ts
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen) {
var that = this
platform.ready().then(() => {
// Okay, so the platform is ready and our plugins are available.
// Here you can do any higher level native things you might need.
statusBar.styleDefault();
splashScreen.hide();
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
that.rootPage = TabsPage; // even though auth failed, he comes here
} else {
that.rootPage = LoginPage;
}
});
});
}
I dont see it in your code but anywhere you call a method to push the main App-Page. You only should show the main App-Page after User successfully logged in. If this dont work maybe the user comes inside of your app, because the Firebase function is asynchron.

Accounts.onCreateUser clean up my code

When a new user account is created I'm using the Accounts.onCreateUser function to insert data into a new collection. I want to check that the insert has successfully worked before progressing. My code appears to work however it seems very messy. I'm wondering if there is a cleaner way to write this code.
Accounts.onCreateUser((options, user) => {
if (user) {
CandidateProfile.insert({
userId: user._id,
firstName: options.profile.name.first,
lastName: options.profile.name.last
});
var checkForNewCandidateProfile = CandidateProfile.findOne(
{ userId: user._id },
{ fields: { userId: 1 } }
);
var userId =
checkForNewCandidateProfile && checkForNewCandidateProfile.userId;
if (userId === user._id) {
return user;
}
}
});
Personally, I don't see any sense in your test. You don't trust insert?
But OK, you need it.
Be sure, that you run your code on the server side. Import it only on server side or just wrap it in if (Meteor.isServer)
Why check if user arg exists? It is, that's how that callback works.
If something's wrong, throw an error to abort user creation.
Possible variant:
if (Meteor.isServer) {
Accounts.onCreateUser((options, user) => {
// You insert sync, so it's up to you to handle errors.
try {
CandidateProfile.insert({
userId: user._id,
firstName: options.profile.name.first,
lastName: options.profile.name.last
});
var checkForNewCandidateProfile = CandidateProfile.findOne(
{ userId: user._id },
{ fields: { userId: 1 } }
);
var userId =
checkForNewCandidateProfile && checkForNewCandidateProfile.userId;
if (userId === user._id) {
return user;
}
} catch (error) {
throw new Error(error);
}
throw new Error("Something's wrong.");
});
}

Meteor reactive publish

This Meteor code displays a message on a headerLabel on a template, the server and/or the client changes the message by inserting a new message in HeaderLabelCol mongo collection and expect the client template to change since it publishes the last inserted document.
I was able to insert a new message using the client browser but did not show till I refreshed the page which may indicate that the reactiveness chain is broken somewhere. What is the problem? How can it be fixed? Thanks
//client.js
Template.header.helpers({
headerLabel: function () {
return HeaderLabelCol.findOne() ? HeaderLabelCol.findOne().headerLabel : 'Make a selection';
}
});
//server.js
HeaderLabelCol = new Mongo.Collection('headerLabelCol');
Meteor.publish('headerLabelCol', function () {
return HeaderLabelCol.find({userId: this.userId}, { sort: { createdAt: -1 } });
});
HeaderLabelCol._ensureIndex({createdAt: -1});
HeaderLabelCol.before.insert(function (userId, doc) {
doc.userId = userId;
doc.createdAt = Date.now();
});
HeaderLabelCol.allow({
insert: function (userId, doc) {
return (userId && doc.owner === userId);
}
});
I think you need to add the condition in your helper as well.
//client.js
Template.header.helpers({
headerLabel: function () {
var result = HeaderLabelCol.findOne({}, { sort: { createdAt: -1 } });
return result ? result.headerLabel : 'Make a selection';
}
});

Firebase profile integrations

What I am trying to do here is to implement a functionality on the start-up. I want my user's firebase authentication email variable to set a variable that represents the current user logged into my app?
With the following code the line that sets the user variable works after I click log in but not on page load! The console logs work perfectly on start-up but not the setting of user to the email...
crossfitApp.controller('globalIdCtrl', ["$scope", 'defautProfileData',
function ($scope, defautProfileData) {
var dataRef = new Firebase("https://glowing-fire-5401.firebaseIO.com");
//defautProfileData.country;
$scope.authenticated = {
currentUser: 10007,
emailAddress: "",
settings: "",
};
$scope.auth = new FirebaseSimpleLogin(dataRef, function (error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
//Not working
$scope.authenticated.currentUser = user.id;
console.log('User ID: ' + user.id + ', ProvideFr: ' + user.provider + user);
console.log(user);
} else {
console.log($scope.auth);
alert('deuces');
//!Trigger not logged in
}
});
}
]); //GlobaldCtrl
The callback to FirebaseSimpleLogin is not invoked inside the scope of Angular's HTML compiler. Normally, whenever you invoke ng-click, ng-submit, et al, Angular fires $scope.$apply(), which checks for any changes to the bound JavaScript variables and applies those to the DOM elements.
When an event outside of Angular changes a variable, you need to let Angular know by manually triggering a $apply event. The safest way to accomplish this is to use $timeout:
angular.controller('MyCtrl', function($scope, $timeout) {
$scope.auth = new FirebaseSimpleLogin(dataRef, function (error, user) {
if (error) {
// an error occurred while attempting login
console.log(error);
} else if (user) {
$timeout(function() {
$scope.currentUser = user.uid;
});
} else {
console.log('not logged in');
}
});
In general, prefer user.uid to user.id, as it is unique across providers.
A library like AngularFire can save you a lot of trouble, as it abstracts a lot of the complexities of integrating Firebase and Angular.

How to add fields to the Meteor.users collection

I want the Facebook accessToken that is stored in my user's document on the client. Following the meteor documentation, I should just add a new publish call.
In server.js:
Meteor.publish("access_token", function () {
return Meteor.users().find(
{ _id : Meteor.userId() },
{'services.facebook.accessToken': 1}
);
});
In client.js:
Meteor.subscribe("access_token");
Alright, here's where I get lost. Should the accessToken just show up in the Meteor.users collection now for the logged in user? Like:
var user = Meteor.users.findOne({ _id : Meteor.userId() });
console.log(user); // includes services.facebook.accessToken now
Obviously, I've tried the above and the accessToken doesn't show up. Yes, I've confirmed that the mongo document contains services.facebook.
So... do I create a new client collection and somehow hook it up to the new publish? How do I get the accessToken?
you should use "fields" keyword
Meteor.users.find({ _id: this.userId },
{ fields: { the-extra-fields-that-you-want-go-here: 1 } }
);
http://docs.meteor.com/#fieldspecifiers
You can publish the field you want:
Meteor.publish( null, function() {
Meteor.users.find({}, {fields: {profile: 1, username: 1, ...}})
}

Resources