Skip to content

Commit 27cef25

Browse files
committed
Add a race between login event and dialog promise when connecting and logged out
1 parent c7df45f commit 27cef25

File tree

2 files changed

+63
-20
lines changed

2 files changed

+63
-20
lines changed

src/extension.ts

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -309,6 +309,15 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
309309
commands.viewLogs.bind(commands),
310310
);
311311

312+
const remote = new Remote(
313+
vscodeProposed,
314+
output,
315+
commands,
316+
ctx.extensionMode,
317+
pathResolver,
318+
cliManager,
319+
);
320+
312321
ctx.subscriptions.push(
313322
secretsManager.onDidChangeSessionToken(async () => {
314323
const token = await secretsManager.getSessionToken();
@@ -320,6 +329,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
320329
output.info("Logging in");
321330
// Should login the user directly if the URL+Token are valid
322331
await commands.login({ url, token });
332+
// Resolve any pending login detection promises
333+
remote.resolveLoginDetected();
323334
}
324335
}),
325336
);
@@ -334,14 +345,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
334345
// (this would require the user to uninstall the Coder extension and
335346
// reinstall after installing the remote SSH extension, which is annoying)
336347
if (remoteSSHExtension && vscodeProposed.env.remoteAuthority) {
337-
const remote = new Remote(
338-
vscodeProposed,
339-
output,
340-
commands,
341-
ctx.extensionMode,
342-
pathResolver,
343-
cliManager,
344-
);
345348
try {
346349
const details = await remote.setup(
347350
vscodeProposed.env.remoteAuthority,

src/remote.ts

Lines changed: 52 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,9 @@ export interface RemoteDetails extends vscode.Disposable {
4444
}
4545

4646
export class Remote {
47+
private loginDetectedResolver: (() => void) | undefined;
48+
private loginDetectedPromise: Promise<void> = Promise.resolve();
49+
4750
public constructor(
4851
// We use the proposed API to get access to useCustom in dialogs.
4952
private readonly vscodeProposed: typeof vscode,
@@ -54,6 +57,27 @@ export class Remote {
5457
private readonly cliManager: CliManager,
5558
) {}
5659

60+
/**
61+
* Creates a new promise that will be resolved when login is detected in another window.
62+
* This should be called when starting a setup operation that might need login.
63+
*/
64+
private createLoginDetectionPromise(): void {
65+
this.loginDetectedPromise = new Promise<void>((resolve) => {
66+
this.loginDetectedResolver = resolve;
67+
});
68+
}
69+
70+
/**
71+
* Resolves the current login detection promise if one exists.
72+
* This should be called from the extension when login is detected.
73+
*/
74+
public resolveLoginDetected(): void {
75+
if (this.loginDetectedResolver) {
76+
this.loginDetectedResolver();
77+
this.loginDetectedResolver = undefined;
78+
}
79+
}
80+
5781
private async confirmStart(workspaceName: string): Promise<boolean> {
5882
const action = await this.vscodeProposed.window.showInformationMessage(
5983
`Unable to connect to the workspace ${workspaceName} because it is not running. Start the workspace?`,
@@ -206,6 +230,8 @@ export class Remote {
206230
remoteAuthority: string,
207231
firstConnect: boolean,
208232
): Promise<RemoteDetails | undefined> {
233+
this.createLoginDetectionPromise();
234+
209235
const parts = parseRemoteAuthority(remoteAuthority);
210236
if (!parts) {
211237
// Not a Coder host.
@@ -218,7 +244,7 @@ export class Remote {
218244
await this.migrateSessionToken(parts.label);
219245

220246
// Get the URL and token belonging to this host.
221-
const { url: baseUrlRaw, token } = await this.cliManager.readConfig(
247+
let { url: baseUrlRaw, token } = await this.cliManager.readConfig(
222248
parts.label,
223249
);
224250

@@ -227,24 +253,38 @@ export class Remote {
227253
!baseUrlRaw ||
228254
(!token && needToken(vscode.workspace.getConfiguration()))
229255
) {
230-
const result = await this.vscodeProposed.window.showInformationMessage(
256+
const dialogPromise = this.vscodeProposed.window.showInformationMessage(
231257
"You are not logged in...",
232258
{
233259
useCustom: true,
234260
modal: true,
235-
detail: `You must log in to access ${workspaceName}.`,
261+
detail: `You must log in to access ${workspaceName}. If you've already logged in, you may close this dialog.`,
236262
},
237263
"Log In",
238264
);
239-
if (!result) {
240-
// User declined to log in.
241-
await this.closeRemote();
265+
266+
// Race between dialog and login detection
267+
const result = await Promise.race([
268+
this.loginDetectedPromise.then(() => ({ type: "login" as const })),
269+
dialogPromise.then((userChoice) => ({
270+
type: "dialog" as const,
271+
userChoice,
272+
})),
273+
]);
274+
275+
if (result.type === "login") {
276+
return this.setup(remoteAuthority, firstConnect);
242277
} else {
243-
// Log in then try again.
244-
await this.commands.login({ url: baseUrlRaw, label: parts.label });
245-
await this.setup(remoteAuthority, firstConnect);
278+
if (!result.userChoice) {
279+
// User declined to log in.
280+
await this.closeRemote();
281+
return;
282+
} else {
283+
// Log in then try again.
284+
await this.commands.login({ url: baseUrlRaw, label: parts.label });
285+
return this.setup(remoteAuthority, firstConnect);
286+
}
246287
}
247-
return;
248288
}
249289

250290
this.logger.info("Using deployment URL", baseUrlRaw);
@@ -360,11 +400,11 @@ export class Remote {
360400
);
361401
if (!result) {
362402
await this.closeRemote();
403+
return;
363404
} else {
364405
await this.commands.login({ url: baseUrlRaw, label: parts.label });
365-
await this.setup(remoteAuthority, firstConnect);
406+
return this.setup(remoteAuthority, firstConnect);
366407
}
367-
return;
368408
}
369409
default:
370410
throw error;

0 commit comments

Comments
 (0)