Meteor publish/subscribe issue - meteor

I am obviously not understanding the publish/subscribe system.
I removed the insecure/autopublish packages.
In my server/publish.js I have:
Meteor.publish("profiles", function () {
return Meteor.users.find({}, {
profile: 1,
status: 1,
services: 0
});
});
In my client/lib/collection.js I have:
Meteor.subscribe("profiles");
Profiles = new Mongo.Collection("profiles");
Now, what I expected was to have a Profiles collection on the client which only contains the profile and status parts of the user document, and does not contain the services part (which has provate info like email and such).
However, on the client the Profiles collection is empty, while the Meteor.users() is available and has everything in it so that anyone who knows how to open the console can see private data about all users...
Anyone know what I'm doing wrong here?
EDIT: Interestingly, when I remove the publish profiles completely, then on the client side the Meteor.users has only the current user in it and only the profile section of it. Now I am totally confused.

UPS, the Meteor .find syntax is a little different from the standard mongodb .find syntax. I needed to add the "fileds" keyword and then it works as expected.
(almost, since my Profiles collection is still empty on the client...?)
Meteor.publish("profiles", function () {
return Meteor.users.find({}, {
fields: {
"services": 0
}
});
});
EDIT: Ok, thanks to Thai Tran I finally realized that in fact I DID misunderstand the publish/subscribe system. I wrongly thought the publish "name" on the server is going to be the collection name on the client. Now that I understand how it works it is clear why my Profiles collection is empty.

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

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

meteor: no page updates after updating collection

I have published and subscribed successfully two collections. On client side, i put the subscribe method into an autorun function and my collections updates every time i change some session variables. My data model looks like the following:
Topics: {
_id: ObjectID,
comments: [
commentId1,
commentId2,
etc...
]
}
Comments: {
_id: ObjectID,
}
When i create a new comment, i insert the comment in the Comments collection and update the Topics collection with a $push on the array. After this i expected, that meteor re-renders my page, but this happens only if i change my topic session variable and change it back or reload the page manually... Only then meteor renews the subscription.
Why is this so? I think i missed something... But the docs gave me no clues.
I guess, the reason is that you are performing database insertion on client side, but can't tell why it's not rendering page again.
One more way you can do that is, perform insertion on server side using Meteor.call() as:
if (Meteor.isClient){
....
....
Meteor.call('addComment', 'your_new_comment');
....
}
And on the server-side:
if(Meteor.isServer){
Meteor.Methods({
'addComment' : function(data){
//your insertion code here.
Comments.insert(data);
}
});
}
The same you can do for Topics also. This will update the relevant data on server-side, and because you have already published-subscribed those data, it will be automatically published on client, even though the session variable doesn't changes.

Resources