New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add mutex for app token refresh #22457
base: main
Are you sure you want to change the base?
Conversation
🦋 Changeset detectedLatest commit: 333ec52 The changes in this PR will be included in the next version bump. This PR includes changesets to release 3 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
…acquiring for localstorage fallback
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 🎸
I've still been able to provoke an invalid token error when using the localStorage
fallback implementation, that was however only one time, after triggering ~100 refreshes very quickly by pulling a tab in an out of the Chrome browser. So I would assume this is super unlikely to be an issue in peoples setup.
Co-authored-by: Hannes Küttner <[email protected]>
const internalKey = `directus-mutex-${key}`; | ||
const useWebLock = !!navigator.locks; | ||
|
||
async function acquireMutex(callback: (lock?: Lock | null) => Promise<any>): Promise<any> { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need a timeout if the lock should not be released for some reason, either here in useMutex
(use expiresMs
?) or on caller side
https://developer.mozilla.org/en-US/docs/Web/API/LockManager/request#signal_example
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I initially added an abort controller but in my tests it was causing more issues than it was solving. The issue i was seeing is that the "timeout to abort" is dependent on how many tabs you have open, if we set a timeout of 5 seconds and the first tab takes 2.5 seconds to refresh then 2 tabs will refresh and any beyond that will logout having passed the 5second timeout, ending up in the error state but is recoverable if you refresh on the login screen (that however is not intuitive i think). I have attempted to create a situation where the lock was not released but could only manage to do by adding a non-resolving promise in the code, the lock is released when the refresh request errors, times out or finishes.
For this reason and because I was able to confirm that a page refresh clears active locks (in the weblocks API) I think simply letting this time out will be a better user experience in the end.
Co-authored-by: ian <[email protected]>
@paescuj mind having a last look with the feedback resolved? |
48cddec
to
333ec52
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, I had started to be logged out very easily with the navigator.locks
method. I was testing with 4 open tabs with concurrent fresh restarts from HMR by saving auth.ts
.
I've tried adding some sleep in the callback for acquireMutex()
, and it seems like there's some race condition elsewhere from a fresh full reload.
Lastly, merged PRs have been updated into this PR, which might be the reason for the behavioural change.
🔍🔍🔍
I can definitely see this being the reason for the log-outs. Good call! Another possible solution could be to, instead of storing the current authentication state and expiry duration in the app, to store in local (or session) storage instead. This would prevent each tab/window from even having to do a request to /auth/refresh on the first refresh. This could be set and checked in the locked part of the refresh logic and short-circuit if another tab populated the info. What do you think about that? I personally would not consider the auth state and the expiry time to be sensitive info, so storing it in session storage is fine by me, but that is mostly a gut feeling. |
@hanneskuettner Yes! I had the same thinking to use On the other hand, I think we would need to clear the session cookie only for auth routes instead of all routes from #22459. 🤔 Example:
|
Scope
What's changed:
expiresMs
is added in case of browser crashesPotential Risks / Drawbacks
Review Notes / Questions
Fixes #22360