Skip to content

Conversation

@EhabY
Copy link
Collaborator

@EhabY EhabY commented Dec 9, 2025

This change centralizes deployment state management and enables seamless multi-deployment support. The extension now properly tracks per-deployment credentials, syncs state across VS Code windows, and handles login/logout flows in a unified way.

Key changes:

Architecture:

  • Add DeploymentManager to centralize deployment state (url, label, token, user) and coordinate client updates, auth contexts, and workspace refreshes
  • Add LoginCoordinator to handle login prompts with cross-window detection, preventing duplicate login dialogs when multiple windows need auth
  • Move deployment types to src/deployment/ module with proper type guards

Storage & Auth:

  • SecretsManager now stores per-deployment credentials using label-based keys (coder.session.<label>) instead of flat sessionToken storage
  • Add LRU tracking for deployments with automatic cleanup of old credentials
  • Add migration from legacy flat storage format
  • Cross-window sync via secrets.onDidChange events
  • Debug command (coder.debug.listDeployments) for inspecting stored state

Commands & Remote:

  • Commands now use DeploymentManager instead of directly manipulating client
  • Remote connection uses LoginCoordinator for auth prompts during workspace connections
  • Remove forceLogout in favor of unified logout through DeploymentManager
  • CliManager.configure() now called on every remote connection, with secrets storage as the source of truth for credentials

WebSocket improvements:

  • CoderApi now implements Disposable to clean up WebSocket connections
  • Add setCredentials() method to update host+token atomically, avoiding unnecessary reconnection cycles
  • Add suspend() support to ReconnectingWebSocket for clean disconnects
  • Simplify WebSocket fallback logic with cleaner SSE fallback handling

Tests:

  • Update secretsManager tests for new per-deployment API
  • Add comprehensive reconnectingWebSocket tests for suspend/reconnect
  • Extend coderApi tests for credential handling

@EhabY EhabY force-pushed the multi-deployment-squashed branch from ead6f43 to bc9bce5 Compare December 9, 2025 20:49
This change centralizes deployment state management and enables seamless
multi-deployment support. The extension now properly tracks per-deployment
credentials, syncs state across VS Code windows, and handles login/logout
flows in a unified way.

Key changes:

Architecture:
- Add DeploymentManager to centralize deployment state (url, label, token, user)
  and coordinate client updates, auth contexts, and workspace refreshes
- Add LoginCoordinator to handle login prompts with cross-window detection,
  preventing duplicate login dialogs when multiple windows need auth
- Move deployment types to src/deployment/ module with proper type guards

Storage & Auth:
- SecretsManager now stores per-deployment credentials using label-based keys
  (coder.session.<label>) instead of flat sessionToken storage
- Add LRU tracking for deployments with automatic cleanup of old credentials
- Add migration from legacy flat storage format
- Cross-window sync via secrets.onDidChange events
- Debug command (coder.debug.listDeployments) for inspecting stored state

Commands & Remote:
- Commands now use DeploymentManager instead of directly manipulating client
- Remote connection uses LoginCoordinator for auth prompts during workspace
  connections
- Remove forceLogout in favor of unified logout through DeploymentManager
- CliManager.configure() now called on every remote connection, with secrets
  storage as the source of truth for credentials

WebSocket improvements:
- CoderApi now implements Disposable to clean up WebSocket connections
- Add setCredentials() method to update host+token atomically, avoiding
  unnecessary reconnection cycles
- Add suspend() support to ReconnectingWebSocket for clean disconnects
- Simplify WebSocket fallback logic with cleaner SSE fallback handling

Tests:
- Update secretsManager tests for new per-deployment API
- Add comprehensive reconnectingWebSocket tests for suspend/reconnect
- Extend coderApi tests for credential handling
@EhabY EhabY force-pushed the multi-deployment-squashed branch from bc9bce5 to 642fd52 Compare December 10, 2025 13:34
Comment on lines +109 to +110
const hostChanged = (currentHost || "") !== (host || "");
const tokenChanged = currentToken !== token;
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Continuation of #633 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

However, if we authenticate without a token (mTLS) then we might begin with the following: { host: XYZ, token: undefined }, but when we authenticate we change the token to "" and thus trigger a reconnect.

The input is the same since undefined might be treated the same as "" except that we might have started with invalid configuration (401s) and then changed the setting and prompted a login again that worked so we trigger this reconnect with the new info.

But why should this trigger a reconnect? Nothing changes from the Coder server's perspective, it has no distinction between a blank token and a missing token. If a missing token was a 401, so will a blank token, so why reconnect when we know it will fail? Or is that not the case?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Here's my thought process:

Let's assume that we authenticate without a token (mTLS) but we are pointing to the wrong files or there's an issue with the tlsCertFile/tlsKeyFile. Let's assume that we then trigger a login event that actually works, so we set the token to "" and thus trigger a reconnect. It's not necessarily about the empty vs. undefined but more about triggering something when we successfully login.

Copy link
Member

@code-asher code-asher Dec 12, 2025

Choose a reason for hiding this comment

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

Ahhhhh ok I think I understand, so embedded in the token is an assumption that, in the mTLS case, undefined means an unsuccessful (or pending?) login and a blank string means a successful login?

This context does introduces its own problem, I think: if one calls setCredentials with defined host and undefined token. This can cause socket.reconnect(). But if I understand correctly, undefined means there is an issue, so socket.reconnect() will fail. If a blank token means success, then I suppose that means we should check explicitly for undefined and not try to reconnect sockets. But maybe we want to formalize this with a boolean or something.

Although, it feels unexpected to me that we would call setCredentials for a failing state. I would expect it to be called when logging in and we know we have valid credentials, or to log out.

