Skip to content
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

Prevent activation of previous workspace when launching Connect via deep link to a different cluster #50063

Merged
merged 23 commits into from
Dec 31, 2024

Conversation

gzdunek
Copy link
Contributor

@gzdunek gzdunek commented Dec 11, 2024

Closes #40962

Problem

Connect always tries to restore the previous workspace, even when it is run via a deep link. This is incorrect behavior- if the user wants to authorize a session for cluster A, we shouldn't force him to log in to a cluster B first.

Solution

Currently, restoring the workspaces from disk also actives the previously active workspace (this includes showing a login dialog if certs expired). Opening a deep link waits for that state to be restored, so it also waits for that dialog to be completed.
We don't want this, so we need to decouple workspace restoration from activating the previously active workspace. This will allow us to signal the frontend interface readiness before the login dialog finishes. When the frontend is ready, a deep link can be processed to activate its corresponding workspace.
Our modal service allows only one regular dialog is active at any time, so opening a new dialog will automatically cancel an existing one.

In other words:

  1. The user has workspaces A and B, with workspace B previously active.
  2. The user launches a deep link to cluster A.
  3. The app restores the state (workspaces A and B) and starts an attempt to set workspace B as active. This attempt does not block the app's initialization.
  4. Since the app is initialized, the deep link is processed, immediately replacing the dialog for activating workspace B with one for activating workspace A. This transition happens so quickly that the user doesn’t see the initial dialog.

While working on this fix, I cleaned up some code in workspacesService and documentsService:

  • setActiveWorkspace had a mix of async/await and .then, it has been rewritten to the newer syntax.
  • I moved some methods that do not need state outside the services.
  • I got rid of previous field in the workspace state. Instead, I store the entire restored state in a separate variable. I needed this, because if we don't restore the previous rootClusterUri immediately, it is overwritten with undefined in setState. I think this approach is actually simpler than what we had before.

@gzdunek gzdunek added no-changelog Indicates that a PR does not require a changelog entry backport/branch/v16 backport/branch/v17 labels Dec 11, 2024
@gzdunek gzdunek requested review from ravicious and avatus December 11, 2024 16:01
@github-actions github-actions bot requested a review from rudream December 11, 2024 16:01
@gzdunek gzdunek removed the request for review from rudream December 11, 2024 16:02
Copy link
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

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

So far I only looked through the code, I'll try to test this on Monday.

Copy link
Contributor

@avatus avatus left a comment

Choose a reason for hiding this comment

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

I did a scan of the code but tested locally and it works. Leaving premp approval because I'm out next week

@ravicious ravicious self-requested a review December 13, 2024 15:36
await this.clustersService.syncRootClustersAndCatchErrors();
this.workspacesService.restorePersistedState();
Copy link
Member

Choose a reason for hiding this comment

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

If I close the app while I'm in workspace A with a valid cert and open it again through a deep link to workspace B, I can close the login modal and continue to use workspace A without making a decision wrt restored state. The user should be forced to make an action before interacting with a workspace.

valid-cert-on-launch.mov

How do we fix this? I almost feel like DeepLinksService should always bail out to the "Connect" screen. As in, when loginAndSetActiveWorkspace is called, we check if the current workspace is equal to the workspace from the URL. If not, then we always zero rootClusterUri in WorkspacesService to force the user back to cluster choice. Otherwise, I feel it kind of makes little sense to launch the app through a link to workspace B, interact with a modal about workspace B (the login modal), then close it and be back at workspace A, just because it was the last used workspace before the app was closed.

I'm just not sure at this point how this is going to interact with the rest of the app.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At first it seemed like a good idea, but then I realized that setting rootClusterUri to undefined unmounts the workspaces, so all the state kept in tabs will be lost :/ And since a deep link can be opened while you work on something, you would loose it.

Because of that, I decided to keep it as is.

Copy link
Member

Choose a reason for hiding this comment

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

