@@ -64,13 +64,7 @@ export class OAuthSessionManager implements vscode.Disposable {
6464private refreshInProgress = false ;
6565private lastRefreshAttempt = 0 ;
6666
67- // Pending authorization flow state
68- private pendingAuthResolve :
69- | ( ( value :{ code :string ; verifier :string } ) => void )
70- | undefined ;
7167private pendingAuthReject :( ( reason :Error ) => void ) | undefined ;
72- private expectedState :string | undefined ;
73- private pendingVerifier :string | undefined ;
7468
7569/**
7670 * Create and initialize a new OAuth session manager.
@@ -370,106 +364,80 @@ export class OAuthSessionManager implements vscode.Disposable {
370364challenge ,
371365) ;
372366
373- return new Promise < { code :string ; verifier :string } > (
367+ const callbackPromise = new Promise < { code :string ; verifier :string } > (
374368( resolve , reject ) => {
375369const timeoutMins = 5 ;
376- const timeout = setTimeout (
370+ const timeoutHandle = setTimeout (
377371( ) => {
378- this . clearPendingAuth ( ) ;
372+ cleanup ( ) ;
379373reject (
380374new Error ( `OAuth flow timed out after${ timeoutMins } minutes` ) ,
381375) ;
382376} ,
383377timeoutMins * 60 * 1000 ,
384378) ;
385379
386- const clearPromise = ( ) => {
387- clearTimeout ( timeout ) ;
388- this . clearPendingAuth ( ) ;
389- } ;
390-
391- this . pendingAuthResolve = ( result ) => {
392- clearPromise ( ) ;
393- resolve ( result ) ;
394- } ;
395-
396- this . pendingAuthReject = ( error ) => {
397- clearPromise ( ) ;
398- reject ( error ) ;
399- } ;
380+ const listener = this . secretsManager . onDidChangeOAuthCallback (
381+ ( { state :callbackState , code, error} ) => {
382+ if ( callbackState !== state ) {
383+ return ;
384+ }
400385
401- this . expectedState = state ;
402- this . pendingVerifier = verifier ;
386+ cleanup ( ) ;
403387
404- vscode . env . openExternal ( vscode . Uri . parse ( authUrl ) ) . then (
405- ( ) => { } ,
406- ( error ) => {
407- if ( error instanceof Error ) {
408- this . pendingAuthReject ?.( error ) ;
388+ if ( error ) {
389+ reject ( new Error ( `OAuth error:${ error } ` ) ) ;
390+ } else if ( code ) {
391+ resolve ( { code, verifier} ) ;
409392} else {
410- this . pendingAuthReject ?. ( new Error ( "Failed to open browser " ) ) ;
393+ reject ( new Error ( "No authorization code received " ) ) ;
411394}
412395} ,
413396) ;
397+
398+ const cleanup = ( ) => {
399+ clearTimeout ( timeoutHandle ) ;
400+ listener . dispose ( ) ;
401+ } ;
402+
403+ this . pendingAuthReject = ( error ) => {
404+ cleanup ( ) ;
405+ reject ( error ) ;
406+ } ;
414407} ,
415408) ;
416- }
417409
418- /**
419- * Clear pending authorization flow state.
420- */
421- private clearPendingAuth ( ) :void {
422- this . pendingAuthResolve = undefined ;
423- this . pendingAuthReject = undefined ;
424- this . expectedState = undefined ;
425- this . pendingVerifier = undefined ;
410+ try {
411+ await vscode . env . openExternal ( vscode . Uri . parse ( authUrl ) ) ;
412+ } catch ( error ) {
413+ throw error instanceof Error
414+ ?error
415+ :new Error ( "Failed to open browser" ) ;
416+ }
417+
418+ return callbackPromise ;
426419}
427420
428421/**
429422 * Handle OAuth callback from browser redirect.
430- * Validates state and resolves pending authorization promise.
431- *
432- * // TODO this has to work across windows!
423+ * Writes the callback result to secrets storage, triggering the waiting window to proceed.
433424 */
434- handleCallback (
425+ async handleCallback (
435426code :string | null ,
436427state :string | null ,
437428error :string | null ,
438- ) :void {
439- if ( ! this . pendingAuthResolve || ! this . pendingAuthReject ) {
440- this . logger . warn ( "Received OAuth callback but no pending auth flow" ) ;
441- return ;
442- }
443-
444- if ( error ) {
445- this . pendingAuthReject ( new Error ( `OAuth error:${ error } ` ) ) ;
446- return ;
447- }
448-
449- if ( ! code ) {
450- this . pendingAuthReject ( new Error ( "No authorization code received" ) ) ;
451- return ;
452- }
453-
429+ ) :Promise < void > {
454430if ( ! state ) {
455- this . pendingAuthReject ( new Error ( "No state received" ) ) ;
456- return ;
457- }
458-
459- if ( state !== this . expectedState ) {
460- this . pendingAuthReject (
461- new Error ( "State mismatch - possible CSRF attack" ) ,
462- ) ;
431+ this . logger . warn ( "Received OAuth callback with no state parameter" ) ;
463432return ;
464433}
465434
466- const verifier = this . pendingVerifier ;
467- if ( ! verifier ) {
468- this . pendingAuthReject ( new Error ( "No PKCE verifier found" ) ) ;
469- return ;
435+ try {
436+ await this . secretsManager . setOAuthCallback ( { state, code, error} ) ;
437+ this . logger . debug ( "OAuth callback processed successfully" ) ;
438+ } catch ( err ) {
439+ this . logger . error ( "Failed to process OAuth callback:" , err ) ;
470440}
471-
472- this . pendingAuthResolve ( { code, verifier} ) ;
473441}
474442
475443/**
@@ -712,13 +680,13 @@ export class OAuthSessionManager implements vscode.Disposable {
712680}
713681
714682/**
715- * Clears all in-memory state and rejects any pending operations .
683+ * Clears all in-memory state.
716684 */
717685dispose ( ) :void {
718686if ( this . pendingAuthReject ) {
719687this . pendingAuthReject ( new Error ( "OAuth session manager disposed" ) ) ;
720688}
721- this . clearPendingAuth ( ) ;
689+ this . pendingAuthReject = undefined ;
722690this . storedTokens = undefined ;
723691this . refreshInProgress = false ;
724692this . lastRefreshAttempt = 0 ;