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.
Related
I have noticed the callback functions never get executed all though the server method runs fine. Also from the Meteor documentation, i understand that when Meteor.Error is thrown it will notify the client but i don't see that working as well. Am i doing something fundamentally wrong?
Client
if (Meteor.isCordova) {
getContacts(function (contacts) {
$meteor.call('createContacts', contacts, function(err){
alert("in create contacts callback");
if(err && err.error === "not-logged-in"){
alert("error due to not-logged-in");
$ionicPopup.alert({
title: err.reason || "User not logged in",
template: 'Please try again after logged in',
okType: 'button-positive button-clear'
});
}
else if(err && err.error === "contacts-exists"){
$ionicPopup.alert({
title: err.reason || "Connections already exists",
template: 'Please try again after logged in',
okType: 'button-positive button-clear'
});
}
$meteor.call('createConnections');
});
});
}
function getContacts(success, error) {
function onSuccess(contacts) {
success && success(contacts);
};
var options = {};
options.multiple = true;
var fields = ["displayName", "name"];
navigator.contacts.find(fields, onSuccess, error, options);
}
Server
createContacts: function (contacts, callback) {
if (!this.userId) {
throw new Meteor.Error('not-logged-in',
'Must be logged in to update contacts')
}
var userId = this.userId, exist = Contacts.findOne({userId: userId});
log.debug("Is contacts for userId %s exist in database ? %s", userId, !! exist);
if (!exist) {
Contacts.insert({'userId': userId, 'contacts': contacts}, function () {
callback && callback();
});
} else {
log.debug("Contacts for user exists so throwing exception as contacts-exists");
var meteorErr = new Meteor.Error('contacts-exists', "Contacts are already exist");
callback && callback(meteorErr);
}
},
These callbacks are asynchronous. Your server-side method shouldn't invoke the callback function, nor even expect one as an argument.
You are right to pass the callback as the last argument to Meteor.call('createContacts'), but it is not up to the receiver of createContacts to determine when that callback should be invoked. In simple terms, from the client's point of view, the server's job is simply to return an 'OK' or an 'error' signal.
Remove any reference to the callback in the method definition (on the server), and expect the client to execute that callback when the server responds.
Try this:
Server
createContacts: function (contacts) {
if (!this.userId) {
throw new Meteor.Error('not-logged-in',
'Must be logged in to update contacts');
}
var userId = this.userId;
var exist = Contacts.findOne({userId: userId});
log.debug("Is contacts for userId %s exist in database ? %s", userId, !! exist);
if (!exist) {
Contacts.insert({'userId': userId, 'contacts': contacts});
} else {
log.debug("Contacts for user exists so throwing exception as contacts-exists");
throw new Meteor.Error('contacts-exists', "Contacts are already exist");
}
},
Client
$meteor.call('createContacts', contacts, function(err){
alert("in create contacts callback");
if(err && err.error === "not-logged-in"){
alert("error due to not-logged-in");
$ionicPopup.alert({
title: err.reason || "User not logged in",
template: 'Please try again after logged in',
okType: 'button-positive button-clear'
});
}
else if(err && err.error === "contacts-exists"){
$ionicPopup.alert({
title: err.reason || "Connections already exists",
template: 'Please try again after logged in',
okType: 'button-positive button-clear'
});
}
$meteor.call('createConnections');
});
Seems like i need to use .then() as documented in Angular-Meteor Document
Also changed the code as per amageddian
I am following the example in the docs:
ref.authWithCustomToken(authToken, function(error, authData) {
if (error) {
console.log("Authentication Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
});
If I pass an invalid token to authWithCustomToken (e.g. authToken is undefined) , it throws an Error, rather than passing an error code to the callback. In other words, neither console.log is executed, but this error is thrown:
First argument must be a valid credential (a string).
Wrapping it in try catch solves the problem easily enough, but I didn't see any mention in the docs. Is it the intended behaviour?
In my use-case the token is passed through a url, so an undefined parameter is a possible error condition.
From firebase-debug.js:
if (!goog.isString(cred)) {
throw new Error(fb.util.validation.errorPrefix(fnName, argumentNumber, optional) + "must be a valid credential (a string).");
}
What probably happened:
You passed a non-string value as authToken so the error not happened in the Firebase (server) side, it happened in the client side (your side) so it will be not reported on the callback function but as Javascript exception in your code.
Possible workaround (I don't know why, but if you want)
If you want to pass a variable as authToken and it could not be a string, and you still want to get the "Wrong credentials" error, instead of type validation, then you need to force string conversion using:
var ref = new Firebase("https://<YOUR-FIREBASE-APP>.firebaseio.com");
var numberAuthToken = 212312312312312; // number
var stringAuthToken = String(numberAuthToken);
ref.authWithCustomToken(stringAuthToken, function(error, authData) {
if (error) {
console.log("Authentication Failed!", error);
} else {
console.log("Authenticated successfully with payload:", authData);
}
});
But it not makes sense for me. :)
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,
});
}
},
})
So I created simple Meteor method:
Meteor.methods({
insertMessage: function(message) {
check(message, String);
if (!this.userId) {
throw new Meteor.Error("not-logged-in", "Must be logged in to create message.");
}
if (!message) {
throw new Meteor.Error("empty-message", "Message cannot be empty string.");
}
var id = Messages.insert({
text: message,
author: Meteor.user().username,
createdAt: new Date()
});
return id;
}
});
I'm calling it on client side like so:
Meteor.call("insertMessage", message, function(error, result) {
if (error) {
console.log(error.reason);
}
});
Everything works awesome. When I'm not logged in I see: "Must be logged in to create message." in console.
BUT!
I also see error stack trace. How to disable it in production. I don't want to show strange stuff in console to users. It works not as an error, just information that I don't want to show.
Thank you.
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
});
}
});