Publish subscribe doesn't seem to work - javascript

I'm new to meteor and I saw that it's better to remove autopublish.
So I try to publish and subscribe a collection to get two different values.
In my meteor side I have :
Meteor.publish('channelUser',
function(){
var user = Meteor.users.findOne({
'_id':this.userId
});
console.log(user);
var test = Channels.find({
'_id': {$in : user.profile.channelIds}
});
return test;
}
);
Meteor.publish('channelToJoin',
function(){
var user = Meteor.users.findOne({
'_id':this.userId
});
console.log(user);
var test = Channels.find({'_id': {$nin: user.profile.channelIds}});
console.log(test);
return test;
});
And in my client side in a first component I have :
this.channelSub = MeteorObservable.subscribe('channelUser').subscribe();
this.channels = Channels.find({});
And on a second component :
Meteor.subscribe("channelToJoin");
this.channels = Channels.find({}).zone();
But on my client side on both of the component, I have the same data.
Is there some kind of conflict in the subscribe ?
I hope I was clear to describe my problem !

Pub/Sub just fills your Client collection Channels.
You can see it as a flow filling your local bucket. You may have several subscriptions filling different documents of Channels collection, but all end up in that single collection on the Client.
Then you have to adjust your query on client side to get the documents you need (e.g. Channels.find({'_id': {$nin: user.profile.channelIds}}); on Client as well). Of course you may have different queries in different templates, and different from the server publication as well.
See also How do I control two subscriptions to display within a single template?

You cannot move a document between collections via a subscription. If you subscribe to get a document that's in Pages collection, defined as new Meteor.Collection("pages"), then no matter how your pub channels look like, on the client the document will be found in the collection defined as new
> Meteor.Collection("pages")
. So remove all traces of MyPages and use Pages on the client as well.

Related

How to create sockets that particular to user and disposable on Socket.Io

I write a Node.Js app and I use Socket.Io as the data transfer system, so requests should be particular to per user. How can I make this?
My actual code;
node:
io.on('connection', (socket) => {
socket.on('loginP', data => {
console.log(data);
})
})
js:
var socket = io('',{forceNew : false});
$("#loginbutton").click(function() {
var sessionInfo = {
name : $("#login input[name='username']").val(),
pass : $("#login input[name='pass']").val()
}
socket.emit("loginP", sessionInfo)
})
It returns one more data for per request and this is a problem for me. Can I make this on Socket.Io or should I use another module, and If I should, which module?
If I understand your question correctly (It's possible I don't), you want to have just one connection from each user's browser to your nodejs program.
On the nodejs side, your io.on('connection'...) event fires with each new incoming user connection, and gives you the socket for that specific connection. So, keep track of your sockets; you'll have one socket per user.
On the browser side, you should build your code to ensure it only calls
var socket = io(path, ...);
once for each path (your path is ''). TheforceNew option is for situations where you have multiple paths from one program.

Meteor: Best practice for modifying document data with user data

