Parse iOS SDK: How to set user "online" status? - ios

Scenario = I have an app that allows users to log in and view other users who are also "online". In order to set each user's online status I believe that I should set the code...
[PFUser currentUser] setObject:[NSNumber numberWithBool:YES] forKey:#"isOnline"];
[[PFUser currentUser] saveInBackground];
at certain times in the application when the user uses the app. Probably the appDelegate but I'm not sure.
(if this is not correct then please correct me)
Question = Where should this code be set inside the application so that it always keeps track of when a user is "online" and when they are "offline"?
(please include method names)

The most reliable way to handle this is to create a column named lastActive of type Date, then create a Cloud Code function to update this with the server time (so clock differences isn't an issue).
Then to get "online" users just have another Cloud Function that does a query where lastActive is greater than now - some time window like 2 minutes.
var moment = require("moment");
Parse.Cloud.define("registerActivity", function(request, response) {
var user = request.user;
user.set("lastActive", new Date());
user.save().then(function (user) {
response.success();
}, function (error) {
console.log(error);
response.error(error);
});
});
Parse.Cloud.define("getOnlineUsers", function(request, response) {
var userQuery = new Parse.Query(Parse.User);
var activeSince = moment().subtract("minutes", 2).toDate();
userQuery.greaterThan("lastActive", activeSince);
userQuery.find().then(function (users) {
response.success(users);
}, function (error) {
response.error(error);
});
});
Your client will want to call the registerActivity Cloud Function every 1.5 minutes to allow some overlap so users don't appear to go offline if their internet is a bit slow.
Of course you can adjust the time windows to suit your needs. You could also add the ability to filter which users are returned (e.g. online friends only).

Related

Parse - Delete another user as super admin from iOS app

I use back4app for my app, I would like to delete another user (not authorised user on this device).
The app throws this:
[Error]: User cannot be deleted unless they have been authenticated. (Code: 206, Version: 1.19.1)
which make sense to me if I am not a super admin of the product. But in case I have super admin rights I would like to remove a user from the system completely.
Is there any solution for this purpose? I've tried to find some function from Parse.Cloud code.
The idea was to create cloud code and execute it form the iOS device by calling smth like this:
PFCloud.callFunctionInBackground("deleteUserAsSuperAdmin",
withParameters: user id param here)
{ success, error) -> Void in
}
I have not found such a solution and for me it's a bit difficult to write such code in a right way using cloud code, for sure if this is an option.
You will have to create a cloud code function for that, and use useMasterKey option to delete the user. Something like:
Parse.Cloud.define('deleteUser', async ({ user, params: { userIdToDelete } }) => {
if (user) {
const query = new Parse.Query(Parse.Role);
query.equalTo('name', 'admin');
query.equalTo('users', user);
const count = await query.count({ useMasterKey: true });
if (count === 1) {
const userToDelete = new Parse.User();
userToDelete = userIdToDelete;
return userToDelete.destroy({ useMasterKey: true });
}
}
throw new Error('Not an admin');
});

PFInstallation currentInstallation clearCache

Are we able to clear PFInstallation data forcefully?
Needed this to recreate another PFInstallation when I want to force logout a user.
Current Problem:
New Account is using PFInstallation of old account and New Account can't update the PFInstallation (clearing it when logging out).
Other Possible Solution:
Update PFInstallation's ACL through cloud code with the new account's data. Is this possible?
In the beforeSave trigger for the installation, check to see if the user is getting set. If so, turn off public read/write access and give read/write access to that user. When logging out, the user should be removed, and you can return public read/write access.
Parse.Cloud.beforeSave(Parse.Installation, function(request, response) {
var installation = request.object;
if( installation.dirty("user") ) {
var acl = new Parse.ACL();
var user = installation.get("user");
if( user ) {
acl.setPublicReadAccess(false);
acl.setPublicWriteAccess(false);
acl.setReadAccess(user.id, true);
acl.setWriteAccess(user.id, true);
}
else {
acl.setPublicReadAccess(true);
acl.setPublicWriteAccess(true);
}
installation.setACL(acl);
}
response.success();
});

Sending Parse Push with Cloud Code

