How to reactively run something on the server in Meteor? - meteor

It seems that most of the reactivity is on the client side.
I've got a server function that I need to run based on the number of items in a collection. The function creates a schema for an OrderSubmissions collection based on the number of documents in Services, which changes.
On the client this is easy - I simply do
/lib/schemas
if(Meteor.isClient){
Tracker.autorun(function(){
Meteor.subscribe('services', function(){
// re-creates OrderSubmissions schema any time the Services subscription returns different data
});
});
};
I could also rig up something with Session or ReactiveVar. Unfortunately, all of these techniques are only available to the client. I need to do:
/lib/schemas
if(Meteor.isServer){
// re-creates OrderSubmissions schema any time Services collection changes
};
Is the only way to do this to use .observe? It seems like kind of an expensive thing to do. My Services collection will change very very infrequently (in fact, at this point all I want to do is create the OrderSubmissions schema in /lib/schemas when my /server/fixtures.js is done loading stuff into the Services collection.)
Unfortunately for my case, Meteor loads stuff in /lib before /server, so my schema in /lib is being created erroneously:
/lib/schemas.js runs and OrderSubmissions schema, which depends on Services to be populated, gets created erroneously on both the server and client because there is nothing in the Services collection.
/server/fixtures.js runs and populates the Services collection.
I need #2 to happen before #1, but #2 needs to stay in server code. I don't want to wrap it in if(Meteor.isServer) in /lib since it's not secure.

If you add peerlibrary:server-autorun
You could do the following on the server
Tracker.autorun(function(){
// this code now reruns on the server if there is a reactive dependency enclosed here
})

Related

Meteor signaling without db write

I've been looking for a good way to do, but haven't found anything that doesn't seem hacky. I want to signal the client without going through the database and a subscription. For example, in a game I want to send a message to the client to display "Player 1 almost scores!". I don't care about this information in the long run, so I don't want to push it to the DB. I guess I could just set up another socket.io, but I'd rather not have to manage a second connection if there is a good way to go it within meteor. Thanks! (BTW, have looked at Meteor Streams, but it appears to have gone inactive)
You know that Meteor provides real-time communication from the server to clients through Publish and Subscribe mechanism, which is typically used to send your MongoDB data and later modifications.
You would like a similar push system but without having to record some data into your MongoDB.
It is totally possible re-using the Meteor Pub/Sub system but without the database part: while with Meteor.publish you typically return a Collection Cursor, hence data from your DB, you can also use its low-level API to send arbitrary real-time information:
Alternatively, a publish function can directly control its published record set by calling the functions added (to add a new document to the published record set), changed (to change or clear some fields on a document already in the published record set), and removed (to remove documents from the published record set). […]
Simply do not return anything, use the above mentioned methods and do not forget calling this.ready() by the end of your publish function.
See also the Guide about Custom publications
// SERVER
const customCollectionName = 'collection-name';
let sender; // <== we will keep a reference to the publisher
Meteor.publish('custom-publication', function() {
sender = this;
this.ready();
this.onStop(() => {
// Called when a Client stops its Subscription
});
});
// Later on…
// ==> Send a "new document" as a new signal message
sender.added(customCollectionName, 'someId', {
// "new document"
field: 'values2'
});
// CLIENT
const signalsCollectionName = 'collection-name'; // Must match what is used in Server
const Signals = new Mongo.Collection(signalsCollectionName);
Meteor.subscribe('custom-publication'); // As usual, must match what is used in Server
// Then use the Collection low-level API
// to listen to changes and act accordingly
// https://docs.meteor.com/api/collections.html#Mongo-Cursor-observe
const allSignalsCursor = Signals.find();
allSignalsCursor.observe({
added: (newDocument) => {
// Do your stuff with the received document.
}
});
Then how and when you use sender.added() is totally up to you.
Note: keep in mind that it will send data individually to a Client (each Client has their own Server session)
If you want to broadcast messages to several Clients simultaneously, then the easiest way is to use your MongoDB as the glue between your Server sessions. If you do not care about actual persistence, then simply re-use the same document over and over and listen to changes instead of additions in your Client Collection Cursor observer.
It's completly fine to use the database for such a task.
Maybe create a collection of "Streams" where you store the intended receiver and the message, the client subscribe to his stream and watches any changes on it.
You can then delete the stream from the database after the client is done with it.
This is a lot easier than reinventing the wheel and writing everything from scratch.

