@@ -44,6 +44,9 @@ export interface RemoteDetails extends vscode.Disposable {
44
44
}
45
45
46
46
export class Remote {
47
+ private loginDetectedResolver :( ( ) => void ) | undefined ;
48
+ private loginDetectedPromise :Promise < void > = Promise . resolve ( ) ;
49
+
47
50
public constructor (
48
51
// We use the proposed API to get access to useCustom in dialogs.
49
52
private readonly vscodeProposed :typeof vscode ,
@@ -54,6 +57,27 @@ export class Remote {
54
57
private readonly cliManager :CliManager ,
55
58
) { }
56
59
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
+
57
81
private async confirmStart ( workspaceName :string ) :Promise < boolean > {
58
82
const action = await this . vscodeProposed . window . showInformationMessage (
59
83
`Unable to connect to the workspace${ workspaceName } because it is not running. Start the workspace?` ,
@@ -217,34 +241,54 @@ export class Remote {
217
241
// Migrate "session_token" file to "session", if needed.
218
242
await this . migrateSessionToken ( parts . label ) ;
219
243
244
+ // Try to detect any login event that might happen after we read the current configs
245
+ this . createLoginDetectionPromise ( ) ;
220
246
// Get the URL and token belonging to this host.
221
247
const { url :baseUrlRaw , token} = await this . cliManager . readConfig (
222
248
parts . label ,
223
249
) ;
224
250
225
- // It could be that the cli config was deleted. If so, ask for the url.
226
- if (
227
- ! baseUrlRaw ||
228
- ( ! token && needToken ( vscode . workspace . getConfiguration ( ) ) )
229
- ) {
230
- const result = await this . vscodeProposed . window . showInformationMessage (
231
- "You are not logged in..." ,
251
+ const showLoginDialog = async ( message :string ) => {
252
+ const dialogPromise = this . vscodeProposed . window . showInformationMessage (
253
+ message ,
232
254
{
233
255
useCustom :true ,
234
256
modal :true ,
235
- detail :`You must log in to access${ workspaceName } .` ,
257
+ detail :`You must log in to access${ workspaceName } . If you've already logged in, you may close this dialog. ` ,
236
258
} ,
237
259
"Log In" ,
238
260
) ;
239
- if ( ! result ) {
240
- // User declined to log in.
241
- await this . closeRemote ( ) ;
261
+
262
+ // Race between dialog and login detection
263
+ const result = await Promise . race ( [
264
+ this . loginDetectedPromise . then ( ( ) => ( { type :"login" as const } ) ) ,
265
+ dialogPromise . then ( ( userChoice ) => ( {
266
+ type :"dialog" as const ,
267
+ userChoice,
268
+ } ) ) ,
269
+ ] ) ;
270
+
271
+ if ( result . type === "login" ) {
272
+ return this . setup ( remoteAuthority , firstConnect ) ;
242
273
} else {
243
- // Log in then try again.
244
- await this . commands . login ( { url :baseUrlRaw , label :parts . label } ) ;
245
- await this . setup ( remoteAuthority , firstConnect ) ;
274
+ if ( ! result . userChoice ) {
275
+ // User declined to log in.
276
+ await this . closeRemote ( ) ;
277
+ return ;
278
+ } else {
279
+ // Log in then try again.
280
+ await this . commands . login ( { url :baseUrlRaw , label :parts . label } ) ;
281
+ return this . setup ( remoteAuthority , firstConnect ) ;
282
+ }
246
283
}
247
- return ;
284
+ } ;
285
+
286
+ // It could be that the cli config was deleted. If so, ask for the url.
287
+ if (
288
+ ! baseUrlRaw ||
289
+ ( ! token && needToken ( vscode . workspace . getConfiguration ( ) ) )
290
+ ) {
291
+ return showLoginDialog ( "You are not logged in..." ) ;
248
292
}
249
293
250
294
this . logger . info ( "Using deployment URL" , baseUrlRaw ) ;
@@ -315,6 +359,8 @@ export class Remote {
315
359
// Next is to find the workspace from the URI scheme provided.
316
360
let workspace :Workspace ;
317
361
try {
362
+ // We could've logged out in the meantime
363
+ this . createLoginDetectionPromise ( ) ;
318
364
this . logger . info ( `Looking for workspace${ workspaceName } ...` ) ;
319
365
workspace = await workspaceClient . getWorkspaceByOwnerAndName (
320
366
parts . username ,
@@ -348,23 +394,7 @@ export class Remote {
348
394
return ;
349
395
}
350
396
case 401 :{
351
- const result =
352
- await this . vscodeProposed . window . showInformationMessage (
353
- "Your session expired..." ,
354
- {
355
- useCustom :true ,
356
- modal :true ,
357
- detail :`You must log in to access${ workspaceName } .` ,
358
- } ,
359
- "Log In" ,
360
- ) ;
361
- if ( ! result ) {
362
- await this . closeRemote ( ) ;
363
- } else {
364
- await this . commands . login ( { url :baseUrlRaw , label :parts . label } ) ;
365
- await this . setup ( remoteAuthority , firstConnect ) ;
366
- }
367
- return ;
397
+ return showLoginDialog ( "Your session expired..." ) ;
368
398
}
369
399
default :
370
400
throw error ;