correct use of Meteor.userId() - meteor

This Meteor server code tries to use Meteor.userId() in public method "sendEmail", but sometimes I get the error
Error Meteor.userId can only be invoked in method calls. Use this.userId
lib = (function () {
return Object.freeze({
'sendEmail': function(msg){
let userId = Meteor.userId();
//do stuff for this user
},
'otherPublicMethod': function(){
//do other things then use sendEmail
lib.sendEmail(); // <---- Error Meteor.userId can only be invoked in method calls. Use this.userId
}
});
}());
// Now I call sendEmail from any where, or can I?
Meteor.methods({
'sendEmail': (msg) => {
lib.sendEmail(msg); // <---- NO error when this is called
},
});
How can it be fixed? thx

i'm going to gently suggest you replace your use of IIFE. instead, you can take advantage of ES16 modules to define your common functions.
as you've indicated, Meteor.userId() is available in method calls, but won't be available in standalone functions on the server. the pattern i use, when invoking such functions from a method call, is to pass in the userId (or actual user). e.g.
imports/api/email/server/utils/emailUtils.js:
const SendEmail = function(userId, msg) {
// do stuff
};
export {SendEmail};
imports/api/email/server/emailMethods.js:
import {SendEmail} from '/imports/api/email/server/utils/emailUtils';
Meteor.methods({
'sendEmail': (msg) => {
check(msg, String);
// other security checks, like user authorization for sending email
SendEmail(Meteor.userId(), msg);
},
});
now, you have a re-usable SendEmail function you can call from any method or publish. additionally, by following this pattern, you're one step closer to creating testable code. i.e. it's easier to test a function into which you're injecting a userId than it is to mock "this.userId" or "Meteor.userId()".

If lib.sendEmail is being called from any async method, then ensure that you bind Meteor environment
e.g. check code below which simulates the async behaviour
lib = (function () {
return Object.freeze({
'sendEmail': function (msg) {
let userId = Meteor.userId();
console.log(userId);
//do stuff for this user
},
'otherPublicMethod': function () {
//do other things then use sendEmail
lib.sendEmail(); // <---- Error Meteor.userId can only be invoked in method calls. Use this.userId
}
});
}());
// Now I call sendEmail from any where, or can I?
Meteor.methods({
'sendEmail': (msg) => {
//simulate async behaviour + bind environment
Meteor.setTimeout(Meteor.bindEnvironment(function () {
lib.sendEmail(msg); // <---- NO error when this is called
}));
//output :
// null - if user has not logged in else
// actual userId - if user is loggedin
//simulate async behaviour without binding environment
Meteor.setTimeout(function () {
lib.sendEmail(msg); // <---- error when this is called
});
//output :
// Exception in setTimeout callback: Error: Meteor.userId can only be invoked in method calls. Use this.userId in publish functions.
},
});

Related

Meteor method + check() + audit-argument-checks + async/await causes exception

Iā€™m using meteor with check() and audit-check-arguments package.
When I use a meteor method using async/await and pass a parameter, even though I use check() to validate the function parametrs, the audit package still throws an exception indicating that not all input parameters have been checked. If I remove the async/await implementation, the package does not crib. What am I missing?
Example:
Meteor.methods({
test: async function(param1){
check(param1, String);
...
await ....
}
});
Throws an exception:
=> Client modified -- refreshing
I20200513-10:43:27.978(5.5)? Exception while invoking method 'test' Error: Did not check() all arguments during call to 'test'
I20200513-10:43:27.979(5.5)? at ArgumentChecker.throwUnlessAllArgumentsHaveBeenChecked (packages/check/match.js:515:13)
Whereas this traditional meteor method does not throw any exceptions
Meteor.methods({
test: function(param1){
check(param1, String);
...
}
});
I know for sure that I am passing exactly one parameter.
It looks like audit-argument-checks only works for synchronous functions.
I don't have this issue because we use mdg:validated-method, which uses requires you to specify an argument validator for each method.
It shuts up the argument checker by wrapping the method function with this:
// Silence audit-argument-checks since arguments are always checked when using this package
check(args, Match.Any);
The simplest solution I can think of, is to separate the check from the async function. You could use a wrapper function to do this:
function checkAndRun(check, run) {
return function(...args) {
check.apply(this, args);
return run.apply(this, args);
}
}
Meteor.methods({
'example': checkAndRun(
function(exampleID){
check(exampleID, String);
},
async function(exampleID) {
const result = await doSomethingAsync(exampleID);
SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
return result.status;
}
}
});
or you could even do it inline with an async IIFE:
Meteor.methods({
example(exampleID) {
check(exampleID, String);
return (async () => {
const result = await doSomethingAsync(exampleID);
SomeDB.update({ _id: exampleID }, { $set: { someKey: result.value } });
return result.status;
})()
}
});
Which, come to think of it, is much simpler than the simplest solution I could think of at first šŸ™ƒ
You just want to separate the sync check from the async method body somehow
In case you're curious, let diving through the source to see where it's called. When the method is called (in ddp-server/livedata-server), we end up here, a sync method call for the first reference of audit-argument-checks:
https://github.com/meteor/meteor/blob/master/packages/ddp-server/livedata_server.js#L1767-L1770
Which takes us into check/Match for another sync call here: https://github.com/meteor/meteor/blob/71f67d9dbac34aba34ceef733b2b51c2bd44b665/packages/check/match.js#L114-L123
Which uses the strange Meteor.EnvironmentVariable construct, which under the hood has another sync call: https://github.com/meteor/meteor/blob/master/packages/meteor/dynamics_nodejs.js#L57

