Updating data from Parse.com without re-login - javascript

I have a web app, that manages a budget for a user.
In the settings page, I can edit the budget, after clicking "save" I return to the main page, and there I have line that states the budget amount.
The problem is, that when I log in, I see the correct budget, after editing the budget and returning to the main page, I still see the old amount. Only after logging out and re-login again, that line in the main page updates to new amount.
Any solutions?
The code that saves the new budget:
$("#saveNewBudgetAmount").click(function(){
var User = Parse.User.extend("User");
var query = new Parse.Query(User);
var newBudget = $("#newBudgetSum").val();
query.equalTo("objectId", Parse.User.current().id);
query.first({
success: function (User) {
User.save(null, {
success: function (user) {
User.set("budget", newBudget);
User.save();
location ="Mainpage.html";
}
});
}
});
});
and the code that displays it on the main page:
var MBudget = (function () {
if (Parse.User.current()) {
return("Your monthly budget is:" +" "+Parse.User.current().get("budget")+" "+"<a href=Settings.html>(Edit)</a>");
}

A few things are happening.
First you should simplify your code, and use both alerts AND error handling so that you know if your code works, and when callbacks are made. You are also calling .save() once before any new values are set, so you have a useless save.
You also need to have a success and error callback for EVERY save function you use - .save() by itself is an asynchronous method, and since you are not calling a success function within your save method, so your app will navigate back to "Mainpage.html" before it is known whether or not the save function worked. Here is a much better implementation:
var newBudget = $("#newBudgetSum").val();
var currentUser = Parse.User.current();
currentUser.save(
{
// Set as many properties as you like in this field,
// think of it as a JSON object except you don't
// have to enclose the values in strings.
budget : newBudget,
}, {
success: function(user) {
alert("Budget successfully saved, new budget is: " + user.get("budget"));
},
error: function(error) {
// error functions will always have an error argument handed back to the client,
// with properties error.code and error.message. Error messages are incredibly useful.
alert("Budget save failed, error: " + error.code + " " + error.message);
}
});
Another tip is that I recommend all users of Parse.com to use alert() messages for their success and error callbacks while in development, for many reasons - but the key reasons are 1) it will alert you to whether or not the code worked, and 2) it will prevent accidental bugs from causing infinite requests to the Parse.com server, which does happen sometimes, and will cause them to charge your account.

The problem is this api : Parse.User.current() never sync data in the cloud. The data of Parse.User.current() is derived from localstorage. You have to refresh it manually by calling save or fetch method on it.
Parse.User.Current() return normal Parse.User object. You can use it directly without querying in advance. So you can just rewrite your first codes as following :
$("#saveNewBudgetAmount").click(function() {
var user = Parse.User.current() ;
var newBudget = $("#newBudgetSum").val();
user.set("budget", newBudget);
user.save(null,{
success: function(user) {
// feedback user and redirect page.
},
error: function(user, error) {
//You should always handle error.
console.error(error.message) ;
}
}) ;
});
With this code, the local data would refresh when ths save() call done successfully. On your main page the budget value of Parse.User.current() object would be correct.

Related

javascript "callbacks" across redirecting / after reloading

