Skip to content

Conversation

@cbaker6
Copy link
Contributor

@cbaker6 cbaker6 commented Apr 2, 2021

Automatically create a new installation after it's deleted from the Keychain. A deletion typically occurs on user logout. Fix #110

  • add changelog entry
  • add tests

@codecov
Copy link

codecov bot commented Apr 2, 2021

Codecov Report

Merging #112 (21729f4) into main (7024222) will increase coverage by 0.01%.
The diff coverage is 100.00%.

❗ Current head 21729f4 differs from pull request most recent head dcbc43d. Consider uploading reports for the commit dcbc43d to get more accurate results
Impacted file tree graph

@@            Coverage Diff             @@
##             main     #112      +/-   ##
==========================================
+ Coverage   81.03%   81.04%   +0.01%     
==========================================
  Files          65       65              
  Lines        5479     5482       +3     
==========================================
+ Hits         4440     4443       +3     
  Misses       1039     1039              
Impacted Files Coverage Δ
Sources/ParseSwift/Objects/ParseInstallation.swift 83.23% <100.00%> (+0.09%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 7024222...dcbc43d. Read the comment docs.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Apr 2, 2021

@funkenstrahlen can you try out this branch and see if you run into the same issue?

The testcases pass and new installationIds are created

@cbaker6 cbaker6 changed the title Installation Recreate new installation after deletion from keychain Apr 2, 2021
#if !os(Linux) && !os(Android)
if let installationFromKeychain: CurrentInstallationContainer<BaseParseInstallation>
= try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) {
if installationFromKeychain.installationId == oldInstallationId {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about extending the test case here and also checking if installationFromKeychain.currentInstallation != nil?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may not always be the case by the time the test gets here because of the dispatching to main queue. If it is nil when it gets here, it won't be in a couple of seconds

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I haven't thought about the dispatch. The additional test case is not required. Has just been a thought as it's not checked right now.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#113 should provide more predictable behavior. Let me know your thoughts on that thread

DispatchQueue.main.async {
if let installationFromMemory: CurrentInstallationContainer<BaseParseInstallation>
= try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) {
if installationFromMemory.installationId == oldInstallationId {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about extending the test case here and also checking if installationFromKeychain.currentInstallation != nil?

}
DispatchQueue.main.async {
if let installationFromKeychain = BaseParseInstallation.current {
if installationFromKeychain.installationId == oldInstallationId {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about extending the test case here and also checking if installationFromKeychain.currentInstallation != nil?

#if !os(Linux) && !os(Android)
if let installationFromKeychain: CurrentInstallationContainer<BaseParseInstallation>
= try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) {
if installationFromKeychain.installationId == oldInstallationId {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about extending the test case here and also checking if installationFromKeychain.currentInstallation != nil?

DispatchQueue.main.async {
if let installationFromMemory: CurrentInstallationContainer<BaseParseInstallation>
= try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) {
if installationFromMemory.installationId == oldInstallationId {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What do you think about extending the test case here and also checking if installationFromKeychain.currentInstallation != nil?

@funkenstrahlen
Copy link

Thank you for quick response and the PR! I added some suggestions above to have a more detailed test suite.

I added this branch to my project and can confirm it works a lot better now! Thank you!

However I still need to manually save the newly created installation to the server after logout because every subsequent ParseInstallation.fetch fails with ParseError code=-1 error=Cannot fetch an object without id before it has been saved to the server once.

    func logout() {
        PodliveUser.logout { (result) in
            switch result {
            case .failure(let error):
                print("Could not logout: " + error.localizedDescription)
            case .success():
                print("successfully logged out")
                NotificationCenter.default.post(name: Notification.Name.userUpdated, object: nil)
                PodliveInstallation.current?.save(completion: { (result) in
                    switch result {
                    case .success(_):
                        self.loginAsAnonymousUser()
                    case .failure(let error):
                        print("Could not save installation: " + error.localizedDescription)
                    }
                })
            }
        }
    }

Do you think saving the installation to the server should be done by the framework after logout?

@cbaker6
Copy link
Contributor Author

cbaker6 commented Apr 2, 2021

Do you think saving the installation to the server should be done by the framework after logout?

I believe this comment may answer this question: #93 (comment)

@cbaker6
Copy link
Contributor Author

cbaker6 commented Apr 2, 2021

Thank you for quick response and the PR! I added some suggestions above to have a more detailed test suite.

I added this branch to my project and can confirm it works a lot better now! Thank you!

However I still need to manually save the newly created installation to the server after logout because every subsequent ParseInstallation.fetch fails with ParseError code=-1 error=Cannot fetch an object without id before it has been saved to the server once.

    func logout() {
        PodliveUser.logout { (result) in
            switch result {
            case .failure(let error):
                print("Could not logout: " + error.localizedDescription)
            case .success():
                print("successfully logged out")
                NotificationCenter.default.post(name: Notification.Name.userUpdated, object: nil)
                PodliveInstallation.current?.save(completion: { (result) in
                    switch result {
                    case .success(_):
                        self.loginAsAnonymousUser()
                    case .failure(let error):
                        print("Could not save installation: " + error.localizedDescription)
                    }
                })
            }
        }
    }

Do you think saving the installation to the server should be done by the framework after logout?

Since you logout/login immediately, I can see the possibility of odd behavior because of the need for Installation to be on the main queue. There can be times your anonymous login occurs before the new installation is created. You can minimize this odd behavior by logging in anonymously after a short time, say a second (dispatch after a second). The changes discussed in #113 should help with this also and you might not need to wait anytime after to log back in, but with the current implementation, you should wait a second or ask the user to press a button to login

@cbaker6 cbaker6 merged commit 3852a93 into main Apr 2, 2021
@cbaker6 cbaker6 deleted the installation branch April 2, 2021 12:42
@funkenstrahlen
Copy link

Do you think saving the installation to the server should be done by the framework after logout?

I believe this comment may answer this question: #93 (comment)

Thank you for linking the related issue thread. Actually I do have custom properties in my Installation object and therefore need to retrieve it manually. However I think you are absolutely correct that fetching the Installation at this point should not be part of the framework.

@funkenstrahlen
Copy link

Since you logout/login immediately, I can see the possibility of odd behavior because of the need for Installation to be on the main queue. There can be times your anonymous login occurs before the new installation is created. You can minimize this odd behavior by logging in anonymously after a short time, say a second (dispatch after a second).

That's correct. Great you thought about that! I think implementing #113 is the best approach to create a more predictable behavior.

@cbaker6
Copy link
Contributor Author

cbaker6 commented Apr 2, 2021

That's correct. Great you thought about that! I think implementing #113 is the best approach to create a more predictable behavior.

Even with the changes, you should probably implement one of my suggestions I mentioned above. You can see that because of threading the new installation may not be ready immediately in the keychain:

DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
if let installationFromMemory: CurrentInstallationContainer<BaseParseInstallation>
= try? ParseStorage.shared.get(valueFor: ParseStorage.Keys.currentInstallation) {
if installationFromMemory.installationId == oldInstallationId
|| installationFromMemory.installationId == nil {
XCTFail("\(installationFromMemory) wasn't deleted and recreated in memory during logout")
}
} else {
XCTFail("Should have a new installation")
}
#if !os(Linux) && !os(Android)
if let installationFromKeychain: CurrentInstallationContainer<BaseParseInstallation>
= try? KeychainStore.shared.get(valueFor: ParseStorage.Keys.currentInstallation) {
if installationFromKeychain.installationId == oldInstallationId
|| installationFromKeychain.installationId == nil {
XCTFail("\(installationFromKeychain) wasn't deleted & recreated in Keychain during logout")
}
} else {
XCTFail("Should have a new installation")
}
#endif
expectation1.fulfill()
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ParseInstallation.current is always nil after ParseUser.logout

3 participants