Thanks for looking at my question. It should be easy for anyone who has used Meteor in production, I am still at the learning stage.
So my meteor setup is I have a bunch of documents with ownedBy _id's reflecting which user owns each document (https://github.com/rgstephens/base/tree/extendDoc is the full github, note that it is the extendDoc branch and not the master branch).
I now want to modify my API such that I can display the real name of each owner of the document. On the server side I can access this with Meteor.users.findOne({ownedBy}) but on the client side I have discovered that I cannot do this due to Meteor security protocols (a user doesnt have access to another user's data).
So I have two options:
somehow modify the result of what I am publishing to include the user's real name on the server side
somehow push the full user data to the clientside and do the mapping of the _id to the real names on the clientside
what is the best practice here? I have tried both and here are my results so far:
I have failed here. This is very 'Node' thinking I know. I can access user data on clientside but Meteor insists that my publications must return cursors and not JSON objects. How do I transform JSON objects into cursors or otherwise circumvent this publish restriction? Google is strangely silent on this topic.
Meteor.publish('documents.listAll', function docPub() {
let documents = Documents.find({}).fetch();
documents = documents.map((x) => {
const userobject = Meteor.users.findOne({ _id: x.ownedBy });
const x2 = x;
if (userobject) {
x2.userobject = userobject.profile;
}
return x2;
});
return documents; //this causes error due to not being a cursor
}
I have succeeded here but I suspect at the cost of a massive security hole. I simply modified my publish to be an array of cursors, as below:
Meteor.publish('documents.listAll', function docPub() {
return [Documents.find({}),
Meteor.users.find({}),
];
});
I would really like to do 1 because I sense there is a big security hole in 2, but please advise on how I should do it? thanks very much.
yes, you are right to not want to publish full user objects to the client. but you can certainly publish a subset of the full user object, using the "fields" on the options, which is the 2nd argument of find(). on my project, i created a "public profile" area on each user; that makes it easy to know what things about a user we can publish to other users.
there are several ways to approach getting this data to the client. you've already found one: returning multiple cursors from a publish.
in the example below, i'm returning all the documents, and a subset of all the user object who own those documents. this example assumes that the user's name, and whatever other info you decide is "public," is in a field called publicInfo that's part of the Meteor.user object:
Meteor.publish('documents.listAll', function() {
let documentCursor = Documents.find({});
let ownerIds = documentCursor.map(function(d) {
return d.ownedBy;
});
let uniqueOwnerIds = _.uniq(ownerIds);
let profileCursor = Meteor.users.find(
{
_id: {$in: uniqueOwnerIds}
},
{
fields: {publicInfo: 1}
});
return [documentCursor, profileCursor];
});
In the MeteorChef slack channel, #distalx responded thusly:
Hi, you are using fetch and fetch return all matching documents as an Array.
I think if you just use find - w/o fetch it will do it.
Meteor.publish('documents.listAll', function docPub() {
let cursor = Documents.find({});
let DocsWithUserObject = cursor.filter((doc) => {
const userobject = Meteor.users.findOne({ _id: doc.ownedBy });
if (userobject) {
doc.userobject = userobject.profile;
return doc
}
});
return DocsWithUserObject;
}
I am going to try this.

MeteorJS - No user system, how to filter data at the client end?

The title might sound strange, but I have a website that will query some data in a Mongo collection. However, there is no user system (no logins, etc). Everyone is an anonymouse user.
The issue is that I need to query some data on the Mongo collection based on the input text boxes the user gives. Hence I cannot use this.userId to insert a row of specifications, and the server end reads this specifications, and sends the data to the client.
Hence:
// Code ran at the server
if (Meteor.isServer)
{
Meteor.publish("comments", function ()
{
return comments.find();
});
}
// Code ran at the client
if (Meteor.isClient)
{
Template.body.helpers
(
{
comments: function ()
{
return comments.find()
// Add code to try to parse out the data that we don't want here
}
}
);
}
It seems possible that at the user end I filter some data based on some user input. However, it seems that if I use return comments.find() the server will be sending a lot of data to the client, then the client would take the job of cleaning the data.
By a lot of data, there shouldn't be much (10,000 rows), but let's assume that there are a million rows, what should I do?
I'm very new to MeteorJS, just completed the tutorial, any advice is appreciated!
My advice is to read the docs, in particular the section on Publish and Subscribe.
By changing the signature of your publish function above to one that takes an argument, you can filter the collection on the server, and limiting the data transferred to what is required.
Meteor.publish("comments", function (postId)
{
return comments.find({post_id: postId});
});
Then on the client you will need a subscribe call that passes a value for the argument.
Meteor.subscribe("comments", postId)
Ensure you have removed the autopublish package, or it will ignore this filtering.

Can you list users in Meteor without a login system?

For example, if I did a chatroom where all you ahve to do is enter a username (so you don't log in), can I still somehow list out all the people in that chatroom? How can I do this?
Instead of using the normal Meteor.users collection, it would probably be easiest to create your own collection for such simple authentication.
If you wanted to make it really simple (most likely, as long as you didn't care if 2 people have the same name), just store the name as a property of a chat room message document.
Edit - answer to comment:
To detect when a user disconnects, you can add an event handler in your publish function like this:
Meteor.publish('some_collection', function(){
var connectionId = this.connection ? this.connection.id : 'server';
this._session.socket.on("close", Meteor.bindEnvironment(function(){
// deal with connectionId closing
}));
});
You can do that.
Simply publish all users and every coming client should subscribe.
server:
Meteor.publish('allUsers', function(){
return Meteor.users.find({},{fields:{profile:1}});
})
client :
Meteor.startup(function(){
Meteor.subscribe('allUsers');
})
Template.listOfUsers.users = function(){
return Meteor.users.find();
}
This is very basic example, which should be adjusted to your needs.

Double indexing in associative array

I'm building a server using Node.js. I store the objects representing my users in an associative array and right now the index of those objects are the sockets id of that connection. Using the Socket.io library i obtain it by simply doing socket.id, so for each handler that takes care of my requests I can always know which user made the request.
Client-side every user has the ids of the connected users (that are different from the socket ids).
The problem araises when i have an handler that is used to send a message from an user to another, i make an example:
User A needs to send a message to user B, my protocol specifies the message as something like this:
MSG_KEY:{MSG:'hello world',USER_ID:'12345'}
Server-side i have an handler that listens to "MSG_KEY" and when the message is sent it is executed and using the socket.id I can retrieve who made the request, but the problem is that i need to get also the user B but this time using his USER_ID. I don't want to use the socket.id to avoid session spoofing.
I first thought about indexing in my array the users by indexing them both from socket.id and user id.
My question is: is it a good idea to do this? Does socket.io provide a way to simplify this?
There's no particular reason you can't do that. I would use two separate objects (maps), and probably have the users be represented by objects rather than strings so that there was really only one user (referenced from two places).
E.g., a user:
var user = {userid: "JohnD", name: "John Doe"};
and then:
var usersByID = {};
usersByName[user.userid];
and
var usersBySocket = {};
usersBySocket[theSocketID] = user;
Or even
var users = {
byID: {},
bySocket: {}
};
users.byID[user.userid] = user;
users.bySocket[theSocketID] = user;

Categories