I've got a site (asp.net mvc razor) on wich some functionalities require authorization / login.
These functionalities can be started by clicking on a button for example.
By clicking on such a button, the system checks whether the user is logged in or not.
If not, the user is redirected to the login page where he can sign in.
After that he will be redirected to the initial page again without initiating the users action.
So heres the workflow:
->Page x -> button y -> click -> redirect to login -> login -> redirect to x.
The redirects are simple Url.Action() statements.
What I want to do is to dynamically redirect to the page the click came from and ideally jump to the senders selector in order to simplify things for users.
What possibilities do I have to achieve this?
Only things coming to my mind are quite ugly stuff using ViewBag and strings
Update:
Info: As storing session variables causes problemes concerning concurrent requests this feature is disabled solution wide so I cannot use session variables.
Besides: One of the main problems is, that I cannot sign in without making an ajax call or sending a form. And by sending a form or making an ajax call I loose the information about the original initiator of the action and the parameters.
I solved this by adding by adding this to all such actions in their controllers:
[HttpPost]
public ActionResult ActionA(Guid articleId, Guid selectedTrainerId)
{
//if user is not authenticated then provide the possibility to do so
if (!Request.IsAuthenticated)
{
var localPath = this.ControllerContext.RequestContext.HttpContext.Request.Url?.LocalPath;
var parameter = this.ControllerContext.RequestContext.HttpContext.Request.Params["offeringRateId"];
var returnUrl = localPath + "?articleId=" + parameter;
return PartialView("LoginForOfferingPreview", new LoginForOfferingPreviewViewModel
{
RequestUrl = returnUrl,
//this will be used in the view the request was initiated by in order to repeat the intial action (after login has been successfull)
Requester = OfferingPreviewRequester.CourseTrialAdd,
//this will be used in the view to initiate the request again
RequestParameters = new List<dynamic> { new { articleId = articleId },new { selectedTrainerId = selectedTrainerId }}
});
}
//actual action
SendBasketEvent(new CourseAddMessage
{
BasketId = BasketId,
OfferingRateId = articleId,
SelectedTrainerId = selectedTrainerId,
SelectedTime = selectedTime,
Participants = selectedParticipants,
CurrentDateTime = SlDateTime.CurrentDateTimeUtc(SlConst.DefaultTimeZoneTzdb),
ConnectionId = connectionId
}, connectionId);
return Json(JsonResponseFactory.SuccessResponse());
}
the hereby returned view for login contains following js code that is called if the login has been succesfull:
function onLoginFormSubmit(data) {
//serialize form containing username+pw
var datastring = $("#loginForm").serialize();
$.ajax({
type: "POST",
url: '#Url.Action("Login_Ajax","Account",new {area=""})',
data: datastring,
success: function (data) {
debugger;
// display model errors if sign in failed
if (!!!data.Success) {
$(".buttons-wrap").append('<span id="loginFormError" style="color:red;"></span>');
$("#loginFormError").append(data.ErrorMessage);
}
//call method of initiating view that will decide what to dow now
if (data.Success) {
var parametersObjectAsString = JSON.parse('#Html.Raw(JsonConvert.SerializeObject(Model.RequestParameters))');
window.onLoginForOfferingPreviewSuccess('#Model.RequestUrl', parametersObjectAsString, '#((int)Model.Requester)');;
}
},
error: function () {
}
});
}
this works fine as long sigining does not fail due to wrong username or pw.
If that happens, the view shows the errors but by now signing in again somethign really strange happens:
At first it seems to work exaclty like signing in successfully by the first time but then the ajax calls in window function onLoginForOfferingPreviewSuccess will always reach the error block without beeing able to tell you why.
Fiddler reveals weird http resonse codes like 227,556 or something
Thx

How to run a Parse cloud job in batches of users to avoid time limit?

I'm currently running the following Parse cloud code job which iterates through every user in the database, and I'm starting to hit the 15 minute time limit. How can I set this to run in batches of 500 users at a time instead of all users at once? I would need it to run through the users in order, so user 1-500 for the first batch, then 500-1000 for the 2nd batch, and so on, that way it doesn't repeat anyone.
Parse.Cloud.job("MCBackground", function(request, status) {
// ... other code to setup usersQuery ...
Parse.Cloud.useMasterKey();
var usersQuery = new Parse.Query(Parse.User);
return usersQuery.each(function(user) {
return processUser(user)
.then(function(eBayResults) {
return mcComparison(user, eBayResults);
});
})
.then(function() {
// Set the job's success status
status.success("MCBackground completed successfully.");
}, function(error) {
// Set the job's error status
status.error("Got an error " + JSON.stringify(error));
});
});
Well, for starters, from your comment I see you have a constraint you can add: only query for user objects that has a matchCenterItem:
query.exist("matchCenterItem");
I don't know how many percent of your users would not have this field, but maybe you already now will have reduced the number of Users to fetch.
I have no idea how often this job is run or what you are comparing with, but how likely is it that this data will change? Do you always need to run it on ALL users, or can you set a flag so that you don't run it on the same user again? At all, or until some time has passed?

