I'm just wondering on how to throw multiple Meteor.Errors for my validation fields like
throw new Meteor.Error('403','Invalid Input','username Id');
throw new Meteor.Error('403','Too Short','password Id');
and throw them at the same time to client.
I'd take an approach like this:
var errors = [];
if (/* password is too short */) {
errors.push("password is too short");
}
if (/* username is invalid */) {
errors.push("username is invalid");
}
// ...
if (errors.length > 0) {
throw new Meteor.Error(403, errors.join("; "));
}
Achieved what I want by making two empty arrays,pushing values if an error is detected, wrapping them up to throw to the client and iterating those values in the client's ERROR callback.
//SERVER
var reason=[],ids=[];
if(error){
reason.push('error details');
ids.push('id of the element');
}
if(reason.length!==0){
throw new Meteor.Error('403',reason,ids);
}
//CLIENT
Accounts.createUser({foo:bar,boo:baz}, function(error){
if(error){
_.each(error.details,function(index,element){
and there your client error code goes
});
}
});
Related
I have a method and it throws an error so I can catch it in my event and display it to the user, like this:
Meteor.methods({
addPlayer: function(nickname) {
if (nickname == "") {
throw new Meteor.Error('empty-nickname', 'You must choose a nickname');
} else {
Player.insert({
nickname: nickname,
});
}
},
})
and in my event
'submit form': function (e) {
e.preventDefault();
var nickname = $('input').val();
Meteor.call('addPlayer', nickname, function(error, result) {
if (error) {
console.log(typeof error);
console.log(error);
}
});
}
However, meteor still throws an Exception while simulating the effect of invoking 'addPlayer', and the error variable is not an error object, but a string with the same message as the console log, so I get two errors in my console instead of an error object.
Wrapping method.call in a try/catch does not work.
What am I missing here?
-- Edit
Here is an print screen of the result:
Image link for full resolution: http://i.stack.imgur.com/zABar.png
Throw the error only on the server. Wrap it inside if(!this.isSimulation) {}
Meteor.methods({
addPlayer: function(nickname) {
if (nickname == "") {
if(!this.isSimulation) {
throw new Meteor.Error('empty-nickname', 'You must choose a nickname');
}
} else {
Player.insert({
nickname: nickname,
});
}
},
})
I'm attempting to use Meteorjs Accounts on the server to create a new user and then send them an email to set their initial password. The idea is that an admin user can add new users.
I can successfully add the new user (I can see the new user ID in the server console if I log it), but that ID is never returned to the client. This is my server-side
Meteor.methods({
createNewUser: function(email){
return Accounts.createUser({email: email});
}
});
And the relevant client-side JS:
if (isNotEmpty(email) && isEmail(email)) {
Meteor.call("createNewUser", email, function(ret){
if (typeof ret.message !== 'undefined') {
if (ret.message === 'Email already exists. [403]') {
alert("exists");
} else {
alert("not created");
}
} else {
Accounts.sendEnrollmentEmail(ret, function(err){
if (err){
alert("email didn't get sent");
} else {
alert('success');
}
});
}
});
}
I get this in my browser console:
Exception in delivering result of invoking 'createNewUser': TypeError: Cannot read property 'message' of undefined
It's probably worth noting that I also get the "exists" alert if I try to submit the same email address twice in a row, so the error is getting returned to the client just fine.
The first argument in callback is always error object.
error equals null if everything is fine.
Meteor.call('createNewUser', email, function( error, result ){
if( error ){
console.error("ERROR -> ", error )
}else{
console.log("User was created!")
}
})
but that ID is never returned to the client.
Thats because you don't have any console.log on the client. also the meteor call look incorrect.
if (isNotEmpty(email) && isEmail(email)) {
Meteor.call("createNewUser", email, function(err,result){
if (typeof ret.message !== 'undefined') {
if (ret.message === 'Email already exists. [403]') {
alert("exists");
} else {
console.log(result) //here for example you should get the id
}
} else {
Accounts.sendEnrollmentEmail(ret, function(err){
if (err){
alert("email didn't get sent");
} else {
alert('success');
}
});
}
});
}
My meteor code goes a couple Meteor.call methods deep at some points. If I have an error in the 2nd layer and I want to throw that meteor error back to the client side how can I do that?
Currently I have something like this, but I'm getting very confusing outputs and I don't think I fully understand what is happening when I'm calling throw new Meteor.Error(500, e.category_code, e.description);
In client.js
Meteor.call('firstCall', data, function (error, result) {
if(result) {
doSomething();
}
else{
console.log(error);//just shows 500
}
});
In server.js
var Future = Meteor.npmRequire("fibers/future");
function extractFromPromise(promise) {
var fut = new Future();
promise.then(function (result) {
fut.return(result);
}, function (error) {
console.log(error);
fut.throw(error);
});
return fut.wait();
}
firstCall: function (data){
try{
Meteor.call('secondCall', data, 'http://testhref.com/test', 'http://testhref2.com/test' function (error, result) {
return result;
});
}
catch(e){
throw new Meteor.Error(500, e.category_code, e.description);
}
}
secondCall: function (data, paymentHref, otherHref){
try{
var associate = extractFromPromise(balanced.get(paymentHref).associate_to_customer(otherHref).debit({
"amount": data.paymentInformation[0].total_amount * 100,
"appears_on_statement_as": "Trash Mountain"}));
}
catch(e){
Collection.update(data.id, {
$set: {
'failed.category_code': e.category_code,
'failed.description': e.description
}
});
throw new Meteor.Error(500, e.category_code, e.description);
}
}
In your case, the catch in firstCall is not going to have anything defined for e.category_code and e.description when secondCall throws. This is because in secondCall you are passing these two as arguments to Meteor.Error, which takes as its arguments error, reason, and details:
https://github.com/meteor/meteor/blob/devel/packages/meteor/errors.js
In order to pass these through, you will need to amend firstCall to use these properties:
firstCall: function (data){
try{
Meteor.call('secondCall', data, 'http://testhref.com/test', 'http://testhref2.com/test');
}
catch(e){
throw new Meteor.Error(500, e.reason, e.details);
}
}
I'm not even sure you need to split it up into two calls for modularity, as you can just use normal Javascript functions. But we can discuss that elsewhere.
I few things to mention here:
Async function don't throw exceptions (except you make them kind of sync using Meteor._wrapAsync as I will explain later), they return the error on another way (as the first argument in NodeJS callback-style). This applies both for Meteor.call and to your doSomeAsyncThing.
I can't see the benefit of using Meteor.call on the server. Meteor.call is meant to call server methods from the client. In this case you could just call YourObj.secondCall from inside of firstCall.
Returning something from inside of a callback (as you are doing inside firstCall) doesn't have any effect. You want your async code to work as sync code, so I suggest using Meteor._wrapAsync which is very well explained here.
So, I would implement server side a bit different:
firstCall: function (data){
try{
return this.secondCall(data);
}
catch(e){
throw new Meteor.Error(500, e.category_code, e.description);
}
secondCall: function (data){
try{
return Meteor._wrapAsync(doSomeAsyncThing)(data);
}
catch(e){
Collection.update(data.id, {
$set: {
'failed.category_code': e.category_code,
'failed.description': e.description
}
});
throw new Meteor.Error(500, e.category_code, e.description);
}
Hope this helps!
I'm building a messenger application, and before a conversation is created I want to verify if a user exists. If it does, then it will create the conversation. If not, then it should return an error. I've been working with this code on the server side but for some reason it won't work. I've tried many different tweaks, but this is basically my structure:
Meteor.methods({
createConversation: function(secondPerson) {
function doesUserExist(secondPerson) {
var userx = Meteor.users.findOne({username: secondPerson});
if (userx === secondPerson) {
return false;
} else {
return true;
}
}
if (doesUserExist()) {
Conversations.insert({
person1: Meteor.user().username,
person2: secondPerson
});
} else {
Conversations.insert({
person1: "didn't work"
});
}
}
});
The main point you were missing is that find returns a cursor, whereas findOne returns a document. Here is one way to implement the method:
Meteor.methods({
createConversation: function(username) {
check(username, String);
if (!this.userId) {
throw new Meteor.Error(401, 'you must be logged in!');
}
if (Meteor.users.findOne({username: username})) {
return Conversations.insert({
person1: Meteor.user().username,
person2: username
});
} else {
throw new Meteor.Error(403, username + " does not exist!");
}
}
});
Note the following features:
validates that username is a string
requires that the user be logged in to create a conversation
reduces the user existence check to a single line
returns the id of the new conversation
uses Meteor.Error with explanations which can be seen on the client
To use it just open your browser console and try making calls like:
Meteor.call('createConversation', 'dweldon', function(err, id){console.log(err, id);});
I'm hoping to be able to customise the error objects that are passed to the client if an exception occurs on the server.
I'm using the 'then' function on the client to handle success and failure:
hub.server.login(username, password).then(function(result) {
// use 'result'
}, function(error) {
// use 'error'
});
If the login succeeds, 'result' is the return value of the Login method on the server. If the login fails, I throw an exception of 'CustomException'. This is an exception with a 'Code' property.
if (!IsValidLogin(username, password))
throw new CustomException { Code: "BADLOGIN", Message: "Invalid login details" };
If I have detailed exceptions enabled, the 'error' argument on the client is 'Invalid login details' - the Message property of the exception.
Is there any way I can selectively change the error result from a string to a complex object? i.e. if 'CustomException' is thrown in a hub method, return a {Code:[...], Message:[...]} object for the client-side fail handler?
This should demonstrate what I'd like to see on the client:
hub.server.login(username, password).then(function(userInfo) {
alert("Hello " + userInfo.Name);
}, function(err) {
if (err.Code === "BADLOGIN.USERNAME")
alert("Unrecognised user name");
else if (err.Code === "BADLOGIN.PASSWORD");
alert("Invalid password");
else
alert("Unknown error: " + err.Message);
});
(Note the 'Code' and 'Message' properties on 'err').
When you call MapHubs with EnabledDetailedErrors set to true as follows:
RouteTable.Routes.MapHubs(new HubConfiguration { EnableDetailedErrors = true });
you will receive your Exception's Message string as the parameter to your fail handler.
I see that you have already figured this out, but I'm including the server side code to enable detailed errors for anyone else who might find this question later.
Unfortunately there is no easy way to send a complex object to the fail handler.
You could do something like this though:
if (!IsValidUsername(username))
{
var customEx = new CustomException { Code: "BADLOGIN.USERNAME", Message: "Invalid login details" };
throw new Exception(JsonConvert.SerializeObject(customEx));
}
if (!IsValidPassword(username, password))
{
var customEx = new CustomException { Code: "BADLOGIN.PASSWORD", Message: "Invalid login details" };
throw new Exception(JsonConvert.SerializeObject(customEx));
}
Then on the client:
hub.server.login(username, password).then(function(userInfo) {
alert("Hello " + userInfo.Name);
}, function(errJson) {
var err = JSON.parse(errJson);
if (err.Code === "BADLOGIN.USERNAME")
alert("Unrecognised user name");
else if (err.Code === "BADLOGIN.PASSWORD");
alert("Invalid password");
else
alert("Unknown error: " + err.Message);
});
I know this is ugly, but it should work.