Skip to content

Commit 1f2f287

Browse files
committed
Improve login experience
1 parent 423742c commit 1f2f287

File tree

11 files changed

+358
-324
lines changed

11 files changed

+358
-324
lines changed

src/commands.ts

Lines changed: 40 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import { createWorkspaceIdentifier, extractAgents } from "./api/api-helper";
99
import { type CliManager } from "./core/cliManager";
1010
import { type ServiceContainer } from "./core/container";
1111
import { type ContextManager } from "./core/contextManager";
12+
import { type Deployment } from "./core/deployment";
1213
import { type MementoManager } from "./core/mementoManager";
1314
import { type PathResolver } from "./core/pathResolver";
1415
import { type SecretsManager } from "./core/secretsManager";
@@ -61,6 +62,17 @@ export class Commands {
6162
this.loginCoordinator = serviceContainer.getLoginCoordinator();
6263
}
6364

65+
/**
66+
* Get the current deployment, throwing if not logged in.
67+
*/
68+
private async requireDeployment(): Promise<Deployment> {
69+
const deployment = await this.secretsManager.getCurrentDeployment();
70+
if (!deployment) {
71+
throw new Error("You are not logged in");
72+
}
73+
return deployment;
74+
}
75+
6476
/**
6577
* Log into the provided deployment. If the deployment URL is not specified,
6678
* ask for it first with a menu showing recent URLs along with the default URL
@@ -76,7 +88,12 @@ export class Commands {
7688
}
7789
this.logger.info("Logging in");
7890

79-
const url = await maybeAskUrl(this.mementoManager, args?.url);
91+
const currentDeployment = await this.secretsManager.getCurrentDeployment();
92+
const url = await maybeAskUrl(
93+
this.mementoManager,
94+
args?.url,
95+
currentDeployment?.url,
96+
);
8097
if (!url) {
8198
return;
8299
}
@@ -88,7 +105,8 @@ export class Commands {
88105
this.logger.info("Using deployment label", label);
89106

90107
const result = await this.loginCoordinator.promptForLogin({
91-
deployment: { url, label },
108+
label,
109+
url,
92110
autoLogin: args?.autoLogin,
93111
oauthSessionManager: this.oauthSessionManager,
94112
});
@@ -97,16 +115,13 @@ export class Commands {
97115
return;
98116
}
99117

100-
// Authorize the global client
118+
// Set client immediately so subsequent operations in this function have the correct host/token.
119+
// The cross-window listener will also update the client, but that's async.
101120
this.restClient.setHost(url);
102121
this.restClient.setSessionToken(result.token);
103122

104-
// Store for later sessions
105-
await this.mementoManager.setUrl(url);
106-
await this.secretsManager.setSessionAuth(label, {
107-
url,
108-
token: result.token,
109-
});
123+
// Set as current deployment (triggers cross-window sync).
124+
await this.secretsManager.setCurrentDeployment({ url, label });
110125

111126
// Update contexts
112127
this.contextManager.set("coder.authenticated", true);
@@ -129,7 +144,6 @@ export class Commands {
129144
}
130145
});
131146

132-
await this.secretsManager.triggerLoginStateChange(label, "login");
133147
vscode.commands.executeCommand("coder.refreshWorkspaces");
134148
}
135149

@@ -162,13 +176,8 @@ export class Commands {
162176
* Log out from the currently logged-in deployment.
163177
*/
164178
public async logout(): Promise<void> {
165-
const url = this.mementoManager.getUrl();
166-
if (!url) {
167-
// Sanity check; command should not be available if no url.
168-
throw new Error("You are not logged in");
169-
}
170-
171-
await this.forceLogout(toSafeHost(url));
179+
const deployment = await this.requireDeployment();
180+
await this.forceLogout(deployment.label);
172181
}
173182

