I am attempting to make all my user sessions with Parse exclusive, meaning if a user is already logged in on a certain device in a certain location, if another device logs in with the same credentials, I want the previous session(s) to be terminated, with a message of an alert view of course. Sort of like the old AOL Instant Messaging format. I figured the code for this action should be written in the login logic, so I wrote this within my login "succession" code :
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if user != nil || error == nil {
dispatch_async(dispatch_get_main_queue()) {
self.performSegueWithIdentifier("loginSuccess", sender: self)
PFCloud.callFunctionInBackground("currentUser", withParameters: ["PFUser":"currentUser"])
//..... Get other currentUser session tokens and destroy them
}
} else {
Thats probably not the correct cloud code call, but you get the point. When the user logs in once again on another device, I want to grab the other sessions and terminate them. Does anyone know the correct way to go about making this request in swift?
I speak swift with a stutter, but I think I can answer adequately in almost-swift. The key idea is to start the success segue only after the cloud says it's okay. Here's what I think you want:
PFUser.logInWithUsernameInBackground(userName, password: passWord) {
(user, error: NSError?) -> Void in
if (user != nil) {
// don't do the segue until we know it's unique login
// pass no params to the cloud in swift (not sure if [] is the way to say that)
PFCloud.callFunctionInBackground("isLoginRedundant", withParameters: []) {
(response: AnyObject?, error: NSError?) -> Void in
let dictionary = response as! [String:Bool]
var isRedundant : Bool
isRedundant = dictionary["isRedundant"]!
if (isRedundant) {
// I think you can adequately undo everything about the login by logging out
PFUser.logOutInBackgroundWithBlock() { (error: NSError?) -> Void in
// update the UI to say, login rejected because you're logged in elsewhere
// maybe do a segue here?
}
} else {
// good login and non-redundant, do the segue
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
} else {
// login failed for typical reasons, update the UI
}
}
Please don't take me too seriously on swift syntax. The idea is to nest the segue in the completion handlers to know that you need to do it before starting it. Also, please note that the explicit placement on the main_queue within the completion handler is unnecessary. The SDK runs those blocks on the main.
A simple check to determine if a user's session is redundant (not unique) looks like this...
Parse.Cloud.define("isLoginRedundant", function(request, response) {
var sessionQuery = new Parse.Query(Parse.Session);
sessionQuery.equalTo("user", request.user);
sessionQuery.find().then(function(sessions) {
response.success( { isRedundant: sessions.length>1 } );
}, function(error) {
response.error(error);
});
});
Related
Updated question
I am trying to manually check if the user is has to be reauthenticated or not. This is what I've come up with:
//MARK: updateEmail
static func updateEmail(email: String, finished: #escaping (_ done: Bool, _ hasToReauthenticate: Bool) -> Void) {
let currentUser = Auth.auth().currentUser
currentUser?.updateEmail(to: email) { err in
if err != nil {
if let errCode = AuthErrorCode(rawValue: err!._code) {
switch errCode {
case .userTokenExpired:
print("expired")
finished(true, true)
break
default:
Utilities.showErrorPopUp(labelContent: "Fehler", description: err!.localizedDescription)
finished(false, false)
}
}
} else {
finished(true, false)
}
}
}
But this is never going through the .userTokenExpired case even when it should.. What am I missing here ?
There is no API in Firebase Authentication that returns when the user has last authenticated, or whether that was recently. The only built-in functionality is that Firebase automatically checks for recent authentication for certain sensitive operations, but that seems to be of no use to you here.
But since your application is making API calls when the user authenticates, you can also record the time when they do so, and then check whether that was recent enough for your use-case.
If you need to check if user is authenicated - is same as reauthenication. Firebase will do their work to do some lower levels like tokens, etc. We don't have to worry about it.
guard let currentUser = Auth.auth().currentUser else {
//authenicate the user.
}
if you want to update the email address in user, the logic should be
check if the user is not nil, then update the email address.
If it is nil, then log in (anonymous or regular workflow to sign in), then update the email address.
I use this similar logic to check if the user is signed in, then do something. Otherwise, sign in as anonymous, then do same something.
The issue was quite simple: I caught the wrong error:
The error I have to catch in my case is .requiresRecentLogin . With that, everything is working fine.
With the Parse's announcement of their retirement, I have migrated my Parse Server onto Heroku. With my still neophyte knowledge of Heroku, I do not know if they have a similar function to that of Cloud Code, but I do know that a few months ago Parse Introduced a Heroku + Parse feature that allows you to run Cloud Code on any node.js environment, particularly Heroku.
My dilemma is, I have already migrated my server from parse to Heroku prior to learning about this feature :/ , so I cannot run any parse cloud code form my terminal because there is no existing server there anymore. So the question is, how can I emulate this following Cloud Code in Heroku & How do I adjust my swift?
Cloud Code:
// Use Parse.Cloud.define to define as many cloud functions as you want.
// For example:
Parse.Cloud.define("isLoginRedundant", function(request, response) {
Parse.Cloud.useMasterKey();
var sessionQuery = new Parse.Query(Parse.Session);
sessionQuery.equalTo("user", request.user);
sessionQuery.find().then(function(sessions) {
response.success( { isRedundant: sessions.length>1 } );
}, function(error) {
response.error(error);
});
});
and here is my swift back in xcode:
PFUser.logInWithUsernameInBackground(userName!, password: passWord!) {
(user, error) -> Void in
if (user != nil) {
// don't do the segue until we know it's unique login
// pass no params to the cloud in swift (not sure if [] is the way to say that)
PFCloud.callFunctionInBackground("isLoginRedundant", withParameters: [:]) {
(response: AnyObject?, error: NSError?) -> Void in
let dictionary = response as! [String:Bool]
var isRedundant : Bool
isRedundant = dictionary["isRedundant"]!
if (isRedundant) {
// I think you can adequately undo everything about the login by logging out
PFUser.logOutInBackgroundWithBlock() { (error: NSError?) -> Void in
// update the UI to say, login rejected because you're logged in elsewhere
// maybe do a segue here?
let redundantSession: String = "you are already logged in on another device"
self.failedMessage(redundantSession)
self.activityIND.stopAnimating()
self.loginSecond.userInteractionEnabled = true
}
} else {
// good login and non-redundant, do the segue
self.performSegueWithIdentifier("loginSuccess", sender: self)
}
}
} else {
// login failed for typical reasons, update the UI
dispatch_async(dispatch_get_main_queue()) {
self.activityIND.stopAnimating()
self.loginSecond.userInteractionEnabled = true
if let message = error?.userInfo["error"] as? String
where message == "invalid login parameters" {
let localizedMessage = NSLocalizedString(message, comment: "Something isn't right, check the username and password fields and try again")
print(localizedMessage)
self.failedMessage(localizedMessage)
}else if let secondMessage = error?.userInfo["error"] as? String
where secondMessage == "The Internet connection appears to be offline." {
self.failedMessage(secondMessage)
}
}
}
}
I would first checkout the example repo and read the parse-server documentation. Parse server supports cloud code out of the box and you simply specify which file contains your functions and triggers in the parse-server config. The link you posted with the integration between parse and heroku is not relevant for parse-server.
In an effort to create the easiest user experience possible, I am on a mission to accept a user as an anonymous user using Parse + Swift. I had thought to use the Anonymous user functions in Parse to accomplish that. As a result, I created the following code:
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
// Override point for customization after application launch.
self.setupParse()
// self.setupAppAppearance()
This first section is to create a user and see if at this point in the process - I have a nil objectId (typically true for the user when first they attempt to open the application).
var player = PFUser.currentUser()
if player?.objectId == nil {
}
else
{
println(player!.objectId)
}
If I have an objectId (indicating that I've been down this road before and saved an anonymous user object) - throw that to the console so I can see what it is and check it in the Parse user object. Cool - good so far.
Next - Check to see if the Object is nil again - this time to decide whether or not to attempt to perform an anonymous login - there's not a thing to use to generate an anonymous user other than this anonymous login action.
if player?.objectId == nil {
PFAnonymousUtils.logInWithBlock({
(success, error) -> Void in
if (error != nil)
{
println("Anonymous login failed.")
}
else
{
println("Anonymous login succeeded.")
If anonymous Login succeeded (still considering doing a network available check before trying to run these bits...but assuming network is valid) save a Bool to "isAnonymous" on the server to make sure that we have identified this user as anonymous - I may want that information later, so it seemed worth throwing this action.
Question 1: Do I need to re-query PFUser.currentUser() (known as player) to make sure that I have a valid anon user object that is connected to the server, or will the player object that I allocated earlier recognize that I've logged in and thereby recognize that I can throw other info into the associated record online? I think this is working as is - but I've been getting weird session token errors:
[Error]: invalid session token (Code: 209, Version: 1.7.5)
player!["isAnonymous"] = true as AnyObject
player!.saveInBackgroundWithBlock {
(success, error) -> Void in
if (error != nil)
{
println("error updating user record with isAnonymous true")
}
else
{
println("successfully updated user record with isAnonymous true")
}
}
}
})
}
else
{
}
return true
}
func setupParse()
{
Parse.setApplicationId("dW1UugqmsKkQCoqlKR3hX8dISjvOuApcffGAWR2a", clientKey: "BtXxjTjBRZVnCZbJODhd3UBUU8zuoPU1HBckXh4t")
enableAutomaticUserCreateInParse()
This next bit is just about trying to figure out some way to deal with those token problems. No idea whether it's doing me any good at all or not. It said to turn this on right after instantiating the Parse connection.
PFUser.enableRevocableSessionInBackgroundWithBlock({
(error) -> Void in
if (error != nil) {
println(error?.localizedDescription)
}
})
Next - just throwing around objects because I keep struggling with being connected and storing stuff or not being connected or losing session tokens. So - til I get this worked out - I'm creating more test objects than I can shake a stick at.
var testObject = PFObject(className: "TestObject")
testObject["foo"] = "barnone"
testObject.saveInBackgroundWithBlock { (success: Bool, error: NSError?) -> Void in
println("Object has been saved.")
}
}
Question2: it appears to me that PFUser.enableAutomaticUser() while very handy - causes some headaches when trying to figure out whether I'm logged in/online/whatever. Anyone have any solid experience with this and able to guide me on how you'd check whether you were connected or not - I need to know that later to be able to decide whether to save more things to the user object or not.
func enableAutomaticUserCreateInParse() {
if PFUser.currentUser()?.objectId == nil
{
myHumanGamePlayer.playerDisplayName = "Anonymous Player"
PFUser.enableAutomaticUser()
}
}
Anyone out there who's an expert on using anonymous users in Parse with Swift, let's get in touch and post a tutorial - because this has cost me more hours than I'd like to think about.
Thank you!
Xylus
For player!["isAnonymous"] = true as AnyObject, don't save it as any object. Save it as a bool and look at your parse to see if it's a bool object. Try querying for current user in a different view controller and print to the command line. I hope this helped
Please help me to solve this problem - after a lot of (not so efficent...) search I can't do this alone.
I have the following methods:
showLoadingAnimation()
to show the loading animation while background tasks are running
hideLoadingAnimation()
to hide the loading animation as soon as all the background tasks are finished
getUserFacebookData()
to get Facebook-user data
uploadUserFacebookDataToServer()
to upload the Facebook-user data to the server (and perform tasks with them).
What I want to perform:
Show up the loading animation: showLoadingAnimation()
Get the user data from Facebook: getFacebookData()
Wait until these data are being downloaded
As soon as the Facebook-user data are being download, upload these data to the server: uploadUserFacebookDataToServer()
Wait untile these data are being uploaded
Hide the loading animation: hideLoadingAnimation()
Now my problem is, that I don't know how to solve this problem. I know, that I should use sync and/or async tasks, GCD... But I don't know how, and I can't find a proper guide to it.
Could someone explain it to me through these functions?
Thanks!
UPDATE:
Thank you, Zhi-Wei Cai, that was the kind of answer what I was hoping for.
Now it seems to work, the calling order is OK,
but now the problem is the same as the beginning:
uploadUserFacebookDataToServer()
doesn't wait until
getUserFacebookData
downloads the user data from Facebook, that's why it won't be able to work with the necessary data given back from
getUserFacebookData
Any idea? Is there anything to do with dispatch?
UPDATE 2:
As you requested, here are the fuctions. I hope, with these information you can help me to solve this problem and to understand this whole process.
func getFacebookUserData(completionHandler: () -> Void)
{
println("getFacebookUserData")
let graphRequest : FBSDKGraphRequest = FBSDKGraphRequest(graphPath: "me", parameters: nil)
graphRequest.startWithCompletionHandler({ (connection, result, error) -> Void in
if ((error) != nil)
{
// Process error
println("Error: \(error)")
}
else
{
let userID : NSString = result.valueForKey("id") as NSString!
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(userID, forKey: "settings_facebookID")
self.facebookID_fromSettings = userID
}
})
and
func getObjectIDfromFacebookID(completionHandler: () -> Void)
{
println("getObjectIDfromFacebookID")
var query = PFQuery(className:"users")
query.whereKey("facebookID", equalTo:facebookID_fromSettings)
println("getObjectIDfromFacebookID: facebookID: " + facebookID_fromSettings)
query.findObjectsInBackgroundWithBlock {
(objects: [AnyObject]?, error: NSError?) -> Void in
if error == nil {
// The find succeeded.
println("Successfully retrieved \(objects!.count) scores.")
// Do something with the found objects
if (objects!.count == 0) {
// New user, registering
println("New user, registering")
}
else
{
//User is already regsitered, reading out objectID
println("User is already regsitered, reading out objectID")
}
if let objects = objects as? [PFObject] {
for object in objects {
println("objectID: " + object.objectId)
var objectID: String = object.objectId
println(objectID)
var defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setObject(objectID, forKey: "settings_objectID")
}
}
}
}
completionHandler()
}
So the first function gets the facebookID from the FB-server, but this process takes time, it won't give a result immediately. The second function should work with this data, that's why it should "wait" until the first gives back the requested data.
I can solve this problem by building these 2 fuctions together in one, but that's "not elegant", and I also would like to use this (sync/async dispatch) method in other parts of the project,
Thanks for trying to help me!
You can use completion handlers:
func showLoadingAnimation() {
self.getUserFacebookData({ () -> Void in
self.uploadUserFacebookDataToServer({ () -> Void in
self.hideLoadingAnimation()
})
})
}
func getUserFacebookData(completionHandler: () -> Void) {
println("getUserFacebookData")
completionHandler()
}
func uploadUserFacebookDataToServer(completionHandler: () -> Void) {
println("uploadUserFacebookDataToServer")
completionHandler()
}
func hideLoadingAnimation() {
println("hideLoadingAnimation")
}
Once showLoadingAnimation() is called, the rest will be done asynchronously.
Reference: https://developer.apple.com/library/ios/featuredarticles/Short_Practical_Guide_Blocks/
[EDIT] Use dispatch_group_t
check following code
Step 1: showLoadingAnimation()
Step 2: dispatch_group_t faceBookService = dispatch_group_create();
Step 3:dispatch_group_async_enter(faceBookService,queue,^{
getUserFacebookData()
// The below given line should be inside completion handler or after the above task has finished
dispatch_group_leave(faceBookService);
});
Step 4:dispatch_group_async_enter(faceBookService,queue,^{
uploadUserFacebookDataToServer()
// The below given line should be inside completion handler or after the above task has finished
dispatch_group_leave(faceBookService);
});
Step 5:dispatch_group_notify(faceBookService,dispatch_get_main_queue(),^{
//Called after finishing both tasks.
hideLoadingAnimation()
});
I've tried updating the same way you would update a PFUser's email and even tried converting obj-c code (from other questions); neither worked. I also have no idea how to use Cloud Code (well...I installed it but I don't know how to pass information into Cloud Code or how to use JavaScript). Is there a way to update a users password without having to send the reset email?
You can not change a user's password that way for security reasons. You have two choices
Password Reset Email
Cloud Code Function to Reset the Password
As I understand that you do not know JavaScript, here is a cloud code function that you can use to reset the user's password, as well as a way to call the function using Swift.
Function (in JavaScript):
Parse.Cloud.define("changeUserPassword", function(request, response) {
// Set up to modify user data
Parse.Cloud.useMasterKey();
var query = new Parse.Query(Parse.User);
query.equalTo("username", request.params.username); // find all the women
query.first({
success: function(myUser) {
// Successfully retrieved the object.
myUser.set("password", request.params.newPassword);
myUser.save(null, {
success: function(myUser) {
// The user was saved successfully.
response.success("Successfully updated user.");
},
error: function(myUser, error) {
// The save failed.
// error is a Parse.Error with an error code and description.
response.error("Could not save changes to user.");
}
});
},
error: function(error) {
alert("Error: " + error.code + " " + error.message);
}
});
});
Swift code to call the above function:
PFCloud.callFunctionInBackground("changeUserPassword", withParameters: ["username" : "MyCoolUsername", "newPassword" : passwordField.text]) {
(result: AnyObject?, error: NSError?) -> Void in
if (error == nil) {
// result is "Successfully updated user."
}
}
Good luck!
Yes, password can be changed without Cloud Code and e-mail. After changing "password" field for current user session is reset, but you can restore it by calling PFUser.logInWithUsername again.
let currentUser = PFUser.current()
currentUser!.password = "<new_password>"
currentUser!.saveInBackground() { (successed, error) in
if successed {
PFUser.logInWithUsername(inBackground: currentUser!.email!, password: currentUser!.password!) { (user, error) in
// Your code here...
}
}
}