After updating object (_User) and later retrieving it, field is not updated (Parse)

ISSUE
I have been trying to update a field (gamesArray) from the current User (Parse.User.current()), so later in another view I can see it. The problem is that when I do it, the field gets updated (I console.log() it right after the object is saved and check it on the Database UI from Parse) but when I go to the other view, in which the User is retrieved and I show the field on screen, it is not updated.
CONTEXT
My _User has an array of Games called gamesArray so I update that array just like this:
addGameToUser = function(game, userId) {
var userQuery = new Parse.Query(Parse.User);
userQuery.equalTo("objectId", userId);
userQuery.first({
success: function(user) {
// User found
},
error: function(error) {
console.log(error.message);
}
}).then(function(user) {
// If user is current user update it
if(user.id == Parse.User.current().id) {
var games = user.get('gamesArray');
games.push(game);
user.set('gamesArray', games);
user.save(null, {
success: function(user) {
console.log(user.get('gamesArray'));
},
error: function(user, error) {
console.log(error);
}
});
}
else {
// Call cloud function...
}
});
}
In this case, console.log(user.get('gamesArray')) returns the updated field. I use this function when the user creates a new game.
The problem is that in a different view, when I retrieve the user and get that field, it is not updated:
getUserGames: function() {
var games = Parse.User.current().get('gamesArray');
console.log(games);
}
Here, console.log(games) is printing the old field value. So if I had 4 games in the array, the previous function printed 5 (4 plus the created one) and this still prints 4.
I thought that maybe I was not saving the Game properly, but this is the output of the Parse's Database UI gamesArray column of the current User:
[{"__type":"Pointer","className":"Game","objectId":"..."}]
The only way that I can get the updated field is logging out and logging in with the same user.
QUESTIONS
Why is that? What am I doing wrong? How could I "update" the user so I don't have to log out?
It sounds like your user object that you are reading from is not the same specific object that you are updating. So you are updating the object in your database, but the user object you're reading from doesn't know that the object in the database has been updated. You should try fetching the user first. iOS has a fetchIfNeeded method.

Parse.com security: can I save an object and claim it's another user's?

