Skip to content

Commit d5cf445

Browse files
committed
Add a race between login event and dialog promise when connecting and logged out
1 parent c979ed9 commit d5cf445

File tree

2 files changed

+66
-33
lines changed

2 files changed

+66
-33
lines changed

src/extension.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
299299
commands.viewLogs.bind(commands),
300300
);
301301

302+
const remote = new Remote(serviceContainer, commands, ctx.extensionMode);
303+
302304
ctx.subscriptions.push(
303305
secretsManager.onDidChangeSessionToken(async () => {
304306
const token = await secretsManager.getSessionToken();
@@ -310,6 +312,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
310312
output.info("Logging in");
311313
// Should login the user directly if the URL+Token are valid
312314
await commands.login({ url, token });
315+
// Resolve any pending login detection promises
316+
remote.resolveLoginDetected();
313317
}
314318
}),
315319
);
@@ -324,7 +328,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
324328
// (this would require the user to uninstall the Coder extension and
325329
// reinstall after installing the remote SSH extension, which is annoying)
326330
if (remoteSSHExtension && vscodeProposed.env.remoteAuthority) {
327-
const remote = new Remote(serviceContainer, commands, ctx.extensionMode);
328331
try {
329332
const details = await remote.setup(
330333
vscodeProposed.env.remoteAuthority,

src/remote/remote.ts

Lines changed: 62 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,9 @@ export class Remote {
5959
private readonly pathResolver: PathResolver;
6060
private readonly cliManager: CliManager;
6161

62+
private loginDetectedResolver: (() => void) | undefined;
63+
private loginDetectedPromise: Promise<void> = Promise.resolve();
64+
6265
public constructor(
6366
serviceContainer: ServiceContainer,
6467
private readonly commands: Commands,
@@ -70,6 +73,27 @@ export class Remote {
7073
this.cliManager = serviceContainer.getCliManager();
7174
}
7275

76+
/**
77+
* Creates a new promise that will be resolved when login is detected in another window.
78+
* This should be called when starting a setup operation that might need login.
79+
*/
80+
private createLoginDetectionPromise(): void {
81+
this.loginDetectedPromise = new Promise<void>((resolve) => {
82+
this.loginDetectedResolver = resolve;
83+
});
84+
}
85+
86+
/**
87+
* Resolves the current login detection promise if one exists.
88+
* This should be called from the extension when login is detected.
89+
*/
90+
public resolveLoginDetected(): void {
91+
if (this.loginDetectedResolver) {
92+
this.loginDetectedResolver();
93+
this.loginDetectedResolver = undefined;
94+
}
95+
}
96+
7397
private async confirmStart(workspaceName: string): Promise<boolean> {
7498
const action = await this.vscodeProposed.window.showInformationMessage(
7599
`Unable to connect to the workspace ${workspaceName} because it is not running. Start the workspace?`,
@@ -233,34 +257,54 @@ export class Remote {
233257
// Migrate "session_token" file to "session", if needed.
234258
await this.migrateSessionToken(parts.label);
235259

260+
// Try to detect any login event that might happen after we read the current configs
261+
this.createLoginDetectionPromise();
236262
// Get the URL and token belonging to this host.
237263
const { url: baseUrlRaw, token } = await this.cliManager.readConfig(
238264
parts.label,
239265
);
240266

241-
// It could be that the cli config was deleted. If so, ask for the url.
242-
if (
243-
!baseUrlRaw ||
244-
(!token && needToken(vscode.workspace.getConfiguration()))
245-
) {
246-
const result = await this.vscodeProposed.window.showInformationMessage(
247-
"You are not logged in...",
267+
const showLoginDialog = async (message: string) => {
268+
const dialogPromise = this.vscodeProposed.window.showInformationMessage(
269+
message,
248270
{
249271
useCustom: true,
250272
modal: true,
251-
detail: `You must log in to access ${workspaceName}.`,
273+
detail: `You must log in to access ${workspaceName}. If you've already logged in, you may close this dialog.`,
252274
},
253275
"Log In",
254276
);
255-
if (!result) {
256-
// User declined to log in.
257-
await this.closeRemote();
277+
278+
// Race between dialog and login detection
279+
const result = await Promise.race([
280+
this.loginDetectedPromise.then(() => ({ type: "login" as const })),
281+
dialogPromise.then((userChoice) => ({
282+
type: "dialog" as const,
283+
userChoice,
284+
})),
285+
]);
286+
287+
if (result.type === "login") {
288+
return this.setup(remoteAuthority, firstConnect);
258289
} else {
259-
// Log in then try again.
260-
await this.commands.login({ url: baseUrlRaw, label: parts.label });
261-
await this.setup(remoteAuthority, firstConnect);
290+
if (!result.userChoice) {
291+
// User declined to log in.
292+
await this.closeRemote();
293+
return;
294+
} else {
295+
// Log in then try again.
296+
await this.commands.login({ url: baseUrlRaw, label: parts.label });
297+
return this.setup(remoteAuthority, firstConnect);
298+
}
262299
}
263-
return;
300+
};
301+
302+
// It could be that the cli config was deleted. If so, ask for the url.
303+
if (
304+
!baseUrlRaw ||
305+
(!token && needToken(vscode.workspace.getConfiguration()))
306+
) {
307+
return showLoginDialog("You are not logged in...");
264308
}
265309

266310
this.logger.info("Using deployment URL", baseUrlRaw);
@@ -331,6 +375,8 @@ export class Remote {
331375
// Next is to find the workspace from the URI scheme provided.
332376
let workspace: Workspace;
333377
try {
378+
// We could've logged out in the meantime
379+
this.createLoginDetectionPromise();
334380
this.logger.info(`Looking for workspace ${workspaceName}...`);
335381
workspace = await workspaceClient.getWorkspaceByOwnerAndName(
336382
parts.username,
@@ -364,23 +410,7 @@ export class Remote {
364410
return;
365411
}
366412
case 401: {
367-
const result =
368-
await this.vscodeProposed.window.showInformationMessage(
369-
"Your session expired...",
370-
{
371-
useCustom: true,
372-
modal: true,
373-
detail: `You must log in to access ${workspaceName}.`,
374-
},
375-
"Log In",
376-
);
377-
if (!result) {
378-
await this.closeRemote();
379-
} else {
380-
await this.commands.login({ url: baseUrlRaw, label: parts.label });
381-
await this.setup(remoteAuthority, firstConnect);
382-
}
383-
return;
413+
return showLoginDialog("Your session expired...");
384414
}
385415
default:
386416
throw error;

0 commit comments

Comments
 (0)