At first it seemed like a good idea, but then I realized that setting rootClusterUri to undefined unmounts the workspaces, so all the state kept in tabs will be lost :/

That sounds like a behavior that we can change. But I'll look at the changes in this PR and the other PR first.

// the next time you would navigate to the workspace.
// TODO(gzdunek): Consider replacing the document reopen dialog with an action in the UI that
// could reopen the previous session at any time.
if (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not too happy with this check but it was the only simple workaround that came to my mind.

Copy link
Member

Choose a reason for hiding this comment

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

I feel like allowing this situation to happen (where documents-reopen gets closed by cluster-login to another workspace which is then closed) makes reasoning about previousDocuments/currentDocuments quite difficult. It adds another state those two can be in and the situation in which this can happen is in itself quite unusual.

We'd remove a lot of complexity if we didn't let that happen, but I don't think it makes sense to stack even more changes in this PR.

If it's not possible to set rootClusterUri to undefined for reasons described in #50063 (comment), then perhaps another solution would be to make the deep link handler re-execute setActiveWorkspace with the workspace that was active at the time when the deep link handler was called.

Just like with many other changes regarding the startup flow, I'm not 100% sure if it won't break anything else, but it seems to make sense. 🙃

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I feel like allowing this situation to happen (where documents-reopen gets closed by cluster-login to another workspace which is then closed) makes reasoning about previousDocuments/currentDocuments quite difficult. It adds another state those two can be in and the situation in which this can happen is in itself quite unusual.

Agree, the startup flow became even more complex with changes from this PR :/

If it's not possible to set rootClusterUri to undefined for reasons described in #50063 (comment), then perhaps another solution would be to make the deep link handler re-execute setActiveWorkspace with the workspace that was active at the time when the deep link handler was called.

I feel that rendering workspaces when rootClusterUri is undefined may require many changes and testing, and I'm afraid that I will spend too much time on it.

Re-executing setActiveWorkspace when we didn't manage to set the correct workspace feels really simple, and should solve the problem the problem with this additional check, since we will always end up setting a workspace.
I can push a one more commit here that will fix it.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah definitely, I agree.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I realized that the logic around documentsRestoredOrDiscarded was broken from the beginning, so that I need that additional check.
We can't verify if we have documents to reopen every time we change the workspace - it must happen at the moment we restore previous workspaces. Otherwise, you can start a session with the default document (so documentsRestoredOrDiscarded: false) and then open a new one. Then you change a workspace, and come back. In setActiveWorkspace we would check if you have something to restore and this condition:

!arrayObjectIsEqual(
    omitUriAndTitle(previousDocuments),
    omitUriAndTitle(currentDocuments)
  )

would say yes.

I just fixed this, I promise it was the last change in this PR!

@gzdunek gzdunek requested a review from ravicious December 17, 2024 15:40
@ravicious ravicious removed their request for review December 18, 2024 10:16
@gzdunek
Copy link
Contributor Author

gzdunek commented Dec 18, 2024

Yesterday I pushed here a few commits for the review comments, and two fixes for bugs that I noticed:

Changes that we discussed in DMs today are in a separate PR #50384.

Copy link
Member

@ravicious ravicious left a comment

Choose a reason for hiding this comment

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

I think we got to a point where this should get merged despite some scenarios not being handled in the perfect way. But let me look at the other PR first.

// the next time you would navigate to the workspace.
// TODO(gzdunek): Consider replacing the document reopen dialog with an action in the UI that
// could reopen the previous session at any time.
if (
Copy link
Member

Choose a reason for hiding this comment

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

I feel like allowing this situation to happen (where documents-reopen gets closed by cluster-login to another workspace which is then closed) makes reasoning about previousDocuments/currentDocuments quite difficult. It adds another state those two can be in and the situation in which this can happen is in itself quite unusual.

We'd remove a lot of complexity if we didn't let that happen, but I don't think it makes sense to stack even more changes in this PR.

If it's not possible to set rootClusterUri to undefined for reasons described in #50063 (comment), then perhaps another solution would be to make the deep link handler re-execute setActiveWorkspace with the workspace that was active at the time when the deep link handler was called.

Just like with many other changes regarding the startup flow, I'm not 100% sure if it won't break anything else, but it seems to make sense. 🙃

@gzdunek gzdunek requested a review from ravicious December 20, 2024 15:52
gzdunek and others added 3 commits December 30, 2024 16:47
* Allow passing abortSignal to `open*Modal` methods, allow closing everything at once

* Close all dialogs when activating a deep link

* Allow only one `setActiveWorkspace` execution at a time

* Review comments

* Always unregister event listeners when a dialog is closed

* Lint

* Add a diagram for launching a deep link

* Remove checking if the signal is aborted in `openImportantDialog`, rename it
@gzdunek gzdunek enabled auto-merge December 31, 2024 14:23
@gzdunek gzdunek added this pull request to the merge queue Dec 31, 2024
Merged via the queue into master with commit 8e9b004 Dec 31, 2024
41 checks passed
@gzdunek gzdunek deleted the gzdunek/device-trust-correct-workspace branch December 31, 2024 14:40
@public-teleport-github-review-bot

@gzdunek See the table below for backport results.

Branch Result
branch/v16 Failed
branch/v17 Failed

gzdunek added a commit that referenced this pull request Jan 3, 2025
…eep link to a different cluster (#50063)

* Refactor `setActiveWorkspace` to async/await

* Keep all the logic that restores a single workspace state in `getWorkspaceDefaultState`

* Separate restoring state from setting active workspace

* Allow the dialog to reopen documents to be closed without any decision

* Add a test that verifies if the correct workspace is activated

* Docs improvements

* Return early if there's no restoredWorkspace

* Fix logger name

* Improve test name

* Make restored state immutable

* Fix comment

* Do not wait for functions to be called in tests

* Add tests for discarding documents reopen dialog

* Move setting `isInitialized` to a separate method

* Remove restored workspace when logging out so that we won't try to restore it when logging in again

* Do not try to restore previous documents if the user already opened new ones

* Do not close dialog in test

* Improve `isInitialized` comment

* Call `setActiveWorkspace` again when we fail to change the workspace

* Fix the logic around restoring previous documents

* Try to reopen documents also after `cluster-connect` is canceled

* canRestoreDocuments -> hasDocumentsToReopen

* Disallow parallel `setActiveWorkspace` calls (#50384)

* Allow passing abortSignal to `open*Modal` methods, allow closing everything at once

* Close all dialogs when activating a deep link

* Allow only one `setActiveWorkspace` execution at a time

* Review comments

* Always unregister event listeners when a dialog is closed

* Lint

* Add a diagram for launching a deep link

* Remove checking if the signal is aborted in `openImportantDialog`, rename it

(cherry picked from commit 8e9b004)
gzdunek added a commit that referenced this pull request Jan 3, 2025
…eep link to a different cluster (#50063)

* Refactor `setActiveWorkspace` to async/await

* Keep all the logic that restores a single workspace state in `getWorkspaceDefaultState`

* Separate restoring state from setting active workspace

* Allow the dialog to reopen documents to be closed without any decision

* Add a test that verifies if the correct workspace is activated

* Docs improvements

* Return early if there's no restoredWorkspace

* Fix logger name

* Improve test name

* Make restored state immutable

* Fix comment

* Do not wait for functions to be called in tests

* Add tests for discarding documents reopen dialog

* Move setting `isInitialized` to a separate method

* Remove restored workspace when logging out so that we won't try to restore it when logging in again

* Do not try to restore previous documents if the user already opened new ones

* Do not close dialog in test

* Improve `isInitialized` comment

* Call `setActiveWorkspace` again when we fail to change the workspace

* Fix the logic around restoring previous documents

* Try to reopen documents also after `cluster-connect` is canceled

* canRestoreDocuments -> hasDocumentsToReopen

* Disallow parallel `setActiveWorkspace` calls (#50384)

* Allow passing abortSignal to `open*Modal` methods, allow closing everything at once

* Close all dialogs when activating a deep link

* Allow only one `setActiveWorkspace` execution at a time

* Review comments

* Always unregister event listeners when a dialog is closed

* Lint

* Add a diagram for launching a deep link

* Remove checking if the signal is aborted in `openImportantDialog`, rename it

(cherry picked from commit 8e9b004)
github-merge-queue bot pushed a commit that referenced this pull request Jan 13, 2025
… via deep link to a different cluster (#50733)

* Prevent activation of previous workspace when launching Connect via deep link to a different cluster (#50063)

* Refactor `setActiveWorkspace` to async/await

* Keep all the logic that restores a single workspace state in `getWorkspaceDefaultState`

* Separate restoring state from setting active workspace

* Allow the dialog to reopen documents to be closed without any decision

* Add a test that verifies if the correct workspace is activated

* Docs improvements

* Return early if there's no restoredWorkspace

* Fix logger name

* Improve test name

* Make restored state immutable

* Fix comment

* Do not wait for functions to be called in tests

* Add tests for discarding documents reopen dialog

* Move setting `isInitialized` to a separate method

* Remove restored workspace when logging out so that we won't try to restore it when logging in again

* Do not try to restore previous documents if the user already opened new ones

* Do not close dialog in test

* Improve `isInitialized` comment

* Call `setActiveWorkspace` again when we fail to change the workspace

* Fix the logic around restoring previous documents

* Try to reopen documents also after `cluster-connect` is canceled

* canRestoreDocuments -> hasDocumentsToReopen

* Disallow parallel `setActiveWorkspace` calls (#50384)

* Allow passing abortSignal to `open*Modal` methods, allow closing everything at once

* Close all dialogs when activating a deep link

* Allow only one `setActiveWorkspace` execution at a time

* Review comments

* Always unregister event listeners when a dialog is closed

* Lint

* Add a diagram for launching a deep link

* Remove checking if the signal is aborted in `openImportantDialog`, rename it

(cherry picked from commit 8e9b004)

* Fix tests
github-merge-queue bot pushed a commit that referenced this pull request Jan 13, 2025
…eep link to a different cluster (#50063) (#50732)

* Refactor `setActiveWorkspace` to async/await

* Keep all the logic that restores a single workspace state in `getWorkspaceDefaultState`

* Separate restoring state from setting active workspace

* Allow the dialog to reopen documents to be closed without any decision

* Add a test that verifies if the correct workspace is activated

* Docs improvements

* Return early if there's no restoredWorkspace

* Fix logger name

* Improve test name

* Make restored state immutable

* Fix comment

* Do not wait for functions to be called in tests

* Add tests for discarding documents reopen dialog

* Move setting `isInitialized` to a separate method

* Remove restored workspace when logging out so that we won't try to restore it when logging in again

* Do not try to restore previous documents if the user already opened new ones

* Do not close dialog in test

* Improve `isInitialized` comment

* Call `setActiveWorkspace` again when we fail to change the workspace

* Fix the logic around restoring previous documents

* Try to reopen documents also after `cluster-connect` is canceled

* canRestoreDocuments -> hasDocumentsToReopen

* Disallow parallel `setActiveWorkspace` calls (#50384)

* Allow passing abortSignal to `open*Modal` methods, allow closing everything at once

* Close all dialogs when activating a deep link

* Allow only one `setActiveWorkspace` execution at a time

* Review comments

* Always unregister event listeners when a dialog is closed

* Lint

* Add a diagram for launching a deep link

* Remove checking if the signal is aborted in `openImportantDialog`, rename it

(cherry picked from commit 8e9b004)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backport/branch/v16 backport/branch/v17 no-changelog Indicates that a PR does not require a changelog entry size/md ui
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Connect tries to login to previous session instead of logging in to the DeviceWebToken's session
3 participants