Meteor Async ValidatedMethod gets called with function parameters undefined

someMethod = new ValidatedMethod({
name: 'someMethodName',
validate: new SimpleSchema({
subId: {type: String, min:1},
planId: {type: String}
}).validator(),
async run(params){
try{
//params is undefined
}
}
});
Using async run(params) causes params to be undefined (seems like the context switches to Global context). Removing the async works fine (except that I cannot use await in the method body anymore obviously).
Why is this, and how can I still use await inside a ValidatedMethod?
Note1 : I call the method from the client like so -- and get the same result if I try to use a regular Meteor.methods({}) definition. I am calling the method using Meteor.apply from the client
ClientHelpers.callWithPromise = function(methodName, methodArgs){
//methodArgs must be an array
return new Promise(function(resolve, reject){
Meteor.apply(methodName, methodArgs, {wait:true}, function(error, result){
if (error){
reject(error);
}
console.log(result);
resolve(result);
});
});
}
Then, calling on client (am sure paramsObject is correct):
var myResult = await ClientHelpers.callWithPromise('someMethodName', [paramsObject]);
Note 2: I have also traced it through to the internals of Meteor.apply , where it is in fact sending the paramsObject over DDP, in debug session:
// Sends the DDP stringification of the given message object
_send(obj) {
this._stream.send(DDPCommon.stringifyDDP(obj));
}
Many thanks for any insight.

Meteor method create insert hook and bind userId on the server

I implemented a hook function, where I attach some createdAt and updatedAt fields to the doc that is inserted to a collection. I can attach this to any collection like this:
export const insertHook = function (doc) {
try {
const user = Meteor.user();
doc.createdBy = user && user._id ? user._id : null;
doc.createdAt = new Date().getTime();
} catch (e) {
console.err(e);
}
};
Attaching the hook to the collection is basically passing it via a third option in the constructor:
class HookedCollection extends Mongo.Collection {
constructor(name, options, hooks={}) {
super(name, options);
this.insertHook = hooks.insertHook;
}
insert(doc, callback) {
if (this.insertHook && Meteor.isServer)
this.insertHook.call(this, doc);
}
}
export const MyDocs = new HookedCollection("mydocs", {}, {insertHook});
In a Meteor method I just do a normal insert:
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc);
}
});
Which creates basically the following error:
Error: Meteor.userId can only be invoked in method calls or publications.
I tried several ways of bind but always ended up in this error. Is there really no way at all to bind the userId to the function?
According to Meteor docs Meteor.userId() is available anywhere but publish functions (Server side Publish function).
You aren't using Meteor.userId() directly in the method but in a callback (see discussion in this github issue). You can pass the userId information to your callback function as a parameter from the method, for example:
// Using Meteor.userId()
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, Meteor.userId());
}
});
// Or using this.userId
Meteor.methods({
insertDoc:function(doc) {
//check doc...
return MyDocs.insert(doc, this.userId());
}
});
As a general rule use Meteor.userId() in the client (that queries the database) and this.userId in the server. More information in this other question Meteor - Why should I use this.userId over Meteor.userId() whenever possible? and in Meteor forums

Test asynchronous functionality in Jasmine 2.0.0 with done()

