'use client' import * as React from 'react' import { browserSupportsWebAuthn, startAuthentication } from '@simplewebauthn/browser' import { Loader2 } from 'lucide-react' import { useT } from '@open-mercato/shared/lib/i18n/context' import { Button } from '@open-mercato/ui/primitives/button' type PasskeyChallengeVerifyProps = { loading: boolean onPrepare: () => Promise | undefined> onVerify: (payload: { response: unknown }) => Promise } function readErrorMessage(error: unknown): string { if (error && typeof error === 'object' && 'message' in error && typeof error.message === 'string') { return error.message } return 'Passkey verification failed.' } function isAbortError(error: unknown): boolean { const name = error && typeof error === 'object' && 'name' in error ? String(error.name) : '' if (name === 'AbortError' || name === 'NotAllowedError') { return true } const message = readErrorMessage(error).toLowerCase() return message.includes('cancel') || message.includes('aborted') || message.includes('not allowed') } export default function PasskeyChallengeVerify({ loading, onPrepare, onVerify, }: PasskeyChallengeVerifyProps) { const t = useT() const handleVerify = React.useCallback(async () => { if (!browserSupportsWebAuthn()) { throw new Error( t('security.login.mfaChallenge.passkey.unsupported', 'Passkeys are not supported by this browser.'), ) } try { const optionsJSON = await onPrepare() if (!optionsJSON || typeof optionsJSON !== 'object') { throw new Error('Passkey options are missing from challenge response.') } const response = await startAuthentication({ optionsJSON: optionsJSON as never }) await onVerify({ response }) } catch (error) { if (isAbortError(error)) { return } throw error } }, [onPrepare, onVerify, t]) return (
{ event.preventDefault() if (loading) return void handleVerify() }} >

{t('security.login.mfaChallenge.passkey.title', 'Two-factor authentication')}

{t('security.login.mfaChallenge.passkey.description', 'Authenticate using your security key.')}

) }