Fix loss of cipher data when using pass generator #1935
Merged
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Objective
Note: this may be a candidate for
rc
Fix #1927. The latest update broke the password generator when accessed via the add-edit component. Maybe 80%, when returning from the password generator to the add-edit component, it would blank all the fields - so you would lose anything you entered, including the password you just generated. The other 20% of the time it would work as expected.
This mainly affected Firefox for me, I don't think I was able to reproduce it on Chrome.
Cause
There are 3 (relevant) trigger events for the Angular router to start navigation:
imperative
- e.g. callingrouter.navigate
popstate
- usually triggered by going forward or back through the browser history, e.g.location.back()
hashchange
- triggered when the hash component of the URL changes (only when using the hash navigation strategy, which we do)There appears to be a bug where:
location.back()
from the password generatorpopstate
event which navigates back to the add-edit pagestateService
to retrieve the cipher information (so far so good)popstate
event changes the URL hash, which triggershashchange
stateService
has been cleared, so it's treated like a brand new cipher and we lose our dataHowever, this is a race condition, which is why it doesn't trigger every time. Sometimes, the
hashchange
event will fire quick enough that it interrupts and aborts thepopstate
navigation. I can't see any clear code changes that introduced this bug, so I'm thinking it's a combination of Angular 11 upgrade + other changes that have thrown out the race condition.This has ostensibly already been reported and fixed here. However, that fix relies on the
id
of the two navigation events being identical, whereas in this case it's incremented by 1. This means the logic of that fix doesn't catch it. If others agree with this assessment, then I'm happy to try to do a minimal reproduction and open an issue.Code changes
As it is, we have to work around it for now. This PR adds
DebounceNavigationService
, which detects the duplicate navigation and will cancel the secondhashchange
navigation if (but only if) the firstpopstate
navigation has successfully completed.It can be used on any route where this behaviour is causing an issue. It also requires the
runGuardsAndResolvers: 'always'
option so that it will be run even though the route is already active.This has the advantage of being entirely local to browser and (hopefully) minimal risk of other regressions.
This double navigation occurs wherever
location.back()
is used. However, it doesn't cause any other issues that I can see, so I've left the other components alone even if they are technically affected.Other options
Don't like this? We could also:
onSameUrlNavigation: 'reload'
inapp-routing.module.ts
. That would revert to the default behaviour where you can't navigate to the same route twice. However, it might cause other regressions - I assume it's there for a reason.await this.stateService.remove('addEditCipherInfo')
on a 500ms timeout in the cipher add-edit component. That way, the state information would still be there for the duplicate navigation. However, this isn't really fixing the race condition, it's just trying to make it more predictable. This didn't feel like the right solution. However, if this is preferred, I did a quick mock-up and pushed it to remote branches calledfix-cipher-state
on browser and jslib.