From c3e56152cd02c35c73eb3c3cf60210ba012a6487 Mon Sep 17 00:00:00 2001 From: Andreas Coroiu Date: Tue, 24 Oct 2023 19:33:52 +0200 Subject: [PATCH] [PM-4401] fix: wait for focus before triggering fallback (#6670) * [PM-4401] fix: wait for focus before triggering fallback * [PM-4401] feat: add timeout --- .../src/vault/fido2/content/page-script.ts | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/apps/browser/src/vault/fido2/content/page-script.ts b/apps/browser/src/vault/fido2/content/page-script.ts index 1f5d98289b..986173a4a4 100644 --- a/apps/browser/src/vault/fido2/content/page-script.ts +++ b/apps/browser/src/vault/fido2/content/page-script.ts @@ -94,6 +94,7 @@ navigator.credentials.create = async ( return WebauthnUtils.mapCredentialRegistrationResult(response.result); } catch (error) { if (error && error.fallbackRequested && fallbackSupported) { + await waitForFocus(); return await browserCredentials.create(options); } @@ -132,9 +133,47 @@ navigator.credentials.get = async ( return WebauthnUtils.mapCredentialAssertResult(response.result); } catch (error) { if (error && error.fallbackRequested && fallbackSupported) { + await waitForFocus(); return await browserCredentials.get(options); } throw error; } }; + +/** + * Wait for window to be focused. + * Safari doesn't allow scripts to trigger webauthn when window is not focused. + * + * @param timeout Maximum time to wait for focus in milliseconds. Defaults to 5 minutes. + * @returns Promise that resolves when window is focused, or rejects if timeout is reached. + */ +async function waitForFocus(timeout: number = 5 * 60 * 1000) { + if (document.hasFocus()) { + return; + } + + let focusListener; + const focusPromise = new Promise((resolve) => { + focusListener = () => resolve(); + window.addEventListener("focus", focusListener, { once: true }); + }); + + let timeoutId; + const timeoutPromise = new Promise((_, reject) => { + timeoutId = window.setTimeout( + () => + reject( + new DOMException("The operation either timed out or was not allowed.", "AbortError") + ), + timeout + ); + }); + + try { + await Promise.race([focusPromise, timeoutPromise]); + } finally { + window.removeEventListener("focus", focusListener); + window.clearTimeout(timeoutId); + } +}