Skip to content

Commit 63c557f

Browse files
committed
Handle final review comments
1 parent 6f307c1 commit 63c557f

File tree

7 files changed

+128
-94
lines changed

7 files changed

+128
-94
lines changed

src/api/coderApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ export class CoderApi extends Api implements vscode.Disposable {
114114
if (host) {
115115
socket.reconnect();
116116
} else {
117-
socket.suspend(WebSocketCloseCode.NORMAL, "Host cleared");
117+
socket.disconnect(WebSocketCloseCode.NORMAL, "Host cleared");
118118
}
119119
}
120120
}

src/core/secretsManager.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -213,9 +213,6 @@ export class SecretsManager {
213213
}
214214

215215
const oldToken = await this.secrets.get(LEGACY_SESSION_TOKEN_KEY);
216-
if (oldToken === undefined) {
217-
return undefined;
218-
}
219216

220217
await this.secrets.delete(LEGACY_SESSION_TOKEN_KEY);
221218
await this.memento.update("url", undefined);
@@ -225,7 +222,7 @@ export class SecretsManager {
225222
if (!existing) {
226223
await this.setSessionAuth(safeHostname, {
227224
url: legacyUrl,
228-
token: oldToken,
225+
token: oldToken ?? "",
229226
});
230227
}
231228

src/deployment/deploymentManager.ts

Lines changed: 97 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import type { WorkspaceProvider } from "../workspace/workspacesProvider";
1111

1212
import type { Deployment, DeploymentWithAuth } from "./types";
1313

14+
/**
15+
* Internal state type that allows mutation of user property.
16+
*/
17+
type DeploymentWithUser = Deployment & { user?: User };
18+
1419
/**
1520
* Manages deployment state for the extension.
1621
*
@@ -28,9 +33,9 @@ export class DeploymentManager implements vscode.Disposable {
2833
private readonly contextManager: ContextManager;
2934
private readonly logger: Logger;
3035

31-
private currentDeployment: (Deployment & { user?: User }) | null = null;
32-
private authListenerDisposable: vscode.Disposable | undefined;
33-
private crossWindowSyncDisposable: vscode.Disposable | undefined;
36+
#deployment: DeploymentWithUser | null = null;
37+
#authListenerDisposable: vscode.Disposable | undefined;
38+
#crossWindowSyncDisposable: vscode.Disposable | undefined;
3439

3540
private constructor(
3641
serviceContainer: ServiceContainer,
@@ -61,14 +66,14 @@ export class DeploymentManager implements vscode.Disposable {
6166
* Get the current deployment state.
6267
*/
6368
public getCurrentDeployment(): Deployment | null {
64-
return this.currentDeployment;
69+
return this.#deployment;
6570
}
6671

6772
/**
6873
* Check if we have an authenticated deployment (with a valid user).
6974
*/
7075
public isAuthenticated(): boolean {
71-
return this.currentDeployment?.user !== undefined;
76+
return this.#deployment?.user !== undefined;
7277
}
7378

7479
/**
@@ -78,8 +83,7 @@ export class DeploymentManager implements vscode.Disposable {
7883
public async changeDeployment(
7984
deployment: DeploymentWithAuth & { user: User },
8085
): Promise<void> {
81-
this.setDeployment(deployment);
82-
86+
this.setDeploymentInternal(deployment);
8387
await this.persistDeployment(deployment);
8488
}
8589

@@ -91,85 +95,90 @@ export class DeploymentManager implements vscode.Disposable {
9195
public async setDeploymentAndValidate(
9296
deployment: Deployment & { token?: string },
9397
): Promise<void> {
94-
this.setDeployment({ ...deployment });
95-
98+
this.setDeploymentInternal(deployment);
9699
await this.tryFetchAndUpgradeUser();
97100
}
98101

99-
private setDeployment(deployment: DeploymentWithAuth): void {
100-
this.currentDeployment = { ...deployment };
101-
if (deployment.token === undefined) {
102-
this.client.setHost(deployment.url);
103-
} else {
104-
this.client.setCredentials(deployment.url, deployment.token);
105-
}
106-
this.registerAuthListener(deployment.safeHostname);
107-
this.updateAuthContexts(deployment.user);
108-
this.refreshWorkspaces();
109-
}
110-
111102
/**
112103
* Clears the current deployment.
113104
*/
114105
public async clearDeployment(): Promise<void> {
115-
this.client.setCredentials(undefined, undefined);
106+
this.#authListenerDisposable?.dispose();
107+
this.#authListenerDisposable = undefined;
108+
this.#deployment = null;
116109

117-
this.authListenerDisposable?.dispose();
118-
this.authListenerDisposable = undefined;
119-
this.currentDeployment = null;
120-
121-
this.updateAuthContexts(undefined);
110+
this.client.setCredentials(undefined, undefined);
111+
this.updateAuthContexts();
122112
this.refreshWorkspaces();
113+
123114
await this.secretsManager.setCurrentDeployment(undefined);
124115
}
125116

126117
public dispose(): void {
127-
this.authListenerDisposable?.dispose();
128-
this.crossWindowSyncDisposable?.dispose();
118+
this.#authListenerDisposable?.dispose();
119+
this.#crossWindowSyncDisposable?.dispose();
129120
}
130121

131-
private subscribeToCrossWindowChanges(): void {
132-
this.crossWindowSyncDisposable =
133-
this.secretsManager.onDidChangeCurrentDeployment(
134-
async ({ deployment }) => {
135-
if (this.isAuthenticated()) {
136-
// Ignore if we are already authenticated
137-
return;
138-
}
122+
/**
123+
* Internal method to set deployment state with all side effects.
124+
* - Updates client credentials
125+
* - Re-registers auth listener if hostname changed
126+
* - Updates auth contexts
127+
* - Refreshes workspaces
128+
*/
129+
private setDeploymentInternal(deployment: DeploymentWithAuth): void {
130+
this.#deployment = { ...deployment };
139131

140-
if (deployment) {
141-
this.logger.info("Deployment changed from another window");
142-
const auth = await this.secretsManager.getSessionAuth(
143-
deployment.safeHostname,
144-
);
145-
await this.setDeploymentAndValidate({
146-
...deployment,
147-
token: auth?.token,
148-
});
149-
}
150-
},
151-
);
132+
// Update client credentials
133+
if (deployment.token !== undefined) {
134+
this.client.setCredentials(deployment.url, deployment.token);
135+
} else {
136+
this.client.setHost(deployment.url);
137+
}
138+
139+
this.registerAuthListener();
140+
this.updateAuthContexts();
141+
this.refreshWorkspaces();
142+
}
143+
144+
/**
145+
* Upgrade the current deployment with a user.
146+
* Use this when the user has been fetched after initial deployment setup.
147+
*/
148+
private upgradeWithUser(user: User): void {
149+
if (!this.#deployment) {
150+
return;
151+
}
152+
153+
this.#deployment.user = user;
154+
this.updateAuthContexts();
155+
this.refreshWorkspaces();
152156
}
153157

154158
/**
155-
* Register auth listener for the given deployment hostname.
159+
* Register auth listener for the current deployment.
156160
* Updates credentials when they change (token refresh, cross-window sync).
157161
*/
158-
private registerAuthListener(safeHostname: string): void {
159-
this.authListenerDisposable?.dispose();
162+
private registerAuthListener(): void {
163+
if (!this.#deployment) {
164+
return;
165+
}
160166

167+
// Capture hostname at registration time for the guard clause
168+
const safeHostname = this.#deployment.safeHostname;
169+
170+
this.#authListenerDisposable?.dispose();
161171
this.logger.debug("Registering auth listener for hostname", safeHostname);
162-
this.authListenerDisposable = this.secretsManager.onDidChangeSessionAuth(
172+
this.#authListenerDisposable = this.secretsManager.onDidChangeSessionAuth(
163173
safeHostname,
164174
async (auth) => {
165-
if (this.currentDeployment?.safeHostname !== safeHostname) {
175+
if (this.#deployment?.safeHostname !== safeHostname) {
166176
return;
167177
}
178+
168179
if (auth) {
169180
this.client.setCredentials(auth.url, auth.token);
170-
171-
// If we don't have a user yet, try to fetch one
172-
if (!this.currentDeployment?.user) {
181+
if (!this.isAuthenticated()) {
173182
await this.tryFetchAndUpgradeUser();
174183
}
175184
} else {
@@ -179,33 +188,54 @@ export class DeploymentManager implements vscode.Disposable {
179188
);
180189
}
181190

191+
private subscribeToCrossWindowChanges(): void {
192+
this.#crossWindowSyncDisposable =
193+
this.secretsManager.onDidChangeCurrentDeployment(
194+
async ({ deployment }) => {
195+
if (this.isAuthenticated()) {
196+
// Ignore if we are already authenticated
197+
return;
198+
}
199+
200+
if (deployment) {
201+
this.logger.info("Deployment changed from another window");
202+
const auth = await this.secretsManager.getSessionAuth(
203+
deployment.safeHostname,
204+
);
205+
await this.setDeploymentAndValidate({
206+
...deployment,
207+
token: auth?.token,
208+
});
209+
}
210+
},
211+
);
212+
}
213+
182214
/**
183215
* Try to fetch the authenticated user and upgrade the deployment state.
184216
*/
185217
private async tryFetchAndUpgradeUser(): Promise<void> {
186-
if (!this.currentDeployment || this.currentDeployment.user) {
218+
if (!this.#deployment || this.isAuthenticated()) {
187219
return;
188220
}
189221

190-
const safeHostname = this.currentDeployment.safeHostname;
222+
const safeHostname = this.#deployment.safeHostname;
191223

192224
try {
193225
const user = await this.client.getAuthenticatedUser();
194226

195227
// Re-validate deployment hasn't changed during await
196-
if (this.currentDeployment?.safeHostname !== safeHostname) {
228+
if (this.#deployment?.safeHostname !== safeHostname) {
197229
this.logger.debug(
198230
"Deployment changed during user fetch, discarding result",
199231
);
200232
return;
201233
}
202234

203-
this.currentDeployment.user = user;
204-
this.updateAuthContexts(user);
205-
this.refreshWorkspaces();
235+
this.upgradeWithUser(user);
206236

207237
// Persist with user
208-
await this.persistDeployment(this.currentDeployment);
238+
await this.persistDeployment(this.#deployment);
209239
} catch (e) {
210240
this.logger.warn("Failed to fetch user:", e);
211241
}
@@ -214,7 +244,8 @@ export class DeploymentManager implements vscode.Disposable {
214244
/**
215245
* Update authentication-related contexts.
216246
*/
217-
private updateAuthContexts(user: User | undefined): void {
247+
private updateAuthContexts(): void {
248+
const user = this.#deployment?.user;
218249
this.contextManager.set("coder.authenticated", Boolean(user));
219250
const isOwner = user?.roles.some((r) => r.name === "owner") ?? false;
220251
this.contextManager.set("coder.isOwner", isOwner);
@@ -232,9 +263,7 @@ export class DeploymentManager implements vscode.Disposable {
232263
/**
233264
* Persist deployment to storage for cross-window sync.
234265
*/
235-
private async persistDeployment(
236-
deployment: DeploymentWithAuth,
237-
): Promise<void> {
266+
private async persistDeployment(deployment: Deployment): Promise<void> {
238267
await this.secretsManager.setCurrentDeployment(deployment);
239268
await this.mementoManager.addToUrlHistory(deployment.url);
240269
}

src/login/loginCoordinator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -291,10 +291,10 @@ export class LoginCoordinator {
291291
},
292292
});
293293

294-
if (user === undefined || validatedToken === undefined) {
295-
return { success: false };
294+
if (user) {
295+
return { success: true, user, token: validatedToken ?? "" };
296296
}
297297

298-
return { success: true, user, token: validatedToken };
298+
return { success: false };
299299
}
300300
}

0 commit comments

Comments
 (0)