How do I call a client side function from the server using meteor? - meteor

Ok so I'm currently doing this and it works, but it seems a bit hacky. Is there a better way to call a function on the client side after a certain amount of time on the server side passes? On the client:
Meteor.subscribe('notifications');
Notifications.find().observe({ //Call whatever function.
added: function(item){
console.log(item);
alert(item.text)
}
});
And this on the server.
Meteor.publish('notifications', function(){
return Notifications.find({createdBy:this.userId});
});
//These run after a certain interval of time passes:
Notifications.insert({text: text, createdBy:createdBy});
Notifications.remove({text: text, createdBy:createdBy});

Make a collection on the server side for sending notifications to the client

Related

Can I delay PUT upload in Express until my server finishes an operation?

I have a server that receives files via PUT and in turn stores them in S3.
After a client PUTs files it will usually request them right in return for rendering. Unfortunately my server takes a bit of time to actually process the upload and store them in S3.
Here is the code I use in Express:
req.pipe(writeStream);
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'PUT, OPTIONS');
res.header('Access-Control-Allow-Headers', 'Content-Type');
if ('OPTIONS' === req.method) {
res.sendStatus(200);
res.end('OK!');
return;
}
if ('PUT' === req.method) {
writeStream.on('error', () => {
res.sendStatus(500);
});
writeStream.on('close', async () => {
// This might take 30s
await this.storage.store(fn, args.blobID);
// This does not seem to have any effect
// I would like to prevent the client from 'finishing' the
// PUT request before I say it is done.
res.end('OK!')
});
res.sendStatus(200);
}
Ideally I could 'stall' my client's PUT request for until storage.store() completed, making sure the file is actually available.
Is there any way how I could do this?
Also, I am not sure about the sendStatus() / end() calls, feel free to comment if I mixed something up.
P.S. I know, in an ideal world I would just hand out signed URLs and let the client upload to S3 directly, but I need AES256 encryption ...
I'm not familiar with S3 uploading, but I guess that there will be a callback(such as sending back a response(success or fail... etc) to you). (If not, please tell me)
S3.upload(data,function(response){ // something like this
// then, you can callback
// ...
// res.end() or res.sendStatus(...)
})
As I remember it, Express don't send timeout automatically, so you just put response in the S3's response function, so that your request should not end until your storing end.
From Must res.end() be called in express with node.js?
You don't have to call res.end() if you call res.send(). res.send() calls res.end() for you.
and res.sendStatus() equals to res.status(...).send(message or code) (via http://expressjs.com/en/api.html)
So, res.sendStatus() will call end().

DDP call interception

I'm thinking of scenarios where I might want to perform cross-cutting/AOP or other functions at the server for my Meteor Js project when a Meteor client (or DDP client) invokes a server-side method over a DDP connection.
This link here gives a really nice example of how to perform AOP on objects, but I wanted to know if there was a way to listen for inbound client requests over the DDP connection much like express-interceptor or action filters for asp.net web api but, of course, for websocket/ddp rpc implementations.
The Meteor Js Api describes only one event "onConnection" at the server. And this SO response mentions a connection._send on the client to perform certain AOP functions...but didn't find a whole lot of official documentation beyond that.
I basically want to know if there is a way to listen at the server for all DDP method calls from all client sessions to the server as described in the DDP spec here
Thanks.
We'll there are a lot of undocumented things in Meteor you'll find. I don't really see it as a problem. Here are some ways to intercept WebSocket traffic in Meteor:
Server
Here's some stuff you can do from the server:
Server -> Client
It's a bit tedious to intercept messages from the server to the client, but this works. You'd probably want to write some code to pin logs to clients.
(function () {
var timeout = 3000
var streamServer = Meteor.server.stream_server
var standardConnect = streamServer.server._events.connection
streamServer.server._events.connection = function (socket) {
var write = socket.write
socket.write = function () {
console.log(arguments)
write.apply(this, args)
}
standardConnect.apply(this, arguments)
}
})()
Client -> Server
To intercept calls from the client on the server you can do this:
Meteor.server.stream_server.server.addListener('connection', function (socket) {
var old = socket._events.data
socket._events.data = function () {
console.log(arguments)
old.apply(this, arguments)
}
})
The above sipped can't be used with the first one. It's not hard to fix dough. If you use this snipped, Meteor.server.stream_server.server._events.connection will simply be an array of functions instead of a function.
Client
Server - > Client
To Listen to calls from server to the client on the client you can do this:
Meteor.connection._stream.on('message', console.log.bind(console))
You can also intercept them using something like this
(function () {
var cb = Meteor.connection._stream.eventCallbacks.message[0]
Meteor.connection._stream.eventCallbacks.message[0] = function () {
console.log(arguments)
cb.apply(this, arguments)
}
})()
Not entirely sure how solid that one is. But it works, so what the heck.
To test it out you can simply do
Meteor.subscribe('test')
Client -> Server
As you pointed out, you can also do similar things with outgoing messages from the client.
Meteor.connection._send = function () {
console.log(arguments)
this.__proto__._send.apply(this, arguments)
}

Meteor observeChanges removed callback won't execute server methods

I am observing changes on the Results collection on the client and calling methods on the server for the added and removed callbacks. (The following is only on the client and 'foo' is on the server.)
Results.find().observeChanges({
added: function (id, doc) {
console.log('added on client')
Meteor.call('foo')
},
removed: function (id) {
console.log('removed on client')
Meteor.call('foo')
}
})
Here is the server code.
Meteor.methods({
foo: function() {
console.log('server code run')
}
})
If I insert a document on the client I get 'added on client' on the client and 'server code run' on the server. If I remove a document on the client, I get 'removed on the client' on the client, but nothing on the server at all.
Does anyone know what is going on?
A few suggestions:
Does the code run on the server (e.g if you put it into a Tracker.autorun block)?
Are there any errors on the server console?
Is there any error on the browser console?
If other code within the removed callback gets executed, the server method call will be executed too. There are no restrictions for these callbacks. I don't think your problem is with the code you've pasted. Maybe add the server method code as well.

Meteor http calls limitations

Currently, I use the built-in meteor http method (see http://docs.meteor.com/#http) for issuing http calls, on both my client and my server.
However, I'm experiencing two issues:
is it possible to cancel a request?
is it possible to have multiple query parameters which share the same key?
Are these just Meteor limitations, or are there ways to get both to work using Meteor?
I know I could you jquery on the clientside, and there must be a server-side solution which supports both as wel, but I'd prefer sticking with meteor code here.
"is it possible to cancel a request?"
HTTP.call() does not appear to return an object on which we could call something like a stop() method. Perhaps a solution would be to prevent execution of your callback based on a Session variable?
HTTP.call("GET", url, function(error, result) {
if (!Session.get("stopHTTP")) {
// Callback code here
}
});
Then when you reach a point where you want to cancel the request, do this:
Session.set("stopHTTP", true);
On the server, instead of Session perhaps you could use an environment variable?
Note that the HTTP.call() options object does accept a timeout key, so if you're just worried about the request never timing out, you can set this to whatever millisecond integer you want.
"is it possible to have multiple query parameters which share the same key?"
Yes, this appears to be possible. Here's a simple test I used:
Meteor code:
HTTP.call("GET", "http://localhost:1337", {
query: "id=foo&id=bar"
}, function(error, result) {
// ...
});
Separate Node.js server: (just the basic example on the Node.js homepage, with a console.log line to output the request URL with query string)
var http = require('http');
http.createServer(function(req, res) {
console.log(req.url); // Here I log the request URL, with the query string
res.writeHead(200, {
'Content-Type': 'text/plain'
});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
When the Meteor server is run, the Node.js server logged:
/?id=foo&id=bar
Of course, this is only for GET URL query parameters. If you need to do this for POST params, perhaps you could store the separate values as a serialized array string with EJSON.stringify?

Meteor - Using collection on client startup

Why this code shows "0"? Shouldn't it return "1"?
Messages = new Meteor.Collection("messages");
if (Meteor.is_client) {
Meteor.startup(function () {
alert(Messages.find().count());
});
}
if (Meteor.is_server) {
Meteor.startup(function () {
Messages.insert({text: "server says hello"});
});
}
If I do the "Messages.find().count()" later, it returns 1.
By default, when a Meteor client starts up, it connects to the server and subscribes to documents in any Meteor.Collection you defined. That takes some time to complete, since there's always some amount of delay in establishing the server connection and receiving documents.
Meteor.startup() on the client is a lot like $() in jQuery -- it runs its argument once the client DOM is ready. It does not wait for your client's collections to receive all their documents from the server. So the way you wrote the code, the call to find() will always run too early and return 0.
If you want to wait to run code until after a collection is first downloaded from the server, you need to use Meteor.subscribe() to explicitly subscribe to a collection. subscribe() takes a callback that will run when the initial set of documents are on the client.
See:
meteor-publish
and meteor-subscribe
Just to follow up with a code example of how to know when a collection is ready to use on the client.
As #debergalis described, you should use the Meteor.subscribe approach - it accepts a couple of callbacks, notably onReady
For example:
if(Meteor.isClient){
Meteor.subscribe("myCollection", {
onReady: function(){
// do stuff with my collection
}
});
}

Resources