I am trying to implement a jasmine test on a simple promise implementation (asynchronous code) with the done() function and my test fails although the code being tested works perfectly fine.
Can anyone please help me to figure out what is missing in my test?
var Test = (function () {
function Test(fn) {
this.tool = null;
fn(this.resolve.bind(this));
}
Test.prototype.then = function (cb) {
this.callback = cb;
};
Test.prototype.resolve = function (value) {
var me = this;
setTimeout(function () {
me.callback(value);
}, 5000);
};
return Test;
})();
describe("setTimeout", function () {
var test, newValue = false,
originalTimeout;
beforeEach(function (done) {
originalTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
test = new Test(function (cb) {
setTimeout(function () {
cb();
}, 5000);
});
test.then(function () {
newValue = true;
console.log(1, newValue);
done();
});
});
it("Should be true", function (done) {
expect(1).toBe(1);
expect(newValue).toBeTruthy();
});
afterEach(function () {
jasmine.DEFAULT_TIMEOUT_INTERVAL = originalTimeout;
});
});
the same test in jsfiddle: http://jsfiddle.net/ravitb/zsachqpg/
This code is testing a simple promise like object, so will call the Test object a promise for convenience.
There are two different async events after the promise creation:
1. The call to the .then() method
2. The resolving of the promise by calling the cb() function in the beforeEach() function.
In the real world these two can be called in any order and at any time.
For the test, the .then() call must be moved to the it() section's callback and all spec methods (e.g expect()) need to be called in it's callback or they'll run before it's resolved. The beforeEach() is part of the test setup while the it() function is the spec, the test itself.
The done() method needs to be called twice,
When the beforeEach() async action is finished (i.e after the cb() is called), that will start running the spec. So it should look something like this:
beforeEach(function (done) {
test = new Test(function (cb) {
setTimeout(function () {
console.log("in beforeEach() | setTimeout()");
cb(resolvedValue);
done()
}, 500);
});
});
When the spec's (it() section's) async action is finished inside the .then() method after all calls to jasmine test methods, this will tell Jasmine the spec finished running (and so the time-out won't be reached). So:
it("Should be " + resolvedValue, function (done) {
test.then(function (value) {
console.log("in then()");
expect(value).toBe(resolvedValue);
done();
});
});
Also, as you can see instead of testing that a variable's value has changed I'm testing that the value passed to the .then() method is the same as the one passed to the promise resolve cb() function as that is the right behaviour you are expecting.
Here's an updated version of your fiddle.
You can check in the browser's console to see that all callbacks are being called
Note: Changing Jasmine's DEFAULT_TIMEOUT_INTERVAL just makes it more convoluted for no reason, so I removed it and some extraneous code.

Meteor callback to sys.exec inside a Meteor.call callback

I have an event triggering a Metor.call():
Meteor.call("runCode", myCode, function(err, response) {
Session.set('code', response);
console.log(response);
});
But my runCode function inside the server's Metheor.methods has inside it a callback too and I can't find a way to make it return something to response in the above code.
runCode: function(myCode) {
var command = 'pwd';
child = exec(command, function(error, stdout, stderr) {
console.log(stdout.toString());
console.log(stderr.toString());
// I Want to return stdout.toString()
// returning here causes undefined because runCode doesn't actually return
});
// I can't really return here because I don't have yet the valuer of stdout.toString();
}
I'd like a way to have the exec callback return something as runCode without setInterval which would work, but as a hacky way in my opinion.
You should use Future from fibers.
See docs here : https://npmjs.org/package/fibers
Essentially, what you want to do is wait until some asynchronous code is run, then return the result of it in a procedural fashion, this is exactly what Future does.
You will find out more here : https://www.eventedmind.com/feed/Ww3rQrHJo8FLgK7FF
Finally, you might want to use the Async utilities provided by this package : https://github.com/arunoda/meteor-npm, it will make your like easier.
// load future from fibers
var Future=Npm.require("fibers/future");
// load exec
var exec=Npm.require("child_process").exec;
Meteor.methods({
runCode:function(myCode){
// this method call won't return immediately, it will wait for the
// asynchronous code to finish, so we call unblock to allow this client
// to queue other method calls (see Meteor docs)
this.unblock();
var future=new Future();
var command=myCode;
exec(command,function(error,stdout,stderr){
if(error){
console.log(error);
throw new Meteor.Error(500,command+" failed");
}
future.return(stdout.toString());
});
return future.wait();
}
});

Resources