Meteor: Difference between using PubSub and Collection.find(options) on the client

I have seen developers subscribe to data published by the server and passing in options to modify the returned cursor. I have also seen developers who just directly call Collection.find(options) directly from the client files.
Is there a difference between the two approaches? If so, which is the recommended method?
Both are complementary.
In order to fully understand how Meteor works with data you have to forget how a traditional app works (i.e. request data and get them in a static form (like json)).
TL;DR Do not forget that Meteor is DB everywhere
Meteor data reactivity works like this:
1) Publication: You have some data in your MongoDB (server) that you want to be available on the client, said last 10 articles over a total of 500. So, you will set a publication on the server like this:
//SERVER
Meteor.publish('lastTen', function() {
return Articles.find({}, {limit: 10, sort: {date: -1}});
});
2) Subscription: To actually make the data available on the client you will subscribe to the publication like this on the client:
//CLIENT
Meteor.subscribe('lastTen');
3) Fetch data on the client: data you have subscribed to in 2) are not send statically to the client, they are instead replicated in a client side DB (currently MiniMongo). That is why to fetch the data and display them to the user you will have to query against this DB on the client side:
//CLIENT
Articles.find({}, {sort: {date: 1}});
In this case there is no need to limit your query as you only have 10 records on the client. Also, you can sort in another order as it is a DB and not static data.
Be careful, by default the autopublish package is installed to allow fast prototyping. It will publish all the data on the client, so you don't need any publish/subscribe mechanism and it may be confusing.

Meteor submiting form: Uncaught TypeError: Cannot read property'vk' of undefined

i'm using accounts-vk for providing login through vkontakte.ru
All works perfectly before i'm deleted meteor-autopublish package.
My connectSubmit.js:
Template.connectSubmit.events({
'submit form': function(e) {
e.preventDefault();
var query = {
vk_id: Meteor.user().services.vk.id,
user_id: Meteor.userId(),
photo: Meteor.user().services.vk.photo
};
query._id = Connects.insert(query);
Router.go('index');
}
});
Error is:
Uncaught TypeError: Cannot read property 'vk' of undefined
And second problem with my template:
This worked good before i'm delete autopublish:
{{currentUser.services.vk.first_name}}
But now not working.
I'm think problem with Meteor.publish function, but i'm have no idea how to resolve it.
When autopublish is on, it will publish all of your user's fields. When you remove autopublish only a few fields are published (username, _id, emails, profile).
If you publish the services field, you are exposing things like login tokens and hashed passwords to the client. Obviously, you never want to do that in production (which is why autopublish should always be removed). For more on this, see the "published secrets" section of my common mistakes article.
So the short answer to your question is that the client shouldn't be doing any of these things in the first place.
The long answer is that if you have logic which requires these data, you'll need to do one of two things (which may warrant separate questions):
if the services info is only needed for data mutation
Use a server-side method in this case. In your submit event you would call a method which does a findOne on the current user and updates the Connects collection. Here it's safe to read the services data because the code is running on the server.
if the services info is needed on the client (e.g. a photo)
You'll need to copy the data from services into a safe field like profile. You could do this either when the user creates her account or whenever she logs in.

What does it mean when a Collection insert results in a 404?

