Parse-SDK-iOS-OSX
Parse-SDK-iOS-OSX copied to clipboard
Current User is not kept in sync between App and Extension
New Issue Checklist
- [x] I am not disclosing a vulnerability.
- [x] I am not just asking a question.
- [x] I have searched through existing issues.
- [x] I can reproduce the issue with the latest versions of Parse Server and the Parse ObjC SDK.
Issue Description
An app extension such as a widget should be able to access the current logged in user in order to make queries. There are good instructions about configuring your app and extension to allow this. However, when the logged-in user changes in the app, the extension's version of this does not update accordingly. I also believe that a user change from the extension is similarly not reflected in the main app.
Steps to reproduce
Please refer to the attached example project, and the included Parse Widget Test Notes.txt
. The tests steps themselves are reproduced below:
Test 1
- Launch the widget. Note the timestamp. Observe that the current user is nil. Confirm that the user-count operation was successful. This just confirms you are connected to the server correctly.
- Tap the widget to open the app. Tap 'log in' and confirm that the current user is updated.
- Return to the widget. Confirm that the timestamp is updated, so the widget was refreshed after the user change.
Expected: the current logged-in user is displayed.
Actual: the current user is still nil.
Test 2 (continue from test 1)
- Relaunch the widget from Xcode. Confirm that the logged-in user is now correctly displayed.
- Tap the widget to open the app and log out. Confirmt hat the displayed user is nil.
- Return to the widget. Confirm that the timestamp is updated, so the widget was refreshed after the user change.
Expected: Current user should be nil.
Actual: The current user is still set to the previous value, and an error message is displayed. (The error is correct since the session token was revoked when the user logged out, only the widget's version of the current user didn't update.)
Thanks for opening this issue!
- ❌ Please edit your post and use the provided template when creating a new issue. This helps everyone to understand your post better and asks for essential information to quicker review the issue.
Have you looked at? https://github.com/parse-community/Parse-SDK-iOS-OSX/issues/1220
Thanks @cbaker6, yes I have seen that issue and it is not related unfortunately. In his instance, queries would fail every time because he missed a configuration step. In this issue, the configuration is correct and queries are actually successful. It is only when the user changes that the extension does not update and queries fail (since the token is now invalid). In other words, the main app's currentUser
and the extension's currentUser
return different things until the app is relaunched, at which time they are back in sync and queries work again.
For anyone else experiencing this issue, I don't really have a direct workaround, but for now I am avoiding relying the shared currentUser
in the extension. Instead, in the main app I am trying to keep track of every action that can cause a change to current user (log in and log out of course, various sign ups, etc) and manually save a copy of the session token to the keychain. Every time the extension needs to make a query, I check for this token. If it is present, I use PFUser.becomInBackground
to manually set the current user. If it is nil, I log out of the extension.
I have just encountered this issue as well. I believe it is caused by the fact that PFUser.current
is stored both in keychain and on the filesystem. When you logOut
in main app, the keychain is properly cleaned, and filesystem for the main app as well, but PFUser.current
remains intact in the memory of the extension, which is thus using stale data.
The extension needs a way to forcibly forget and reload the PFUser.current
from the keychain and filesystem. Keychain and filesystem are already shared between the main app and extension, so I believe the underlying issue is that there is simply stale data kept in extension memory... that is all...
still investigating how to workaround this with current SDK.
Investigation: Both PFUser.currentUser
and PFInstallation.currentInstallation
are loaded from disk (sessionToken from keychain) and kept in memory for the whole life of process (main or extension).
Because data and keychain are shared between the main app and extension, this is not a problem for cold app start or cold extension start, when Parse loads proper and fresh data from disk/keychain.
But when currentUser
or currentInstallation
object changes in the main app, as a result of sign in, sign out, sign up, there is no way for extension to know it, unless you tell it somehow. How to actually tell the extension that current user has changed in the main app depends on what works for you. Shared User Defaults flag may work nicely.
I have prototyped a solution that works for me, but may not be ideal. In my extensions I simply forget the current user whenever the extension is called, and force a reload. Since extensions have little time to waste, this was the quickest method I came up with. Another more generic solution may be comparing the filesystem timestamp to see if reload is needed, which would then work both ways.
But for now, I have added a method PFUser.forgetCurrentUser
which simply clears the current user from memory, thus forcing any subsequent use of it to load it from disk/keychain again. Similar thing may be done for currentInstallation.
The code for now lives here: https://github.com/mman/Parse-SDK-iOS-OSX/commit/dc3307eb892d6f83cdc8191f21bb0c58b17d1a5e
ideas? Martin
PFUser.currentUser and PFInstallation.currentInstallation are loaded from disk (sessionToken from keychain)
Maybe a solution would be to store the current user and installation in keychain like the session token, then have both the app and extension access it? If the session token is already stored there, the necessary basic keychain logic may already be there? We may just need to add a simple migration mechanism that migrates from file to keychain.
PFUser.currentUser and PFInstallation.currentInstallation are loaded from disk (sessionToken from keychain)
Maybe a solution would be to store the current user and installation in keychain like the session token, then have both the app and extension access it? If the session token is already stored there, the necessary basic keychain logic may already be there? We may just need to add a simple migration mechanism that migrates from file to keychain.
It does not matter much where the user or installation or session token are stored. What matters is when they should be reloaded and how to detect/communicate that from the main app to the extension or the other way. The key issue here is that when you sign out in the main app, the extension still has a current user and session token in memory and will use it until it realizes that session token is actually invalid or current user refresh fails.
The logic to get current user / current installation at the moment simply returns the object that is in memory, and if it is not there, it will load it. What I have done is just added a method to “forget” the object so that it is refetched forcibly, proper systematic fix will somehow check the validity of the in memory object against the permanent storage (disk or keychain) and will reload if needed…
Keychain may provide advantages. We could always read the keychain value (instead of occasionally loading it from file) and synch the keychain across app and extension, see the Apple docs. So whenever the extension queries the user, it directly uses the keychain value which is always in-sync with the app that may have modified its value.
@mtrezza Yeah, always storing/fetching from either keychain or disk could be a solution. The sessionToken
being sensitive and unique to everything makes sense to store in keychain (we do that already). currentUser
as an object may be stored there as well I assume, since it is always shared across all extensions.
However for example currentInstallation
currently represents the iOS app and stores deviceToken
for push. Live Activity runs in an extension and can haver its own deviceToken
for push. Watch app extension can run independently from the main app and can obtain their own unique deviceToken
, so live activity extension and watch app extension nowadays kind of behave like separate Installation
objects, so sharing one main Installation
object via keychain probably does not make sense.
Another option I was thinking about was to store push tokens for watch app and live activity as separate fields in Installation
object, but that way there is no easy way to target those in existing push adapter infra, and I think it will be rather hacky.
So not an easy way forward as far as I can see, because we probably want to unify the support for shared currentUser
and separate currentInstallation
for main app and all its potential extensions. And all this while supporting existing App Groups config where the app and extensions essentially write to the same directory. Thinking out loud we probably could include extension type or bundle identifier or something unique in a dir path where to store everything... not sure what is the best