Meteor.call and latency compensation - meteor

I'm trying to understand why am I getting the error when calling a meteor server method. It works on the server side but it's throwing errors in the browser.
This is my server code in /server/methods.js file:
Meteor.methods({
getTicketSettings: function(){
var getTicketConfig = function(callback){
Assets.getText('ticketCustomizing.json', function(error, res){
if (error)
throw new Meteor.Error({error:'ticket-getCustomizing', reason:'No se pudo recuperar la configuraciĆ³n.'});
else callback && callback(null, JSON.parse(res));
});
}
var syncAssetRetrieve = Meteor.wrapAsync(getTicketConfig);
var result = syncAssetRetrieve();
return result;
},
});
And this is in my client/server code in /lib/initialization.js file:
App.config.tickets.tipos = new Mongo.Collection('tipos');
Meteor.startup(function(){
moment.locale('es');
var ticketSettingsObj = Meteor.call('getTicketSettings');
console.log(ticketSettingsObj);
_.map(ticketSettingsObj.tipos, function(tipo){
App.config.tickets.tipos.insert(tipo);
});
});
When I run my application I have the JSON object logged in the console but the browser is showing this error: Uncaught TypeError: Cannot read property 'tipos' of undefined in my /lib/initialization.js here:
_.map(ticketSettingsObj.tipos, function(tipo){
App.config.tickets.tipos.insert(tipo);
});
Obviously I misunderstood something but still wondering...

You need to pass a callback to the Meteor.call. The server can run it synchronously, blocking until it gets a return, but the client cannot so ticketSettingsObj will always be undefined.
See Meteor docs
Without error handling (and untested):
Meteor.call('getTicketSettings', function(error, result){
console.log(result);
_.map(result.tipos, function(tipo){
App.config.tickets.tipos.insert(tipo);
});
});

Do a console.log(App.config.tickets) and see if it returns a valid object. If it doesn't then you have defined the object App.config.tickets only on server side. If this is intentional and you only want this to be accessible on server side then then add a if(Meteor.isServer) or move the definition it to a file inside /server directory.

Related

Meteor.call in client doesn't wait when the called method has another call inside