I cannot find any documentation on Parse.Push used in Parse Cloud Code. The usage case that I see is like this...
// Send the push notification to results of the query
Parse.Push.send({
where: pushQuery,
data: {
alert: message
}
}).then(function() {
response.success("Push was sent successfully.")
}, function(error) {
response.error("Push failed to send with error: " + error.message);
});
What I am trying to do is send a push notification if a recipient user is setup for notifications (i.e. has a valid Installation instance, associated to their user).
At the moment I create the query and pass that into the above with pushQuery. What I notice is that a Push is created in the Parse dashboard but the Pushes sent is 0.
Really I just want to create the Push if a user exists. I have created the query and can run this and return if I get results or not like this...
Parse.Cloud.define("sendTurnNotificationToUser", function(request, response) {
var senderUser = request.user;
var recipientUserId = request.params.recipientId;
var message = request.params.message;
// Validate the message text.
// For example make sure it is under 140 characters
if (message.length > 140) {
// Truncate and add a ...
message = message.substring(0, 137) + "...";
}
// Send the push.
// Find devices associated with the recipient user
var recipientUser = new Parse.User();
recipientUser.id = recipientUserId;
var pushQuery = new Parse.Query(Parse.Installation);
pushQuery.equalTo("user", recipientUser);
pushQuery.find({
success: function(results) {
response.success("push user lookup was ok");
response.success(results);
},
error: function() {
response.error("push user lookup failed");
}
});
I could add the Parse.Push.send call to the success of the query. However the Parse.Push.send has a where clause and I do not know what is required there? I do not want to run the query twice.
You're on the right track. Push "advanced targeting" allows the app to push to installations resulting from a query. That's what the where clause is for...
// don't run find on the pushQuery. set it up as you have it
// then, assuming it returns some installation(s)...
Parse.Push.send({ where: pushQuery, data: "hello" }).then(function(result) {
response.success(result);
}, function(error) {
response.error(error);
});
Incidentally, you can use createWithoutData on Parse.User as a shortcut ...
var recipient = Parse.User.createWithoutData(request.params.recipientId);
but the longer form you have should work, too.
It seems like you may be overthinking this. There's no harm in sending a push notification to 0 installations as the push query will not match any recipients. I wouldn't worry too much about this and I wouldn't add such a pre-check to your code. It would add an unnecessary delay to your code and of course would result in the query being run twice.
If you want to do this anyway -- maybe you wish to keep your push logs free of clutter - you can indeed query over the Installation class to check if the query would have matched a set of installations, and if it does, you can then pass that same query to Parse.Push.send().
Yes, that will result in the query run twice, but that's to be expected, as you can't know how many objects will be matched without running the query.

Returning multiple Parse classes in one PFQuery?

I currently have my parse classes set up as follows
User - objectid, usename, password, location
Profile - birthday, weight, height, et....
Settings - user app preferences such as "Show my location"
Both profile and settings have a pointer to the user objectid called "user"
Is there anyway I can call a query knowing the Users.objectid that returns both Profile and Settings?
I have played around with includes key and matches query but only get back empty results.
If it isn't possible is there a way to execute a function once both queries have completed? (using getFirstObjectInBackgroundWithBlock)
Any help would greatly be appreciated.
No, but you could hide the combination in a function....
function profileAndSettingsForUser(user) {
var profiles;
var profileQuery = new Parse.Query("Profile");
profileQuery.equalTo("user", user);
return profileQuery.find().then(function(result) {
profiles = result;
settingsQuery = new Parse.Query("Settings");
settingsQuery.equalTo("user", user);
return settingsQuery.find();
}).then(function(settings) {
return profiles.concat(settings);
});
};
You could even locate that function in the cloud, so to hide the combination from the client.
Parse.Cloud.define("profileAndSettingsForUser", function(request, response) {
// we could pass a userId in params, then start by querying for that user
// or, if we know its always the current user who's calling for his own profile and settings...
var user = request.user;
profileAndSettingsForUser(user).then(function(profileAndSettings) {
response.success(profileAndSettings);
}, function(error) {
response.error(error);
});
});

Parse iOS SDK - Login using Mobile phone verification code like Watsapp

How to implement login mechanism with mobile verification code.
SignUp (New User with New Mobile Number)
I can able to do this for signup user by generating a random password after verifying code send to his mobile number.
Login (Existing User with Mobile Number)
But don't knows how to implement this. I cant use changepassword method because it works only for an already logged in user.
Setting the Current User
Saw this Method in Parse Documentation. Can I use this method. If yes, how can I get session token.
[PFUser becomeInBackground:#"session-token-here" block:^(PFUser *user, NSError *error) {
if (error) {
// The token could not be validated.
} else {
// The current user is now set to user.
}
}];
Successfully changed the password without login calling cloud code from ios and then logged in with a new password.
iOS Code
[PFCloud callFunctionInBackground:#"assignPasswordToUser" withParameters:#{#"username":[self generateUsername],#"password":loginModel.verficationCode} block:^(id object, NSError *error) {
if(!error)
{
NSLog(#"Assign New Password Success");
[self doLogin];
}else{
NSLog(#"Assign New Password Failed");
[self handError:error];
}
}];
Cloud Code
Parse.Cloud.define("assignPasswordToUser", function(request, response){
Parse.Cloud.useMasterKey();
var query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username);
query.first({
success: function(theUser){
var newPassword = request.params.password;
console.log("New Password: " + newPassword);
console.log("set: " + theUser.set("password", newPassword));
theUser.save(null,{
success: function(theUser){
// The user was saved correctly
response.success(1);
},
error: function(SMLogin, error){
response.error("save failure");
}
});
},
error: function(error){
response.error("error");
}
});
});
Assuming your wanted to allow something like registering a new device by scanning a QR Code or something shown on an existing device, you could do that without having to change the password as follows:
User class extra properties:
loginValidation: String
loginValidationExpiry: DateTime
You would use something like a 1 or 2 minute expiry to make things safer, making sure you create the Date using server-time in a Cloud Function. You could generate a guid/uuid for the code and create the QR code on an existing authenticated platform.
On the new device after reading the QR Code you could call this cloud function:
Parse.Cloud.define("validateLoginCode", function(request, response) {
Parse.Could.useMasterKey();
var username = request.params.username;
var validationCode = request.params.validationCode;
var query = new Parse.Query(Parse.User);
query.equalTo("username", username);
query.equalTo("loginValidation", validationCode);
query.first({
success: function(user) {
if (user) {
var expiry = user.get("loginValidationExpiry");
var now = new Date();
if (expiry > now) {
// code is valid, get token, only valid because
// we got user with master key
response.success({ token: user.getSessionToken() });
} else {
response.error("code expired");
}
} else {
response.error("invalid user/code");
}
},
error: function(error) {
response.error("error");
}
});
});
You could now use becomeInBackground:block: method in your calling code.

Resources