From 2a4d097fac665f3c1db2a868ce5e5a5622254278 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristian=20Pallar=C3=A9s?= Date: Fri, 5 Sep 2025 17:56:27 +0200 Subject: [PATCH 1/2] feat: improve authentication flow MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Show a modal before starting the authentication redirect - In rare cases, telemetry wouldn’t track if the authentication process was canceled --- src/plugins/setup.ts | 7 +++++-- src/utils/authenticate.ts | 34 +++++++++++++++++++++++----------- 2 files changed, 28 insertions(+), 13 deletions(-) diff --git a/src/plugins/setup.ts b/src/plugins/setup.ts index 02f70f1..f127d71 100644 --- a/src/plugins/setup.ts +++ b/src/plugins/setup.ts @@ -126,10 +126,13 @@ export default createPlugin( message: "Waiting for authentication response from the browser...", }); - const { authToken } = await minDelay( + const { authToken, canceled } = await minDelay( requestAuthentication(context, cancellationToken), ); - if (cancellationToken.isCancellationRequested) { + if (canceled) { + void window.showWarningMessage("Authentication cancelled."); + } + if (canceled || cancellationToken.isCancellationRequested) { telemetry.track({ name: "auth_token_configured", payload: { diff --git a/src/utils/authenticate.ts b/src/utils/authenticate.ts index 41131c6..03d7f1a 100644 --- a/src/utils/authenticate.ts +++ b/src/utils/authenticate.ts @@ -22,7 +22,10 @@ import { assertIsError } from "./assert.ts"; export async function requestAuthentication( context: ExtensionContext, cancellationToken?: CancellationToken, -): Promise<{ authToken: string }> { +): Promise< + | { authToken: string; canceled?: undefined } + | { authToken?: undefined; canceled: true } +> { return new Promise((resolve, reject) => { const uriHandler = window.registerUriHandler({ handleUri: (uri: Uri) => { @@ -34,7 +37,7 @@ export async function requestAuthentication( if (authToken) { resolve({ authToken }); } else { - window.showErrorMessage("No token found in URI."); + void window.showErrorMessage("No token found in URI."); reject(new Error("No token found in URI")); } }, @@ -42,14 +45,19 @@ export async function requestAuthentication( context.subscriptions.push(uriHandler); cancellationToken?.onCancellationRequested(() => { uriHandler.dispose(); - reject(new Error("Authentication cancelled")); + resolve({ canceled: true }); }); - void redirectToLocalStack(); + void redirectToLocalStack().then(({ canceled }) => { + if (canceled) { + uriHandler.dispose(); + resolve({ canceled: true }); + } + }); }); } -async function redirectToLocalStack() { +async function redirectToLocalStack(): Promise<{ canceled: boolean }> { // You don't have to get the Uri from the `env.asExternalUri` API but it will add a query // parameter (ex: "windowId%3D14") that will help VS Code decide which window to redirect to. // If this query parameter isn't specified, VS Code will pick the last windows that was focused. @@ -63,13 +71,17 @@ async function redirectToLocalStack() { const url = new URL(process.env.LOCALSTACK_WEB_AUTH_REDIRECT!); url.searchParams.set("windowId", redirectSearchParams.get("windowId") ?? ""); - const openSuccessful = await env.openExternal(Uri.parse(url.toString())); - - if (!openSuccessful) { - window.showErrorMessage( - `Open LocalStack sign-in URL in browser by entering the URL manually: ${url.toString()}`, - ); + const selection = await window.showInformationMessage( + `LocalStack needs to open the browser to continue with the authentication process.`, + { modal: true }, + "Continue", + ); + if (!selection) { + return { canceled: true }; } + + const openSuccessful = await env.openExternal(Uri.parse(url.toString())); + return { canceled: !openSuccessful }; } const LOCALSTACK_AUTH_FILENAME = `${os.homedir()}/.localstack/auth.json`; From 55e233e6e40a1d8a40f5f3d15bc8ea2194fa33fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cristian=20Pallar=C3=A9s?= Date: Fri, 5 Sep 2025 18:11:20 +0200 Subject: [PATCH 2/2] refactor canceled to cancelled --- src/plugins/setup.ts | 6 +++--- src/utils/authenticate.ts | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/plugins/setup.ts b/src/plugins/setup.ts index f127d71..b6ae624 100644 --- a/src/plugins/setup.ts +++ b/src/plugins/setup.ts @@ -126,13 +126,13 @@ export default createPlugin( message: "Waiting for authentication response from the browser...", }); - const { authToken, canceled } = await minDelay( + const { authToken, cancelled } = await minDelay( requestAuthentication(context, cancellationToken), ); - if (canceled) { + if (cancelled) { void window.showWarningMessage("Authentication cancelled."); } - if (canceled || cancellationToken.isCancellationRequested) { + if (cancelled || cancellationToken.isCancellationRequested) { telemetry.track({ name: "auth_token_configured", payload: { diff --git a/src/utils/authenticate.ts b/src/utils/authenticate.ts index 03d7f1a..b849844 100644 --- a/src/utils/authenticate.ts +++ b/src/utils/authenticate.ts @@ -23,8 +23,8 @@ export async function requestAuthentication( context: ExtensionContext, cancellationToken?: CancellationToken, ): Promise< - | { authToken: string; canceled?: undefined } - | { authToken?: undefined; canceled: true } + | { authToken: string; cancelled?: undefined } + | { authToken?: undefined; cancelled: true } > { return new Promise((resolve, reject) => { const uriHandler = window.registerUriHandler({ @@ -45,19 +45,19 @@ export async function requestAuthentication( context.subscriptions.push(uriHandler); cancellationToken?.onCancellationRequested(() => { uriHandler.dispose(); - resolve({ canceled: true }); + resolve({ cancelled: true }); }); - void redirectToLocalStack().then(({ canceled }) => { - if (canceled) { + void redirectToLocalStack().then(({ cancelled }) => { + if (cancelled) { uriHandler.dispose(); - resolve({ canceled: true }); + resolve({ cancelled: true }); } }); }); } -async function redirectToLocalStack(): Promise<{ canceled: boolean }> { +async function redirectToLocalStack(): Promise<{ cancelled: boolean }> { // You don't have to get the Uri from the `env.asExternalUri` API but it will add a query // parameter (ex: "windowId%3D14") that will help VS Code decide which window to redirect to. // If this query parameter isn't specified, VS Code will pick the last windows that was focused. @@ -77,11 +77,11 @@ async function redirectToLocalStack(): Promise<{ canceled: boolean }> { "Continue", ); if (!selection) { - return { canceled: true }; + return { cancelled: true }; } const openSuccessful = await env.openExternal(Uri.parse(url.toString())); - return { canceled: !openSuccessful }; + return { cancelled: !openSuccessful }; } const LOCALSTACK_AUTH_FILENAME = `${os.homedir()}/.localstack/auth.json`;