I'm looking at this example of modeling a blog system using javascript, code snippet copied as below:
var user = Parse.User.current();
// Make a new post
var Post = Parse.Object.extend("Post");
var post = new Post();
post.set("title", "My New Post");
post.set("body", "This is some great content.");
post.set("user", user);
post.save(null, {
success: function(post) {
// Find all posts by the current user
var query = new Parse.Query(Post);
query.equalTo("user", user);
query.find({
success: function(usersPosts) {
// userPosts contains all of the posts by the current user.
}
});
}
});
It basically creates a post object and sets the current user object to its user field. To show all blog posts by the current user, it queries all blog posts with the user field set to the current user.
But since the User table by default is read only to all users, wouldn't this be problematic that a malicious user (X) can create random posts and "claim" that they are create by another user (Y), by setting the user field of those posts to Y as he queries from the User table? So the consequence would be that when the system shows posts for user Y, he would see all his true posts in addition to the post that was "forged" by X.
Is the mitigation that the User table needs to be ACL'd somehow? But if it is the solution, then why is the default behavior that an arbitrary user can see the entire User table?
Cloud Code is your friend here.
In this case you want a beforeSave handler that locks the user field to the currently authenticated user on new objects, and rejects the save if they're updating a post and trying to change the user field (or just using ACLs to prevent everyone except the post owner from modifying Post rows).
Something like this:
Parse.Cloud.beforeSave('Post', function(request, response) {
var post = request.object;
var user = request.user;
if (post.isNew()) {
post.set('user', user);
response.success();
} else {
// any special handling you want for updates, e.g.:
if (post.dirty('user')) {
response.error('Cannot change the owner of a Post!');
} else {
response.success();
}
}
});
My recommended approach to handling updates for something like a "Post" would be to prevent all updates. In the "Set permissions" for the class in the Data Browser I would change the following:
Update : Disabled
Delete : Disabled
To disable something just untick the "Any user can perform this action". Optionally you might want to assign a Role like "Administrator" or "Moderator" to allow those people to directly update/delete items.
These functions would then only be possible from Cloud Code when useMasterKey() is used, e.g.:
Parse.Cloud.define('deletePost', function(request, response) {
var postID = request.params.postID;
var query = new Parse.Query('post');
query.get(postID).then(function (post) {
if (post) {
// post found
var postOwner = post.get('user');
if (postOwner.id == request.user.id) {
// we let the owner delete their own posts
// NOTE: need to use the master key to modify/delete posts
Parse.Cloud.useMasterKey();
post.destroy().then(function () {
// TODO: delete all replies too?
response.success();
}, function (error) {
response.error(error);
});
} else {
response.error('Only the owner of a post can delete it!');
}
} else {
// post not found, might as well respond with success
response.success();
}
}, function (error) {
response.error(error);
}
});
But since the User table by default is read only to all users,
wouldn't this be problematic that a malicious user can create random
posts and "claim" that they are create by another user, by setting the
user field to the other user?
You can play around with curl to explore this.
IMO - you are right about world read on the _User class. So what. That is read.
When it comes to POST action, you are going to need an authenticated session as the user in question. You cant just spuuf things by claiming that u are a user that you read on the table.
try curl posts without an established session as the user. You will get a 403 or some 'illegal access' response.

Phonegap db.transaction callback firing more than once

I am having an issue with a project I am working on using phonegap
[Step 1] -
I am storing a users login "session" locally (Web SQL) from a web server. This is working fine, I can successfully connect to the web server, post the user login data, get a response from the server, create the local User database and store the user "session" value
[Step 2] - I then need to pass this "session" value back to the web server, and receive response data from the server. Again, this works as expected but the problem is, the callback function is being executed twice.
Step 2 is called when the user taps a button on the screen, and it doesn't seem like the reason that Step 2 is being called twice if because phonegap is picking up the tap more than once, I have tried:
$(".yes_sync").live("tap", function(){
console.log("tap!");
...
Which only logs a single tap event.
When the user taps I am calling:
var db = window.openDatabase("MVIdb", "1.0", "MVIsqlite", 200000);
db.transaction(getUserId, getUserIdFailed, getUserIdsSuccess);
The getUserId, getUserIdFailed and getUserIdsSuccess functions look like so:
function getUserId(tx){
tx.executeSql("SELECT * FROM user WHERE id = '1'", [], getUserIdsSuccess, getUserIdFailed);
}
function getUserIdFailed(tx, results){
console.log("Error retrieving user session ID");
}
function getUserIdsSuccess(tx, results){
console.log("Success retrieving user session ID");
if(typeof results != 'undefined'){
var return_value = results.rows.item(0).user_id;
user_session_id = return_value;
var token = $.md5(user_session_id+"whatever!");
$.get('http://localhost/project/dummyserver/sync?user_id=' + user_session_id + '&token=' + token, function(data) {
data = $.parseJSON(data);
for (var key in data){
console.log(data[key]['user_id']);
}
$(".ui-loader").fadeOut();
jQuery.mobile.changePage("_sync_complete.html", { role: "dialog", transition: "pop" } );
});
}
}
As you can see, the last line in the callback for the $.get in the callback for the success opens a pop up dialogue. This dialogue is being called twice.
I have noticed that phonegap has a lot of asynchronous behaviour, which I understand is to prevent the system from feeling "laggy", but surely it shouldn't be executing a callback function on a db.transaction more than once?
Your callback is called twice because you are passing it both to the executeSql command and the transaction. Remove it from one to make it work as expected.
Not the most elegant solution, but I went with using a global variable
var has_getUserIdsSuccess = false;
And in the callback function:
if(typeof results != 'undefined'){
if (has_getUserIdsSuccess == false){
has_getUserIdsSuccess = true;
// as per above
has_getUserIdsSuccess = false; // set it back to false so use can press the execute this function again later
}
I am still interested to see if anyone has a better solution!

Categories