Skip to content

Commit 40f1d63

Browse files
committed
Improve login synchronization + Add background oauth refresh
1 parent 04ec9e6 commit 40f1d63

File tree

5 files changed

+155
-100
lines changed

5 files changed

+155
-100
lines changed

src/api/oauthInterceptors.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,10 @@ export function attachOAuthInterceptors(
2424
client.getAxiosInstance().interceptors.response.use(
2525
// Success response interceptor: proactive token refresh
2626
(response) => {
27-
if (oauthSessionManager.shouldRefreshToken()) {
28-
logger.debug(
29-
"Token approaching expiry, triggering proactive refresh in background",
30-
);
31-
32-
// Fire-and-forget: don't await, don't block response
33-
oauthSessionManager.refreshToken().catch((error) => {
34-
logger.warn("Background token refresh failed:", error);
35-
});
36-
}
27+
// Fire-and-forget: don't await, don't block response
28+
oauthSessionManager.refreshIfAlmostExpired().catch((error) => {
29+
logger.warn("Proactive background token refresh failed:", error);
30+
});
3731

3832
return response;
3933
},

src/commands.ts

Lines changed: 35 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import { type CoderApi } from "./api/coderApi";
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";
1312
import { type MementoManager } from "./core/mementoManager";
1413
import { type PathResolver } from "./core/pathResolver";
1514
import { type SecretsManager } from "./core/secretsManager";
@@ -45,11 +44,11 @@ export class Commands {
4544
// if you use multiple deployments).
4645
public workspace?: Workspace;
4746
public workspaceLogPath?: string;
48-
public workspaceRestClient?: CoderApi;
47+
public remoteWorkspaceClient?: CoderApi;
4948

5049
public constructor(
5150
serviceContainer: ServiceContainer,
52-
private readonly restClient: CoderApi,
51+
private readonly extensionClient: CoderApi,
5352
private readonly oauthSessionManager: OAuthSessionManager,
5453
) {
5554
this.vscodeProposed = serviceContainer.getVsCodeProposed();
@@ -65,12 +64,12 @@ export class Commands {
6564
/**
6665
* Get the current deployment, throwing if not logged in.
6766
*/
68-
private async requireDeployment(): Promise<Deployment> {
69-
const deployment = await this.secretsManager.getCurrentDeployment();
70-
if (!deployment) {
67+
private requireExtensionBaseUrl(): string {
68+
const url = this.extensionClient.getAxiosInstance().defaults.baseURL;
69+
if (!url) {
7170
throw new Error("You are not logged in");
7271
}
73-
return deployment;
72+
return url;
7473
}
7574

7675
/**
@@ -117,9 +116,9 @@ export class Commands {
117116

118117
// Set client immediately so subsequent operations in this function have the correct host/token.
119118
// The cross-window listener will also update the client, but that's async.
120-
this.restClient.setCredentials(url, result.token);
119+
this.extensionClient.setCredentials(url, result.token);
121120

122-
// Set as current deployment (triggers cross-window sync).
121+
// Set as current deployment
123122
await this.secretsManager.setCurrentDeployment({ url, label });
124123

125124
// Update contexts
@@ -173,14 +172,12 @@ export class Commands {
173172
* Log out from the currently logged-in deployment.
174173
*/
175174
public async logout(): Promise<void> {
176-
const deployment = await this.requireDeployment();
177-
await this.forceLogout(deployment.label);
178-
}
179-
180-
public async forceLogout(label: string): Promise<void> {
175+
const baseUrl = this.requireExtensionBaseUrl();
181176
if (!this.contextManager.get("coder.authenticated")) {
182177
return;
183178
}
179+
180+
const label = toSafeHost(baseUrl);
184181
this.logger.info(`Logging out of deployment: ${label}`);
185182

186183
// Fire and forget OAuth logout
@@ -190,7 +187,7 @@ export class Commands {
190187

191188
// Clear from the REST client. An empty url will indicate to other parts of
192189
// the code that we are logged out.
193-
this.restClient.setCredentials(undefined, undefined);
190+
this.extensionClient.setCredentials(undefined, undefined);
194191

195192
// Clear current deployment (triggers cross-window sync)
196193
await this.secretsManager.setCurrentDeployment(undefined);
@@ -211,8 +208,8 @@ export class Commands {
211208
* Must only be called if currently logged in.
212209
*/
213210
public async createWorkspace(): Promise<void> {
214-
const deployment = await this.requireDeployment();
215-
const uri = deployment.url + "/templates";
211+
const baseUrl = this.requireExtensionBaseUrl();
212+
const uri = baseUrl + "/templates";
216213
await vscode.commands.executeCommand("vscode.open", uri);
217214
}
218215

@@ -226,13 +223,13 @@ export class Commands {
226223
*/
227224
public async navigateToWorkspace(item: OpenableTreeItem) {
228225
if (item) {
229-
const deployment = await this.requireDeployment();
226+
const baseUrl = this.requireExtensionBaseUrl();
230227
const workspaceId = createWorkspaceIdentifier(item.workspace);
231-
const uri = deployment.url + `/@${workspaceId}`;
228+
const uri = baseUrl + `/@${workspaceId}`;
232229
await vscode.commands.executeCommand("vscode.open", uri);
233-
} else if (this.workspace && this.workspaceRestClient) {
230+
} else if (this.workspace && this.remoteWorkspaceClient) {
234231
const baseUrl =
235-
this.workspaceRestClient.getAxiosInstance().defaults.baseURL;
232+
this.remoteWorkspaceClient.getAxiosInstance().defaults.baseURL;
236233
const uri = `${baseUrl}/@${createWorkspaceIdentifier(this.workspace)}`;
237234
await vscode.commands.executeCommand("vscode.open", uri);
238235
} else {
@@ -250,13 +247,13 @@ export class Commands {
250247
*/
251248
public async navigateToWorkspaceSettings(item: OpenableTreeItem) {
252249
if (item) {
253-
const deployment = await this.requireDeployment();
250+
const baseUrl = this.requireExtensionBaseUrl();
254251
const workspaceId = createWorkspaceIdentifier(item.workspace);
255-
const uri = deployment.url + `/@${workspaceId}/settings`;
252+
const uri = baseUrl + `/@${workspaceId}/settings`;
256253
await vscode.commands.executeCommand("vscode.open", uri);
257-
} else if (this.workspace && this.workspaceRestClient) {
254+
} else if (this.workspace && this.remoteWorkspaceClient) {
258255
const baseUrl =
259-
this.workspaceRestClient.getAxiosInstance().defaults.baseURL;
256+
this.remoteWorkspaceClient.getAxiosInstance().defaults.baseURL;
260257
const uri = `${baseUrl}/@${createWorkspaceIdentifier(this.workspace)}/settings`;
261258
await vscode.commands.executeCommand("vscode.open", uri);
262259
} else {
@@ -274,7 +271,7 @@ export class Commands {
274271
*/
275272
public async openFromSidebar(item: OpenableTreeItem) {
276273
if (item) {
277-
const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL;
274+
const baseUrl = this.extensionClient.getAxiosInstance().defaults.baseURL;
278275
if (!baseUrl) {
279276
throw new Error("You are not logged in");
280277
}
@@ -329,15 +326,14 @@ export class Commands {
329326
const terminal = vscode.window.createTerminal(app.name);
330327

331328
// If workspace_name is provided, run coder ssh before the command
332-
const deployment = await this.requireDeployment();
329+
const baseUrl = this.requireExtensionBaseUrl();
330+
const label = toSafeHost(baseUrl);
333331
const binary = await this.cliManager.fetchBinary(
334-
this.restClient,
335-
deployment.label,
332+
this.extensionClient,
333+
label,
336334
);
337335

338-
const configDir = this.pathResolver.getGlobalConfigDir(
339-
deployment.label,
340-
);
336+
const configDir = this.pathResolver.getGlobalConfigDir(label);
341337
const globalFlags = getGlobalFlags(
342338
vscode.workspace.getConfiguration(),
343339
configDir,
@@ -374,14 +370,14 @@ export class Commands {
374370
folderPath?: string,
375371
openRecent?: boolean,
376372
): Promise<void> {
377-
const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL;
373+
const baseUrl = this.extensionClient.getAxiosInstance().defaults.baseURL;
378374
if (!baseUrl) {
379375
throw new Error("You are not logged in");
380376
}
381377

382378
let workspace: Workspace | undefined;
383379
if (workspaceOwner && workspaceName) {
384-
workspace = await this.restClient.getWorkspaceByOwnerAndName(
380+
workspace = await this.extensionClient.getWorkspaceByOwnerAndName(
385381
workspaceOwner,
386382
workspaceName,
387383
);
@@ -417,7 +413,7 @@ export class Commands {
417413
localWorkspaceFolder: string = "",
418414
localConfigFile: string = "",
419415
): Promise<void> {
420-
const baseUrl = this.restClient.getAxiosInstance().defaults.baseURL;
416+
const baseUrl = this.extensionClient.getAxiosInstance().defaults.baseURL;
421417
if (!baseUrl) {
422418
throw new Error("You are not logged in");
423419
}
@@ -473,7 +469,7 @@ export class Commands {
473469
* this is a no-op.
474470
*/
475471
public async updateWorkspace(): Promise<void> {
476-
if (!this.workspace || !this.workspaceRestClient) {
472+
if (!this.workspace || !this.remoteWorkspaceClient) {
477473
return;
478474
}
479475
const action = await this.vscodeProposed.window.showWarningMessage(
@@ -486,7 +482,7 @@ export class Commands {
486482
"Update",
487483
);
488484
if (action === "Update") {
489-
await this.workspaceRestClient.updateWorkspaceVersion(this.workspace);
485+
await this.remoteWorkspaceClient.updateWorkspaceVersion(this.workspace);
490486
}
491487
}
492488

@@ -501,7 +497,7 @@ export class Commands {
501497
let lastWorkspaces: readonly Workspace[];
502498
quickPick.onDidChangeValue((value) => {
503499
quickPick.busy = true;
504-
this.restClient
500+
this.extensionClient
505501
.getWorkspaces({
506502
q: value,
507503
})
@@ -564,7 +560,7 @@ export class Commands {
564560
// we need to fetch the agents through the resources API, as the
565561
// workspaces query does not include agents when off.
566562
this.logger.info("Fetching agents from template version");
567-
const resources = await this.restClient.getTemplateVersionResources(
563+
const resources = await this.extensionClient.getTemplateVersionResources(
568564
workspace.latest_build.template_version_id,
569565
);
570566
return extractAgents(resources);

0 commit comments

Comments
 (0)