Remove quick pick flow for protocol handler (#270455)

Remove quick pick flow

Since we have device code flow, that's better.

ref https://github.com/microsoft/vscode/issues/270452
This commit is contained in:
Tyler James Leonhardt
2025-10-08 18:20:16 -07:00
committed by GitHub
parent b676e12277
commit bcbd0b4a98
3 changed files with 15 additions and 130 deletions

View File

@@ -5,17 +5,14 @@
import type { ILoopbackClient, ServerAuthorizationCodeResponse } from '@azure/msal-node';
import type { UriEventHandler } from '../UriEventHandler';
import { Disposable, env, l10n, LogOutputChannel, Uri, window } from 'vscode';
import { DeferredPromise, toPromise } from './async';
import { isSupportedClient } from './env';
import { env, LogOutputChannel, Uri } from 'vscode';
import { toPromise } from './async';
export interface ILoopbackClientAndOpener extends ILoopbackClient {
openBrowser(url: string): Promise<void>;
}
export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener {
private _responseDeferred: DeferredPromise<ServerAuthorizationCodeResponse> | undefined;
constructor(
private readonly _uriHandler: UriEventHandler,
private readonly _redirectUri: string,
@@ -24,14 +21,16 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener {
) { }
async listenForAuthCode(): Promise<ServerAuthorizationCodeResponse> {
await this._responseDeferred?.cancel();
this._responseDeferred = new DeferredPromise();
const result = await this._responseDeferred.p;
this._responseDeferred = undefined;
if (result) {
return result;
}
throw new Error('No valid response received for authorization code.');
const url = await toPromise(this._uriHandler.event);
this._logger.debug(`Received URL event. Authority: ${url.authority}`);
const result = new URL(url.toString(true));
return {
code: result.searchParams.get('code') ?? undefined,
state: result.searchParams.get('state') ?? undefined,
error: result.searchParams.get('error') ?? undefined,
error_description: result.searchParams.get('error_description') ?? undefined,
error_uri: result.searchParams.get('error_uri') ?? undefined,
};
}
getRedirectUri(): string {
@@ -45,93 +44,7 @@ export class UriHandlerLoopbackClient implements ILoopbackClientAndOpener {
}
async openBrowser(url: string): Promise<void> {
if (isSupportedClient(this._callbackUri)) {
void this._getCodeResponseFromUriHandler();
} else {
// Unsupported clients will be shown the code in the browser, but it will not redirect back since this
// isn't a supported client. Instead, they will copy that code in the browser and paste it in an input box
// that will be shown to them by the extension.
void this._getCodeResponseFromQuickPick();
}
const uri = Uri.parse(url + `&state=${encodeURI(this._callbackUri.toString(true))}`);
await env.openExternal(uri);
}
private async _getCodeResponseFromUriHandler(): Promise<void> {
if (!this._responseDeferred) {
throw new Error('No listener for auth code');
}
const url = await toPromise(this._uriHandler.event);
this._logger.debug(`Received URL event. Authority: ${url.authority}`);
const result = new URL(url.toString(true));
this._responseDeferred?.complete({
code: result.searchParams.get('code') ?? undefined,
state: result.searchParams.get('state') ?? undefined,
error: result.searchParams.get('error') ?? undefined,
error_description: result.searchParams.get('error_description') ?? undefined,
error_uri: result.searchParams.get('error_uri') ?? undefined,
});
}
private async _getCodeResponseFromQuickPick(): Promise<void> {
if (!this._responseDeferred) {
throw new Error('No listener for auth code');
}
const inputBox = window.createInputBox();
inputBox.ignoreFocusOut = true;
inputBox.title = l10n.t('Microsoft Authentication');
inputBox.prompt = l10n.t('Provide the authorization code to complete the sign in flow.');
inputBox.placeholder = l10n.t('Paste authorization code here...');
inputBox.show();
const code = await new Promise<string | undefined>((resolve) => {
let resolvedValue: string | undefined = undefined;
const disposable = Disposable.from(
inputBox,
inputBox.onDidAccept(async () => {
if (!inputBox.value) {
inputBox.validationMessage = l10n.t('Authorization code is required.');
return;
}
const code = inputBox.value;
resolvedValue = code;
resolve(code);
inputBox.hide();
}),
inputBox.onDidChangeValue(() => {
inputBox.validationMessage = undefined;
}),
inputBox.onDidHide(() => {
disposable.dispose();
if (!resolvedValue) {
resolve(undefined);
}
})
);
Promise.allSettled([this._responseDeferred?.p]).then(() => disposable.dispose());
});
// Something canceled the original deferred promise, so just return.
if (this._responseDeferred.isSettled) {
return;
}
if (code) {
this._logger.debug('Received auth code from quick pick');
this._responseDeferred.complete({
code,
state: undefined,
error: undefined,
error_description: undefined,
error_uri: undefined
});
return;
}
this._responseDeferred.complete({
code: undefined,
state: undefined,
error: 'User cancelled',
error_description: 'User cancelled',
error_uri: undefined
});
}
}

View File

@@ -51,7 +51,7 @@ suite('UriHandlerLoopbackClient', () => {
});
// Skipped for now until `listenForAuthCode` is refactored to not show quick pick
suite.skip('listenForAuthCode', () => {
suite('listenForAuthCode', () => {
test('should return auth code from URL', async () => {
const code = '1234';
const state = '5678';