From c5efa03908adcec9eb3864fd9f77030d64158cc2 Mon Sep 17 00:00:00 2001 From: Leo Vasanko Date: Fri, 18 Sep 2026 00:27:21 +0000 Subject: [PATCH] profile(): resolve 'login' when login flow completes inside the dialog The overlay now tracks which dialog kind is open: auth-success from a profile dialog resolves 'login', auth-back only rejects AuthCancelledError for auth dialogs. --- paskia-js/src/overlay.ts | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/paskia-js/src/overlay.ts b/paskia-js/src/overlay.ts index be53cad..584bdd4 100644 --- a/paskia-js/src/overlay.ts +++ b/paskia-js/src/overlay.ts @@ -41,15 +41,18 @@ body.paskia-backdrop { } ` -type DialogResult = 'logout' | 'back' +type DialogResult = 'login' | 'logout' | 'back' +type DialogKind = 'auth' | 'profile' let authIframe: HTMLIFrameElement | null = null let authPromise: Promise | null = null let authResolve: ((result?: DialogResult) => void) | null = null let authReject: ((error: Error) => void) | null = null // Auth flows reject AuthCancelledError on auth-back (callers rely on it to -// abort request retries); the profile dialog resolves 'back' instead. -let cancelAsError = true +// abort request retries) and resolve void on auth-success. The profile dialog +// never rejects: auth-back resolves 'back', and auth-success (the user logged +// in while the profile dialog was open) resolves 'login'. +let dialogKind: DialogKind = 'auth' let messageListenerInstalled = false let backdropHolders = 0 @@ -101,7 +104,7 @@ function handleAuthMessage(event: MessageEvent): void { case 'auth-success': hideAuthIframe() if (authResolve) { - authResolve() + authResolve(dialogKind === 'profile' ? 'login' : undefined) authPromise = null authResolve = null authReject = null @@ -110,7 +113,7 @@ function handleAuthMessage(event: MessageEvent): void { case 'auth-back': hideAuthIframe() - if (cancelAsError && authReject) { + if (dialogKind === 'auth' && authReject) { authReject(new AuthCancelledError()) } else if (authResolve) { authResolve('back') @@ -140,13 +143,13 @@ function ensureMessageListener(): void { } } -function openIframe(iframeUrl: string, title: string, dialog: boolean, cancelError: boolean): Promise { +function openIframe(iframeUrl: string, title: string, kind: DialogKind): Promise { injectStyles() ensureMessageListener() if (authPromise) return authPromise - cancelAsError = cancelError + dialogKind = kind iframeUrl = withAppTheme(iframeUrl) if (document.getElementById(AUTH_IFRAME_ID)) { @@ -167,7 +170,7 @@ function openIframe(iframeUrl: string, title: string, dialog: boolean, cancelErr authIframe = document.createElement('iframe') authIframe.id = AUTH_IFRAME_ID - if (dialog) authIframe.classList.add('paskia-dialog') + if (kind === 'profile') authIframe.classList.add('paskia-dialog') authIframe.title = title authIframe.src = iframeUrl document.body.appendChild(authIframe) @@ -201,19 +204,20 @@ function withAppTheme(iframeUrl: string): string { } export function showAuthIframe(iframeUrl: string, title = 'Authentication'): Promise { - return openIframe(iframeUrl, title, false, true).then(() => undefined) + return openIframe(iframeUrl, title, 'auth').then(() => undefined) } /** * Show the minimal profile of the logged-in user in a compact dialog iframe. * - * Unlike the auth flows, this always resolves — 'logout' when the user - * signed out inside the frame, 'back' when they closed it without action. + * Unlike the auth flows, this always resolves — 'login' when the user was + * signed out and completed the login flow inside the frame, 'logout' when + * they signed out inside the frame, 'back' when they closed it otherwise. * The caller decides from context how to react to each (e.g. whether to * start a new login attempt with showAuthIframe). */ export function profile(): Promise { - return openIframe('/auth/restricted/iframe#mode=profile', 'Profile', true, false) + return openIframe('/auth/restricted/iframe#mode=profile', 'Profile', 'profile') .then((result) => result ?? 'back') }