Or...should setCredentials be separate from whether sockets are reconnected?

@EhabY EhabY force-pushed the multi-deployment-squashed branch from 642fd52 to 5fd7241 Compare December 10, 2025 14:38
@EhabY EhabY force-pushed the multi-deployment-squashed branch from 5fd7241 to 5e83d93 Compare December 10, 2025 14:39
@EhabY EhabY force-pushed the multi-deployment-squashed branch from 5e83d93 to 46afe7f Compare December 10, 2025 14:50
@EhabY EhabY force-pushed the multi-deployment-squashed branch from 46afe7f to acf26aa Compare December 10, 2025 14:59
Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

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

I ran out of time to test it but will do so tomorrow!

allWorkspacesProvider.fetchAndRefresh();
} else {
output.warn("No error, but got unexpected response", user);
output.info("Deployment set but not authenticated");
Copy link
Member

Choose a reason for hiding this comment

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

Ah yeah this is the use case I am curious about. Initially I felt like we would not want to set the deployment until we have valid credentials so we can set it all at once together and avoid having to synchronize, but thinking about it, this could allow you to try logging in, fail or cancel, close the window, come back later, and be able to try logging in again without having to enter the URL a second time?

But, if I try to log into a new deployment, then change my mind and cancel, I could lose being logged into that previous deployment, which might feel bad. We could handle the above case by just having the URL in the history I suppose.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

try logging in again without having to enter the URL a second time?

The idea here is that you could login, not open VS Code in a while then open it again and now suddenly you are not authenticated (but still you maintain the last deployment that you were connected to). So you can have stuff like the URL prepopulated when you click login.

I could lose being logged into that previous deployment, which might feel bad

I mean currently you cannot do this WITHOUT clicking logout anyway (unless the current deployment is not authenticated because the token is gone) but then setCurrentDeployment is only set at the very end anyway...

Copy link
Member

@code-asher code-asher Dec 12, 2025

Choose a reason for hiding this comment

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

The idea here is that you could login, not open VS Code in a while then open it again and now suddenly you are not authenticated (but still you maintain the last deployment that you were connected to). So you can have stuff like the URL prepopulated when you click login.

But this does not require that we set the deployment without validation and then set it again with validation later. In this case, of a successful login, we would have a current deployment set.

I mean currently you cannot do this WITHOUT clicking logout anyway

Mostly I was thinking of it from an isolated point of view, that this prevents such a feature, but it is possible to trigger this with the URI handler, for example if I am logged in dev.coder.com and open vscode://coder.coder-remote/open?url=test.coder.com&owner=my-username&workspace=my-ws.

Although, maybe that is actually a bug, I forget if we decided to change the current deployment with the URI handler if something else is logged in.

As an aside, it would be nice if it was possible to directly log in a different deployment without having to log out first (fewer button clicks).

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I feel like we are talking about different things now lol

it would be nice if it was possible to directly log in a different deployment without having to log out first (fewer button clicks).

We currently can do that, that's why we do setDeploymentAndValidate when handling vscode URLs. What was the original ask actually? Setting all the deployment info (with the User) all at once as compared to setting the deployment info partially (everything BUT user)?

Copy link
Member

@code-asher code-asher Dec 13, 2025

Choose a reason for hiding this comment

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

I feel like we are talking about different things now lol

Could be lol

it would be nice if it was possible to directly log in a different deployment without having to log out first (fewer button clicks).
We currently can do that

Here I mean by clicking "login" again (there is no way to do this currently since we have no login button or menu item once you are logged in) or by running the login command (if you do this, it simply does nothing). Not something we need to add, just thinking out loud it would be nice if I could switch deployments without having to click logout first, and that if we did one day add that, setting the current deployment in this way (as a two-step process) would cause issues.

While it is not possible from the UI, it is possible to directly log into a different deployment with the URI handler, though I am not sure that is intended? Should the URI handler change what I am logged into currently? Or should it only be used for the remote connection? I vaguely recall talking about this, but idk what conclusion we reached.

What was the original ask actually? Setting all the deployment info (with the User) all at once as compared to setting the deployment info partially (everything BUT user)?

Yup yup. Currently we set the deployment, then we log in, then we set it again with credentials. This means that if you cancel during the login, you are stuck with this new deployment because we already set it as the current deployment. Or, if your credentials are invalid, you are stuck with that broken connection (until you log into the other again, which admittedly is not that hard since we are not deleting tokens anymore).

For example I am logged into dev.coder.com and if I open a URI from my test deployment but the token is wrong or missing, my current deployment gets nuked so now my sidebar just shows "login". I would expect dev.coder.com to still be logged in. If I restart VS Code, I will actually still be logged into dev.coder.com. We only set it for the current instance, temporarily.

I propose setting it only once we have validated auth, so we set it just the one time, and a failure or cancellation during login will leave the current deployment in place.

@EhabY
Copy link
Collaborator Author

EhabY commented Dec 11, 2025

@code-asher I've add tests in the last commit BTW, if you wish I can make it a separate PR 🙏

@EhabY EhabY force-pushed the multi-deployment-squashed branch 2 times, most recently from 542c240 to 1abfb6e Compare December 11, 2025 13:16
@EhabY EhabY force-pushed the multi-deployment-squashed branch from 1abfb6e to b37031e Compare December 11, 2025 22:12
@EhabY EhabY force-pushed the multi-deployment-squashed branch from b37031e to 6f307c1 Compare December 11, 2025 22:14
@code-asher
Copy link
Member

Nah same PR for tests I would say!

Copy link
Member

@code-asher code-asher left a comment

Choose a reason for hiding this comment

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

Wow tried it out and the cross-window stuff is quite magical. ✨ Really cool!!

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.

2 participants