Meteor: Async Callback issues - javascript

Currently I have the user click submit and then a click event occurs where a token is created and a method is called. What I am trying to do is after the charge get a callback which says if it is successfully or not. If successful it will run router.go to the confirmation page. If it is not successful then it will let the user know the card has been declined. All the above I can code out except despite non stop tinkering, I can't seem to figure out how to pass the message back to the event.
Here is my server side method:
Meteor.methods({
'chargeCard': function(token,amount,email) {
var Stripe = StripeAPI('where the key info guys');
// get a sync version of our API async func
var stripeCustomersCreateSync=Meteor.wrapAsync(Stripe.customers.create,Stripe.customers);
// call the sync version of our API func with the parameters from the method call
var result=stripeCustomersCreateSync({
description: 'Woot! A new customer!',
card: token,
email: email
}, function(error,result) {
if(error) {
return error;
}
return 'Success';
});
return result;
}
});
and my Client side method:
Stripe.card.createToken({
number: $('#cc-number').val(),
cvc: $('#card-cvc').val(),
exp_month: expM,
exp_year: expY,
name: $('#fn').val(),
address_zip: $('#postCode').val()
}, stripeResponseHandler);
}
function stripeResponseHandler(status, response) {
var $form = $('form');
if (response.error) {
// Show the errors on the form
$form.find('.validation').text(response.error.message);
return false;
} else {
var token = response.id;
var amount = 15000;
var Payid = $('#pid').text();
var userEmail = Leaguemembers.findOne({_id: Payid}).Email;
Meteor.call('chargeCard', token, amount,userEmail, function (error, result) {
console.log(error,result); alert(result); alert(error);
}
);
}
};
Any help would be greatly appreciated.
EDIT:
I went back into the backend and I can see the errors being generated through console.log but still am unable to pass it back to where the call was made to display those errors to the user or pass them to the confirmation page. All I seem to get is undefined.

The meteor.call should look like this
Meteor.call('chargeCard',token,amount,username,function(err,result){
if(!err){
Router.go("theRoute") //if there is not error go to the route
}else{
console.log(err.reason) // show the error
}
})

Related

AJAX response error: net::ERR_EMPTY_RESPONSE

CODE:
FRONT-END
$(document).ready(function(){
$('.delete-post').on('click', function(){
var id = $(this).data('id');
var section = $(this).data('section');
var url = '/users/delete/'+id;
if(confirm("Delete Post ?")){
$.ajax({
url: url,
type:'DELETE',
success: function(result){
console.log('Deleting post...');
window.location.href='/users/profile';
},
error: function(err){
console.log(err);
}
});
}
});
});
BACK-END:
router.delete('/delete/:id', function(req, res, next) {
var id = req.params.id;
var section = req.params.section;
var image = "";
var author = "";
var postRef = firebase.database().ref("posts/"+section+"/"+id);
var userRef = firebase.database().ref("users/posts/"+id);
var likesRef = firebase.database().ref("users/likes/"+id);
var hotRef = firebase.database().ref("hot/"+section+"/"+id);
postRef.once('value', function(snapshot){
image = snapshot.image;
author = snapshot.author;
if (firebase.auth().currentUser.uid.toString() == author) {
var file = bucket.file(image);
file.delete(function (err, apiResponse) {
if (err) {
console.log(err);
}
else {
console.log("Deleted successfully");
postRef.remove();
userRef.remove();
hotRef.remove();
likesRef.remove();
req.flash('success_msg','Post Deleted');
res.send(200);
}
});
}
});
});
SITUATION:
I added delete buttons so the user could delete his posts.
When the user clicks the button an AJAX request is made to my Node.js server.
But I get the following error:
ERROR:
net::ERR_EMPTY_RESPONSE
QUESTION:
What is this error and how do I fix it ?
The response you're getting is actually correct. Per the docs, Firebase returns a 200 status code and an empty response. net::ERR_EMPTY_RESPONSE is exactly that. What you should do is check for both null and a 200 status code in the response; if true, you can safely assume that the post was deleted.
My personal opinion is that Firebase should really consider returning something more substantial than nothing and a generic, catch-all status code. I'd prefer something like 204 No Content, or 410 Gone. But, alas.
—
Side note: this conditional will never return anything if the post doesn't belong to the author — your API should still return something (an error, probably in this case) even if your conditional doesn't match. Like:
if (firebase.auth().currentUser.uid.toString() == author) {
// your code
} else {
res.status(401).send("User does not have permission to complete the operation.")
}