174183
public async forceLogout(label: string): Promise<void> {
@@ -177,8 +186,7 @@ export class Commands {
177186
}
178187
this.logger.info(`Logging out of deployment: ${label}`);
179188

180-
// Only clear REST client and UI context if logging out of current deployment
181-
// Fire and forget
189+
// Fire and forget OAuth logout
182190
this.oauthSessionManager.logout().catch((error) => {
183191
this.logger.warn("OAuth logout failed, continuing with cleanup:", error);
184192
});
@@ -188,8 +196,10 @@ export class Commands {
188196
this.restClient.setHost("");
189197
this.restClient.setSessionToken("");
190198

191-
// Clear from memory.
192-
await this.mementoManager.setUrl(undefined);
199+
// Clear current deployment (triggers cross-window sync)
200+
await this.secretsManager.setCurrentDeployment(undefined);
201+
202+
// Clear all auth data for this deployment
193203
await this.secretsManager.clearAllAuthData(label);
194204

195205
this.contextManager.set("coder.authenticated", false);
@@ -203,8 +213,6 @@ export class Commands {
203213

204214
// This will result in clearing the workspace list.
205215
vscode.commands.executeCommand("coder.refreshWorkspaces");
206-
207-
await this.secretsManager.triggerLoginStateChange(label, "logout");
208216
}
209217

210218
/**
@@ -213,7 +221,8 @@ export class Commands {
213221
* Must only be called if currently logged in.
214222
*/
215223
public async createWorkspace(): Promise<void> {
216-
const uri = this.mementoManager.getUrl() + "/templates";
224+
const deployment = await this.requireDeployment();
225+
const uri = deployment.url + "/templates";
217226
await vscode.commands.executeCommand("vscode.open", uri);
218227
}
219228

@@ -227,8 +236,9 @@ export class Commands {
227236
*/
228237
public async navigateToWorkspace(item: OpenableTreeItem) {
229238
if (item) {
239+
const deployment = await this.requireDeployment();
230240
const workspaceId = createWorkspaceIdentifier(item.workspace);
231-
const uri = this.mementoManager.getUrl() + `/@${workspaceId}`;
241+
const uri = deployment.url + `/@${workspaceId}`;
232242
await vscode.commands.executeCommand("vscode.open", uri);
233243
} else if (this.workspace && this.workspaceRestClient) {
234244
const baseUrl =
@@ -250,8 +260,9 @@ export class Commands {
250260
*/
251261
public async navigateToWorkspaceSettings(item: OpenableTreeItem) {
252262
if (item) {
263+
const deployment = await this.requireDeployment();
253264
const workspaceId = createWorkspaceIdentifier(item.workspace);
254-
const uri = this.mementoManager.getUrl() + `/@${workspaceId}/settings`;
265+
const uri = deployment.url + `/@${workspaceId}/settings`;
255266
await vscode.commands.executeCommand("vscode.open", uri);
256267
} else if (this.workspace && this.workspaceRestClient) {
257268
const baseUrl =
@@ -328,18 +339,14 @@ export class Commands {
328339
const terminal = vscode.window.createTerminal(app.name);
329340

330341
// If workspace_name is provided, run coder ssh before the command
331-
332-
const url = this.mementoManager.getUrl();
333-
if (!url) {
334-
throw new Error("No coder url found for sidebar");
335-
}
342+
const deployment = await this.requireDeployment();
336343
const binary = await this.cliManager.fetchBinary(
337344
this.restClient,
338-
toSafeHost(url),
345+
deployment.label,
339346
);
340347

341348
const configDir = this.pathResolver.getGlobalConfigDir(
342-
toSafeHost(url),
349+
deployment.label,
343350
);
344351
const globalFlags = getGlobalFlags(
345352
vscode.workspace.getConfiguration(),

src/core/container.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export class ServiceContainer implements vscode.Disposable {
4444
this.contextManager = new ContextManager();
4545
this.loginCoordinator = new LoginCoordinator(
4646
this.secretsManager,
47+
this.mementoManager,
4748
this.vscodeProposed,
4849
this.logger,
4950
);

src/core/mementoManager.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,27 +7,16 @@ export class MementoManager {
77
constructor(private readonly memento: Memento) {}
88

99
/**
10-
* Add the URL to the list of recently accessed URLs in global storage, then
11-
* set it as the last used URL.
12-
*
13-
* If the URL is falsey, then remove it as the last used URL and do not touch
14-
* the history.
10+
* Add a URL to the history of recently accessed URLs.
11+
* Used by the URL picker to show recent deployments.
1512
*/
16-
public async setUrl(url: string | undefined): Promise<void> {
17-
await this.memento.update("url", url);
13+
public async addToUrlHistory(url: string): Promise<void> {
1814
if (url) {
1915
const history = this.withUrlHistory(url);
2016
await this.memento.update("urlHistory", history);
2117
}
2218
}
2319

24-
/**
25-
* Get the last used URL.
26-
*/
27-
public getUrl(): string | undefined {
28-
return this.memento.get("url");
29-
}
30-
3120
/**
3221
* Get the most recently accessed URLs (oldest to newest) with the provided
3322
* values appended. Duplicates will be removed.

0 commit comments

Comments
 (0)