I have a very simple test app in meteor, and I've created a collection like so:
var people = new Meteor.Collection("people");
When I try to do a simple insert, like this:
people.insert({name: "Benson"});
I get a 404 error with the text "Method not found". I admit there's a good chance I've fat-fingered something here, but I'd love to know both what's wrong, and why the error is so opaque (i.e. where it's coming from).
This error almost certainly means you've only defined people on the client, but not on the server. The new Meteor.Collection('people') declaration has to also run on the server, or else the server doesn't know how to run your insert command.
Be sure you're calling new Meteor.Collection on both the client and the server. Are you calling it inside if (Meteor.is_client), or in a file under the client subdirectory?
Some more details: On the server, new Meteor.Collection defines three remote methods (Meteor.methods) that insert, update, and remove documents in the named MongoDB collection. On the client, the same command creates an in-memory minimongo collection that lives inside the browser, and defines three stubs that simulate the methods by applying the same change to the minimongo collection. By only declaring the collection on the client, your client code runs the local insert just fine, but when it asks the server to perform the real insert, the server has no idea what method you've asked it to execute.
If you want to use Collection only on Client side and you don't need to save that data to server you can declare your collection in "client" folder or in .isClient() function by passing null to the constructor like this:
if(Meteor.isClient()){
// Some other code
...
onlyClientCollection = new Meteor.Collection(null);
// Some other code
...
}

How do you secure the client side MongoDB API?

I don't want just all of my users being able to insert/destroy data.
While there is no documented way to do this yet, here's some code that should do what you want:
Foo = new Meteor.Collection("foo");
...
if (Meteor.is_server) {
Meteor.startup(function () {
Meteor.default_server.method_handlers['/foo/insert'] = function () {};
Meteor.default_server.method_handlers['/foo/update'] = function () {};
Meteor.default_server.method_handlers['/foo/remove'] = function () {};
});
}
This will disable the default insert/update/remove methods. Clients can try to insert into the database, but the server will do nothing, and the client will notice and remove the locally created item when the server responds.
insert/update/remove will still work on the server. You'll need to make methods with Meteor.methods that run on the server to accomplish any database writes.
All of this will change when the authentication branch lands. Once that happens, you'll be able to provide validators to inspect and authorize database writes on the server. Here's a little more detail: http://news.ycombinator.com/item?id=3825063
[UPDATE] There is now an official and documented Auth Package which provides different solutions to secure a collection.
On a CRUD level :
[Server] collection.allow(options) and collection.deny(options). Restricts default write methods on this collection. Once either of these are called on a collection, all write methods on that collection are restricted regardless of the insecure package.
And there is also insecureto remove full write access from the client.
source : Getting Started with Auth (thanks to #dan-dascalescu)
[OLD ANSWER]
Apparently there are working on Auth Package(?) that should avoid any users taking full control on the db as it is now. There is also someone suggesting that there is an existing solution (workaround) by defining your own mutations (methods) and make them failed if they attempts to perform an unauthorized action. I didn't get it much better but I think this will often be necessary since I doubt the Auth Package will let you implement the usual auth logic on a row level but probably only on the CRUD methods. Will have to see what the devs have to say.
[EDIT]
Found something that seems to confirm my thoughts :
Currently the client is given full write access to the collection. They can execute arbitrary Mongo update commands. Once we build authentication, you will be able to limit the client's direct access to insert, update, and remove. We are also considering validators and other ORM-like functionality.
Sources of this answer :
Accessing to DB at client side as in server side with meteor
https://stackoverflow.com/questions/10100813/data-validation-and-security-in-meteor/10101516#10101516
A more succinct way:
_.each(['collection1', 'collection2'], function(collection){
_.each(['insert','update', 'remove'], function(method){
Meteor.default_server.method_handlers['/' + collection + '/' + method] = function(){}
});
});
or to make it more idiomatic:
extend meteor:
_.extend(Meteor.Collection.prototype, {
remove_client_access: function(methods){
var self = this;
if(!methods) methods = ['insert','update','remove'];
if(typeof methods === 'String') methods = [methods];
_.each(methods, function(method){
Meteor.default_server.method_handlers[self._prefix + method] = function(){}
});
}
});
Calls are simpler:
List.remove_client_access() // restrict all
List.remove_client_access('remove') //restrict one
List.remove_client_access(['remove','update']) //restrict more than one
I am new to Meteor, but what I have come across so far are these two points
You can limit what a client can access in the database by adding parameters to the find command in the server-side publish command. Then when the client calls Collection.find({}), the results that are returned correspond to what on the server side would be, for example, Collection.find({user: this.userId}) (see also Publish certain information for Meteor.users and more information for Meteor.user and http://docs.meteor.com/#meteor_publish)
One thing that is built in (I have meteor 0.5.9) is that the client can only update items by id, not using selectors. An error is logged to console on the client if there is an attempt that doesn't comply. 403: "Not permitted. Untrusted code may only update documents by ID." (see Understanding "Not permitted. Untrusted code may only update documents by ID." Meteor error).
In view of number 2, you need to use Meteor.methods on the server side to make remote procedure calls available to the client with Meteor.call.

Resources