Ajax call on $.ajax().complete

I have a problem with jQuery ajax function. I working with API that provides users and RBAC managment. By design this is separated functions, so when i create a user and assign a role for it i should call two requests - first i send 'create user' and it's return a {"success":"true", "id":"[id nuber]"} then i send 'assign role' with params like "{"item":"RoleName", "user_id":"[id from previous request]"}".
There is object "api" which have some methods for work with API. It is a simple wrapper which knocking on www.myurl.api/ and returns json. Because of it may take a long time api object methods takes a handlers that will be run on success and fail. If api now running a request then api.ready == false, otherwise api.aready == true. Result of last request stored in api.data as object.
Problem is that result not saved in api.data in case when two API request cascaded, like:
api.send(params, //params is json for user creation
function(){ //handler on this request result
... //creating another parms for assignment from api.data
api.send(params2, function(){//handler that works if api coorectly creates a new user
... //here i try send a request with params and it fails
})
}
);
code of api.send method:
send: function (entity, request, params, method, handler){
if (!method)
method='POST';
if (request.toLowerCase()=='get')
request = '';
if (request)
request += '-';
api.data = null;
params.apiKey = api.key;
api.ready = false;
api.handler = handler;
$.ajax({
url: this.url+request+ entity,
method: 'GET',
data: params
}).complete(function(msg) {
api.data = JSON.parse(msg.responseText);
if (api.data[0] && api.data[0].meta)
api.data.forEach(function (element, index, array){
element.meta = JSON.parse(element.meta)
});
api.ready = true;
api.handler.call();
});
}
and this is function that calls to create new user
function createUser(){
validateCreateForm();
if (!createValidated )
return;
var values = {
"username": $('#inputUsername').val(),
"password": $('#inputPassword').val(),
"comment": "Added by "+adderUsername
};
api.send('users','add', values, 'POST', function () {
if (api.data.success="true"){
//===========all in this if works ONLY if api works succesfully
//===========and api.data.id is exist and correct
message("success", "Was created username " + values.username);
$('#inputUsername').val('');
$('#inputPassword').val('');
//==========Problem is here
id = api.data.id; //in this var stores id
console.log('api.data.id is ' + id);//undefined, should be some int.
//if write something like id=42 rights will be correcttly assigned for user with id 42
//================================================================
if (!$('#inputRole').val())
return;
api.send('assignments',
'add',
{
"user_id": id,
"item_name": $('#inputRole').val()
},
'POST',
function () {
if (api.data.success="true"){
message("success", "Account was created and permissions granted");
}
else {
message("success", "Inner error. Please, try again later.");
}
}
);
}
else {
message("danger", "Inner error. Please, try again later.");
}
);
}

Passing data from service to controller

I have created a service with the purpose of accessing an API. I need to return data to my controller but am unsure how to do this as I completely new to sails.
My Service:
// API call to get the ID of contact within Get Response with email address
getContact: function(options) {
// establish connection to API
var api = new getResponse(apiKey, apiUrl);
var contact = api.getContactsByEmail(options.email, null, null, function (response) {
JSON.stringify(response);
console.log(JSON.stringify(response));
});
return contact;
},
I know the API call is working as when I log the response I get the correct response:
{"success":true,"data":{"error":null,"id":1,"result":{"sds":{"ip":null,"name":"Full Name","origin":"api","cycle_day":0,"email":"email#email.com","campaign":"id","created_on":"date","changed_on":null}}}}
My Controller:
index: function(req, res) {
var contact = GetresponseService.getContact({email: 'email#email.com'});
console.log(contact);
return res.send(contact);
}
I want to retrieve the ID value but when I log the value of contact I get undefined. I think my problem is related to the scope but not sure.
Can anyone point me in the right direction?
Because you are directly assigning a value from api.getContactsByEmail() which does not return a value.
By the nature of node.js, the function api.getContactsByEmail() gives you callback with the response. You have to get the value from within the anonymous callback function like this:
// API call to get the ID of contact within Get Response with email address
getContact: function(options) {
// establish connection to API
var api = new getResponse(apiKey, apiUrl);
var contact = "";
api.getContactsByEmail(options.email, null, null, function (response) {
contact = response;
JSON.stringify(response);
console.log(JSON.stringify(response));
return contact;
});
}
more ...
In my opinion, its better to return a callback instead of direct return of value.
// API call to get the ID of contact within Get Response with email address
getContact: function(options, callback) {
// establish connection to API
var api = new getResponse(apiKey, apiUrl);
var contact = "";
api.getContactsByEmail(options.email, null, null, function (response) {
contact = response;
JSON.stringify(response);
console.log(JSON.stringify(response));
if(typeof(callback) == "function")
callback(contact);
else
return contact; // return contact if there is callback func.
});
}
You can use it like:
index: function(req, res) {
var contact;
GetresponseService.getContact({email: 'email#email.com'}, function(contactResult) {
contact = contactResult;
console.log(contact);
return res.send(contact);
});
}
Everything looks like it should work, however I think you're running into an issue with this piece
var contact = api.getContactsByEmail(options.email, null, null, function (response) {
JSON.stringify(response);
console.log(JSON.stringify(response));
});
api.getContactsByEmail is asynchronous I assume, so this declarative statement won't work.
Instead declare contact and return it inside the callback, something like:
api.getContactsByEmail(options.email, null, null, function (response) {
JSON.stringify(response);
console.log(JSON.stringify(response));
var contact = response.contacts; //or however you access the contact(s) from the response variable
//You should also be watching for errors in your callbacks, but that's a different topic
return contact;
});
Read up on asynchronous calls in javascript and make sure you have a solid grasp on when data is accessible when using them.

Why does validation not fail on client side but fails on server side?

Right now, I have a form with several fields and on submit, I want to check if the username is taken or not. If taken, do nothing (show validation error), if not taken, successfully proceed onto the next form.
Here's what I have done so far:
View:
var RequestCreateAccount_Submit = function () {
var res = false;
ValidationAttribute.BlankValue(true);
var form = $('form#RequestCreateAccount');
$.validator.unobtrusive.parse(form);
var res = form.valid();
var data = form.serialize();
if (res) {
$.ajax({
url: Url.getFullUrl('Account/RequestCreateAccount_Submit'),
type: 'Post',
data: data,
cache:false,
success: function (data) {
//Next Dialog
},
error: AjaxLog.HandleAjaxCallFail
});
}
return res;
}
Controller:
[AllowAnonymous]
[HttpPost]
public ActionResult RequestCreateAccount_Submit(UserAccount userAccount)
{
//Check if username is unique
if (!WebSecurity.UserExists(userAccount.UserName))
{
UserSession.AddValue(StateName.CreateOrEditAccount, "CurrentUserAccount", userAccount);
JsonResult res = Json(new { Success = true, data = "", Message = "" });
return res;
}
JsonResult jres = Json(new { Success = false, data = "", Message = "Username is already registered"});
return jres;
}
I tested it with a known username and it did hit the success=false (outside of the if statement) line and it did not go inside the if statment. So I know the validation on the server side works.
However, I am wondering why on the client side, it still success = true and the next dialog appeared. It did not fail on validation. What am I doing wrong on the client side?
The reason is that your controller does actually successfully return a result. It is just that the successful result indicates an error. While logically similar at this point, they are very different. Error is going to be reserved for actual exceptions thrown or 404 no route present type of scenarios.
You should check for the response status inside of your success callback function
dotNetFiddle Demo
$.ajax({
url: Url.getFullUrl('Account/RequestCreateAccount_Submit'),
type: 'Post',
data: data,
cache:false,
success: function (data) {
if(data.Success === false){
AjaxLog.HandleAjaxCallFail();
// this may not make as much sense though
// as the request didn't actually fail, just the action did
//TODO: code for name fail
return;//do not process next dialog
}
//Next Dialog
},
error: AjaxLog.HandleAjaxCallFail
});
The success = false of your result object doesn't means that the request failed. It stands only for data.success, nothing more. The resquest is still successful(HTTP 200), which I think is the right response code. If you return an error code like new HttpStatusCodeResult(404, "error message"); it means that your request failed, but it isn't true.
You request works whatever the result of the check is. So you may check this in your success callback, instead of the error callback:
success: function(data) {
if (data.success) {
//Next Dialog
}
else {
// error message
}
}

How to chain functions in Parse CloudCode?

I've done a parse job that checks every "X" time if "emailSent" is false, for each user. If it is, I call a function to send a email and change the "emailSent" to true. That works.
My problem is with the function "getMaxId". I need to return the maxid value to change each user "id_client" column, but I don't know how. I've tried this but it doesn't work. This is writing nothing: "console.log("Write somethingggg"); "
Here is the code...
Parse.Cloud.job("test", function(request, status) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
var texto = "New verified emails:\n\t";
// Query for all users
var query = new Parse.Query(Parse.User);
//query.equalTo("emailVerified", true);
query.equalTo("emailSent", false);
query.each(function(user) {
user.set("emailSent", true);
user.save();
var datos = user.get("email")+"\n";
texto=texto+datos;
Parse.Cloud.run("getMaxId", {},{
success: function(results) {
console.log("Write somethingggg");
user.set("id_client", "gofoadasda");
user.save();
var datos = user.get("id_client")+"\n";
//console.log("id_client: "+datos);
response.success();
},
error: function(results, error) {
response.error(errorMessageMaker("running chained function",error));
}
}).then(function() {
// Set the job's success status
}, function(error) {
// Set the job's error status
status.error("Uh oh, something went wrong.");
});
Parse.Cloud.run("sendEmail",{
success: function(results) {
response.success(results);
},
error: function(results, error) {
response.error(errorMessageMaker("running chained function",error));
}
});
}).then(function() {
// Set the job's success status
console.log("texto: "+texto);
status.success("Migration completed successfully.");
}, function(error) {
// Set the job's error status
status.error("Uh oh, something went wrong.");
});
});
Parse.Cloud.define("sendEmail", function(request, response) {
Parse.Cloud.httpRequest({
url: 'http://www.example.com/sendemail.php',
params: {
email : 'email#email.com'
},
success: function(httpResponse) {
console.log(httpResponse.text);
},
error: function(httpResponse) {
console.error('Request failed with response code ' + httpResponse.status);
}
});
});
Parse.Cloud.define("getMaxId", function(request,response) {
var query = new Parse.Query(Parse.User);
query.descending("id_client");
query.find({
success: function(results) {
var idmax=results[0].get("id_client")
console.log("idmax: "+idmax);
response.success(idmax);
},
error: function() {
response.error(" is an error");
}
});
});
FIRST CHANGES:
After #danh help, I tried to do what I need, changing some code:
Important: id_client is a int value which it's unique for each user, it starts at 20000.
get all the users with the flag sentEmail=false.
For each of those users, getMaxId (this returns the actual max "id_client" value for all the users).
Change value of sentEmail to true, set user id_client to the actual max id.
Send email.
New code (sendEmail has no changes):
var _ = require('underscore');
// return a promise to get the max value of id_client in the user table
function getMaxId(user) {
var query = new Parse.Query(Parse.User);
//return query.count();
query.descending("id_client");
query.limit(1);
return query.find().then(function(users) {
if(users[0].get("id_client")<20000){ //No users yet.
user.set("id_client", 20000); //First id:20000
user.save();
return 20000;
}
else{ //There are users. Get the maxId and increment +1.
user.set("id_client", users[0].get("id_client")+1);
user.save();
return (users.length)? users[0].get("id_client")+1 : 0;
}
});
}
// return a promise for users with emailSent flag == false
function usersWithUnsentEmail() {
var query = new Parse.Query(Parse.User);
query.equalTo("emailSent", false);
return query.find();
}
// return a promise to send email to the given user, and to set its
// emailSent flag = true
function sendEmailToUser(user) {
return sendEmail(user.get("email")).then(function() {
user.set("emailSent", true);
return user.save();
});
}
Parse.Cloud.job("test", function(request, response) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
usersWithUnsentEmail().then(function (users){
var emailPromises = _.map(users, function(user) {
//what I understand is that here, for each user, we call getMaxId, getting the actual max id_client, and then, we pass it to "sendEmailToUser".
return getMaxId(user).then(function(max){
return sendEmailToUser(user);
});
});
return Parse.Promise.when(emailPromises);//This means that we have looped all users, is it?
}).then(function(results) {
response.success(results);
}, function(error) {
response.error(error);
});
});
I've tested this with 2 users with the flag "sentEmail" = false and actual max id_client was 20001
Result:
sentEmail flags changed correctly.
2 emails sent correctly.
Error here: id_client for both users changed to 20002. It has to be 20002 and 20003.
Logs in parse:
I2015-04-22T09:44:13.433Z] v90: Ran job test with:
Input: {}
Result: undefined
E2015-04-22T09:44:29.005Z] v90: Ran job test with:
Input: {}
Failed with: Error: Job status message must be a string
at updateJobMessageAndReturn (<anonymous>:790:7)
at Object.success (<anonymous>:828:9)
at main.js:217:18
at e (Parse.js:3:8736)
at Parse.js:3:8185
at Array.forEach (native)
at Object.x.each.x.forEach [as _arrayEach] (Parse.js:1:661)
at c.extend.resolve (Parse.js:3:8136)
at Parse.js:3:8815
at e (Parse.js:3:8736)
EDITED:
We need their email and the id_client that we will assign them.
May be I haven't explained well, the email won't be sent to the user email, the email will be sent to a email that I've determined in the sendemail.php script, and it will be always the same.
I'll explain: You have a local database at home, and parse database. When this Parse.job is called, it will send an email to you (email of php) with a list of the email and the id_client of each user updated. Now you can manually update your local database with the email received info.
So, for this reason, it will be better to send only one email, at the end of all the updates. (I didn't say that because I had a lot of problems yet trying to understand how cloudCode works...)
There are a few things that need fixing in the code: (1) as a rule, use promises if you're doing more than two consecutive asynchronous things, (2) don't call Parse.Cloud.run from cloud code, it's what you call from clients who wish to invoke cloud functions, (3) style-wise, you'll go nuts trying to figure it out later on unless you break the code into small, promise-returning steps.
I've applied all three bits of advice to your code. I don't fully understand the logic as described in code and text, but hopefully I got close enough for you to make sense of it.
// using underscore js, which provides _.map below as well as loads of other useful stuff
var _ = require('underscore');
// return a promise to get the max value of id_client in the user table
function getMaxId() {
var query = new Parse.Query(Parse.User);
query.descending("id_client");
query.limit(1);
return query.find().then(function(users) {
return (users.length)? users[0].get("id_client") : 0;
});
}
// return a promise for users with emailSent flag == false
function usersWithUnsentEmail() {
var query = new Parse.Query(Parse.User);
query.equalTo("emailSent", false);
return query.find();
}
// return a promise to send email to the given user, and to set its
// emailSent flag = true, and to set its clientId to the passed value
function sendEmailToUser(user, idClient) {
return sendEmail(user.get("email")).then(function() {
user.set("emailSent", true);
user.set("id_client", idClient);
return user.save();
});
}
// return a promise to send email to the given email address via an http service
function sendEmail(email) {
var params = {url: 'http://www.example.com/sendemail.php', params: {email : email} };
return Parse.Cloud.httpRequest(params);
}
Parse.Cloud.job("test", function(request, response) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
var maxIdClient;
getMaxId().then(function(result) {
maxIdClient = result;
return usersWithUnsentEmail();
}).then(function(users) {
var emailPromises = _.map(users, function(user) {
return sendEmailToUser(user, maxIdClient);
});
return Parse.Promise.when(emailPromises);
}).then(function(results) {
response.success(results);
}, function(error) {
response.error(error);
});
});
EDIT - we're kind of working on logic here particular to the app, as opposed to the concept of promises, but here goes anyway. To restate the functional requirement: We want a job to find users who have not yet been recorded in another database, represented by a flag called "emailSent". Our goal is to assign these users a unique id, and send their info (for now, we'll say email address and that id) via email to some fixed destination.
So
// getMaxId() from above is almost ready, except the minimum id_client
// value is 20000, so change the line that set this to:
return (users.length)? users[0].get("id_client") : 20000;
// usersWithUnsentEmail() from above is fine
// delete sendEmailToUser() since we're not doing that
// change sendEmail() to take an array of users to be conveyed to
// the other database. Send email about them, then change each user's
// emailSent status and save them
function sendEmail(users) {
var params = {url: 'http://www.example.com/sendemail.php', params: {users : JSON.stringify(users)} };
return Parse.Cloud.httpRequest(params).then(function() {
_.each(users, function(user) {user.set("emailSent", true);});
return Parse.Object.saveAll(users);
});
}
// add a function that takes an array of users, updates their
// id_client to be a new, unique value, and sends mail about them
// to a remote server
function synchUsers(users, idMax) {
_.each(users, function(user) {
user.set("id_client", idMax);
idMax += 1;
});
return sendEmail(users);
}
// update the job taking all this into account
Parse.Cloud.job("test", function(request, response) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
var maxIdClient;
getMaxId().then(function(result) {
maxIdClient = result;
return usersWithUnsentEmail();
}).then(function(users) {
return synchUsers(users, maxIdClient+1);
}).then(function(results) {
response.success(results);
}, function(error) {
response.error(error);
});
});

Categories