I am following what I've read around being the standard way to use Meteor.call but it's behaving strangely in this scenario:
Client:
Template.sometemplate.events({
'submit .somebutton'(event){
...
Meteor.call('stuff.someMethod', param1, function (err, res){
console.log(err);
console.log(res);
};
}
})
Server /api/stuff.js:
Meteor.methods({
'stuff.someMethod'(param1){
...
Meteor.call('otherstuff.someOtherMethod', param1, function(err, res){
if(err){ throw new Meteor.Error(400,'wrong things');}
if(res) { return 'ok';}
}
);
}
})
Server /api/otherstuff.js:
Meteor.methods({
'otherstuff.someOtherMethod'(param1){
...
return OtherStuff.findOne(query);
}
})
On the client side I click and immediately see the console.log for both err and res as undefined. Whereas in other parts of the application when the client calls a server method, which is not calling another method, the client waits for the answer before executing the asynch callback.
There must be something wrong in how I use the Meteor.call inside a server method calling another server method. The scenario is that for instance I want to insert a document and while doing so I want to check some values in order to link it to other documents from other collections.
Thank you very much,
T.
Sync call on the server
Using Meteor.call on the server does not require a callback, unless you really want to work async on the server side.
If you do not pass a callback on the server, the method invocation
will block until the method is complete. It will eventually return the
return value of the method, or it will throw an exception if the
method threw an exception. (Possibly mapped to 500 Server Error if the
exception happened remotely and it was not a Meteor.Error exception.)
Instead of passing a callback you would either return the result
return Meteor.call(...)
or assign it to a variable that is used for further processing.
const retVal = Meteor.call(...)
Better way: Externalize shared code
If two meteor methods rely on the same code (e.g. one is calling the other) you should extract this code into a shared function. This makes testing and tracing errors also easier.
server/api/common.js
export const sharedFunction = function(param1) {
// ... do somethin
return OtherStuff.findOne(query);
}
server/api/stuff.js:
import { sharedFunction } from './common.js';
Meteor.methods({
'stuff.someMethod'(param1){
// ...
const temp = sharedFunction(param1);
// ...
return result; // or temp if this should be returned to client
}
})
server/api/otherstuff.js
import { sharedFunction } from './common.js';
Meteor.methods({
'otherstuff.someOtherMethod'(param1){
return sharedFunction(param1);
}
});
Using the sharedFunction follows the concepts of DRY and Single Point of Failure.

Error invoking Method 'pushFile': Internal server error [500] on uploading with meteor

I am trying to upload a file to the server but I keep getting Error invoking Method 'pushFile': Internal server error [500] in the console. I am not exactly sure what is going on here. I am pretty much brand new to meteor and any help would be greatly appreciated.
Template.hello.events({
'change .fileInput': function(event, template){
event.preventDefault();
// var theName = event.target.theName.value;
console.log(theName);
FS.Utility.eachFile(event, function(file){
var fileObj = new FS.File(file);
fileObj.itemtext = theName;
Meteor.call("pushFile", fileObj);
});
}
});
}
if(Meteor.isServer){
Meteor.methods({
'pushFile': function(fileObj){
fileObj.userId = this.userId;
Uploads.insert(fileObj, function(err){
console.log(err);
});
}
});
}
the rest of the error is below:
I20151112-17:29:03.764(-5)? Exception while invoking method 'pushFile' Error: DataMan constructor received data that it doesn't support
I20151112-17:29:03.770(-5)? at EventEmitter.FS.Collection.insert (packages/cfs_collection/packages/cfs_collection.js:269:1)
I20151112-17:29:03.770(-5)? at [object Object].Meteor.methods.pushFile (uploadexample.js:39:21)
I20151112-17:29:03.769(-5)? at new DataMan (packages/cfs_data-man/packages/cfs_data-man.js:75:1)
I20151112-17:29:03.770(-5)? at setData (packages/cfs_file/packages/cfs_file.js:107:1)
I20151112-17:29:03.770(-5)? at EventEmitter.fsFileAttachData [as attachData] (packages/cfs_file/packages/cfs_file.js:102:1)
I20151112-17:29:03.771(-5)? at maybeAuditArgumentChecks (livedata_server.js:1698:12)
I20151112-17:29:03.771(-5)? at livedata_server.js:708:19
I20151112-17:29:03.771(-5)? at [object Object]._.extend.withValue (packages/meteor/dynamics_nodejs.js:56:1)
I20151112-17:29:03.772(-5)? at livedata_server.js:706:40
I20151112-17:29:03.772(-5)? at [object Object]._.extend.withValue (packages/meteor/dynamics_nodejs.js:56:1)
Check the answer here... Meteor File Upload Not Working
When you need to insert a file that's located on a client, always call myFSCollection.insert on the client. While you could define your own method, pass it the fsFile, and call myFSCollection.insert on the server, the difficulty is with getting the data from the client to the server. When you pass the fsFile to your method, only the file info is sent and not the data. By contrast, when you do the insert directly on the client, it automatically chunks the file's data after insert, and then queues it to be sent chunk by chunk to the server. And then there is the matter of recombining all those chunks on the server and stuffing the data back into the fsFile. So doing client-side inserts actually saves you all of this complex work, and that's why we recommend it.
Then to secure the insert, since it is coming from the client side, setup your allow / deny rules to decide who can insert what where. In your server folder, add a file (usually /server/allow/Uploads.js) Something like this...
Uploads.allow({
insert: function (userId, doc) {
// the user must be logged in, and whatever other constraints you want
return (userId && otherCoolSecurityCheckFunction());
},
update: function (userId, doc, fields, modifier) {
// can only change your own documents
return doc.owner === userId;
},
remove: function (userId, doc) {
// can only remove your own documents
return doc.owner === userId;
},
fetch: ['owner']
});
See the allow docs for more information...
http://docs.meteor.com/#/full/allow

Error when calling http method from client in meteor

