From 5e06ac1b25e451290cd95c90c056eb3c7a9f19f2 Mon Sep 17 00:00:00 2001 From: Jesse Wright <63333554+jeswr@users.noreply.github.com> Date: Thu, 11 Jun 2026 21:01:44 +0100 Subject: [PATCH] fix: reuse the popup for the interactive retry instead of closing it The flow always tries prompt=none first, so a fresh login is a silent attempt, a login_required, then an interactive retry. Closing the popup on every callback meant that retry had to open a new window, outside the original click's already spent user activation, so popup blockers stopped it and the user was stranded in the "open new window" dialog. Keeping the window open for the interaction errors lets the retry navigate it by name, which needs no activation. A code or a terminal error such as access_denied still closes it, and the cancel and abort paths are unchanged. Co-Authored-By: Claude Opus 5 (1M context) --- src/AuthorizationCodeFlow.ts | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/AuthorizationCodeFlow.ts b/src/AuthorizationCodeFlow.ts index 36adfce..f692d56 100644 --- a/src/AuthorizationCodeFlow.ts +++ b/src/AuthorizationCodeFlow.ts @@ -194,7 +194,13 @@ export class AuthorizationCodeFlow extends HTMLElement { this.ownerDocument.defaultView?.removeEventListener("message", onMessage) signal.removeEventListener("abort", onAbort) this.#switchModal.close() - this.#authorizationWindow?.close() + + // Keep the popup open so the interactive retry navigates this named window, + // instead of opening a new one that popup blockers would stop. + if (!needsInteraction(message.data)) { + this.#authorizationWindow?.close() + } + respondWithCode(message.data) } @@ -252,3 +258,18 @@ export class AuthorizationCodeFlow extends HTMLElement { this.#cancelCodeRequest?.call(undefined, new CodeRequestCancelledError(this.#authorizationUri!)) } } + +/** + * Whether the authorization server answered a silent attempt by asking for user interaction. + * + * @remarks These are the errors that token providers retry interactively. Anything else, an authorization code or a terminal error such as `access_denied`, ends the flow. + */ +function needsInteraction(authorizationResponse: unknown): boolean { + try { + const error = new URL(authorizationResponse as string).searchParams.get("error") + + return error === "interaction_required" || error === "consent_required" || error === "login_required" + } catch { + return false + } +}