From 78707e2d796ff63f05eac0138da61f183909350d Mon Sep 17 00:00:00 2001 From: Rachel Macfarlane Date: Fri, 19 Feb 2021 14:56:54 -0800 Subject: [PATCH] Fix #117098 --- .../github-authentication/src/common/utils.ts | 47 ++++++++++--------- .../github-authentication/src/githubServer.ts | 18 ++++--- 2 files changed, 37 insertions(+), 28 deletions(-) diff --git a/extensions/github-authentication/src/common/utils.ts b/extensions/github-authentication/src/common/utils.ts index 6ebfe7b08b3..914e9288c86 100644 --- a/extensions/github-authentication/src/common/utils.ts +++ b/extensions/github-authentication/src/common/utils.ts @@ -3,7 +3,7 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import { Event, Disposable } from 'vscode'; +import { EventEmitter, Event, Disposable } from 'vscode'; export function filterEvent(event: Event, filter: (e: T) => boolean): Event { return (listener, thisArgs = null, disposables?) => event(e => filter(e) && listener.call(thisArgs, e), null, disposables); @@ -47,29 +47,34 @@ const passthrough = (value: any, resolve: (value?: any) => void) => resolve(valu * @param adapter controls resolution of the returned promise * @returns a promise that resolves or rejects as specified by the adapter */ -export async function promiseFromEvent( +export function promiseFromEvent( event: Event, - adapter: PromiseAdapter = passthrough): Promise { + adapter: PromiseAdapter = passthrough): { promise: Promise, cancel: EventEmitter } { let subscription: Disposable; - return new Promise((resolve, reject) => - subscription = event((value: T) => { - try { - Promise.resolve(adapter(value, resolve, reject)) - .catch(reject); - } catch (error) { - reject(error); + let cancel = new EventEmitter(); + return { + promise: new Promise((resolve, reject) => { + cancel.event(_ => reject()); + subscription = event((value: T) => { + try { + Promise.resolve(adapter(value, resolve, reject)) + .catch(reject); + } catch (error) { + reject(error); + } + }); + }).then( + (result: U) => { + subscription.dispose(); + return result; + }, + error => { + subscription.dispose(); + throw error; } - }) - ).then( - (result: U) => { - subscription.dispose(); - return result; - }, - error => { - subscription.dispose(); - throw error; - } - ); + ), + cancel + }; } export function arrayEquals(one: ReadonlyArray | undefined, other: ReadonlyArray | undefined, itemEquals: (a: T, b: T) => boolean = (a, b) => a === b): boolean { diff --git a/extensions/github-authentication/src/githubServer.ts b/extensions/github-authentication/src/githubServer.ts index 6a0ecfa357f..36694fbf0ce 100644 --- a/extensions/github-authentication/src/githubServer.ts +++ b/extensions/github-authentication/src/githubServer.ts @@ -40,7 +40,7 @@ export class GitHubServer { private _statusBarItem: vscode.StatusBarItem | undefined; private _pendingStates = new Map(); - private _codeExchangePromises = new Map>(); + private _codeExchangePromises = new Map, cancel: vscode.EventEmitter }>(); constructor(private readonly telemetryReporter: TelemetryReporter) { } @@ -86,17 +86,21 @@ export class GitHubServer { // Register a single listener for the URI callback, in case the user starts the login process multiple times // before completing it. - let existingPromise = this._codeExchangePromises.get(scopes); - if (!existingPromise) { - existingPromise = promiseFromEvent(uriHandler.event, this.exchangeCodeForToken(scopes)); - this._codeExchangePromises.set(scopes, existingPromise); + let codeExchangePromise = this._codeExchangePromises.get(scopes); + if (!codeExchangePromise) { + codeExchangePromise = promiseFromEvent(uriHandler.event, this.exchangeCodeForToken(scopes)); + this._codeExchangePromises.set(scopes, codeExchangePromise); } return Promise.race([ - existingPromise, - promiseFromEvent(onDidManuallyProvideToken.event, (token: string | undefined): string => { if (!token) { throw new Error('Cancelled'); } return token; }) + codeExchangePromise.promise, + promiseFromEvent(onDidManuallyProvideToken.event, (token: string | undefined): string => { + if (!token) { throw new Error('Cancelled'); } + return token; + }).promise ]).finally(() => { this._pendingStates.delete(scopes); + codeExchangePromise?.cancel.fire(); this._codeExchangePromises.delete(scopes); this.updateStatusBarItem(false); });