I am trying to consume a REST API in the meteor application. Inside the server.js file which is in the server folder, I have written this code:
Meteor.methods({
checkTwitter: function () {
this.unblock();
return Meteor.http.call("GET", "http://search.twitter.com/search.json?q=perkytweets");
}
});
Inside the client.js file, which is in the client folder, I have written down this code:
Meteor.call("checkTwitter", function(error, results) {
console.log(results.content); //results.data should be a JSON object
});
I get this error message in console:
"Exception while simulating the effect of invoking 'checkTwitter' Error: Can't make a blocking HTTP call from the client; callback required".
I have the callback function defined in client due to which I don't understand this error. What is it that I am doing wrong?
Meteor.http has been deprecated, please see the HTTP package.
I think that since there's a stub, "checkTwitter" will actually also run on the client. Once the server returns, its result will overwrite the result from teh client run.
In this case, since Meteor.http.call can't run on the client without a callback, you get the error.
Try changing:
Meteor.methods({
checkTwitter: function () {
this.unblock();
return Meteor.http.call("GET", "http://search.twitter.com/search.json?q=perkytweets");
}
});
With
Meteor.methods({
checkTwitter: function () {
if (Meteor.isServer) {
this.unblock();
return Meteor.http.call("GET", "http://search.twitter.com/search.json?q=perkytweets");
}
}
});

Meteor async call to Instagram API not returning on client side. Returns on server side

Server side:
Meteor.methods({
getFromInstagram: function(){
console.log("called!");
Future = Npm.require('fibers/future');
var myFuture = new Future();
var url = "https://api.instagram.com/v1/media/popular?access_token=[ACCESSTOKEN]";
Meteor.http.get(url, function(error, results){
if(error){
myFuture.throw(error);
} else {
myFuture.return(results);
}
});
console.log( myFuture.wait() );
return myFuture.wait();
}
});
Client side:
instagramContent = Meteor.call("getFromInstagram");
console.log(instagramContent);
The server side console log works and returns an object.
The client side console log in Chrome console returns undefined. What am I overlooking?
Client-side will always be async.
Try something like:
instagramContent = Meteor.call("getFromInstagram", function (error, result) {
console.log(result);
});
From the documentation (http://docs.meteor.com/#meteor_call):
On the client, if you do not pass a callback and you are not inside a stub, call will return undefined, and you will have no way to get the return value of the method. That is because the client doesn't have fibers, so there is not actually any way it can block on the remote execution of a method.

How to get current user in custom route?

As per this answer I created my own route so that I could handle file uploads. Here's what I've got:
var router = Connect.middleware.router(function(route) {
route.post('/upload', function(req, res) {
var filename = req.headers['x-filename'];
var path = Path.join('.uploads', filename);
var writeStream = FileSystem.createWriteStream(path);
writeStream.on('error', function(e) {
console.error(e);
res.writeHead(500);
res.end();
}).on('close', function() {
Fiber(function() {
console.log(Meteor.user());
}).run();
res.writeHead(200);
res.end();
});
req.pipe(writeStream);
});
});
app.use(router);
This works great for uploading files, but when I try to acess Meteor.user() it gives me:
app/server/main.js:24
}).run();
^
Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
at Object.Meteor.userId (app/packages/accounts-base/accounts_server.js:95:13)
at Object.Meteor.user (app/packages/accounts-base/accounts_server.js:100:25)
at app/server/main.js:23:36
Exited with code: 1
I can't see anything in the req object that might help me out.
Is there any way to get access to the user object?
For the time being, I'm getting the user ID client side and passing it along through the headers which I then use to look up server side:
route.post('/upload', function(req, res) {
Fiber(function() {
var userId = req.headers['x-userid'];
var user = Meteor.users.findOne({_id:userId});
if(user) {
...
} else {
res.writeHead(403,'User not logged in');
res.end();
}
}).run();
});
I don't like this because it's not at all secure. It would be easy to upload something under a different user's account.
Edit: Nevermind. The very act of calling Meteor.users.findOne({_id:userId}); somehow breaks the upload stream. Every file gets corrupt as soon as I put that in; they upload up to about 700 KB and then just stop and close the connection without error.
If it's still valid question.
The problem is that there is no way how to get Meteor.user() in this part of code.
But you can always reach Meteor.userId .. and it's not null if user is logged in .. so you can upload only for logged user. (if req.headers['x-userid'] == Meteor.userId)
The very act of calling Meteor.users.findOne({_id:userId}); somehow
breaks the upload stream.
Because it's reactive part.. so every time if Meteor.users collection is updated this part of code is executed again.
So if you can use only Meteor.userId (which is changed only if user is logged in/out) it should work fine.
I've run into this quite a few times, and it's frustrating. I don't think you can make Meteor.userId() calls from inside a fiber. I usually do a var userId = Meteor.userId(); before I call the fiber, and then reference that variable instead.

Resources