diff --git a/src/lib/useRootWebAuthnAccount.ts b/src/lib/useRootWebAuthnAccount.ts index bdd661db..16a36da6 100644 --- a/src/lib/useRootWebAuthnAccount.ts +++ b/src/lib/useRootWebAuthnAccount.ts @@ -2,15 +2,19 @@ import { useQuery } from '@tanstack/react-query' import { Account } from 'viem/tempo' +import type { WebAuthnP256 } from 'viem/tempo' import { useConnection } from 'wagmi' +import { config, webAuthnRpId } from '../wagmi.config' type RootWebAuthnAccount = ReturnType +type RootWebAuthnCredential = WebAuthnP256.P256Credential type RootWebAuthnAccountProvider = { getAccount: (options: { accessKey?: boolean | undefined address?: `0x${string}` | undefined signable?: boolean | undefined }) => RootWebAuthnAccount + request: (args: { method: 'eth_accounts' }) => Promise } export function useRootWebAuthnAccount() { @@ -23,12 +27,19 @@ export function useRootWebAuthnAccount() { if (!address) throw new Error('account address not ready') if (!connector) throw new Error('connector not ready') - const provider = (await connector.getProvider()) as RootWebAuthnAccountProvider - return provider.getAccount({ - accessKey: false, - address: address as `0x${string}`, - signable: true, - }) + const provider = await connector.getProvider() + if (isRootWebAuthnAccountProvider(provider)) { + await waitForProviderAccount(provider, address as `0x${string}`) + + return provider.getAccount({ + accessKey: false, + address: address as `0x${string}`, + signable: true, + }) + } + + const credential = await waitForStoredCredential(address as `0x${string}`) + return accountFromCredential(credential) }, refetchOnReconnect: false, refetchOnWindowFocus: false, @@ -36,3 +47,58 @@ export function useRootWebAuthnAccount() { staleTime: Number.POSITIVE_INFINITY, }) } + +function accountFromCredential(credential: RootWebAuthnCredential) { + return Account.fromWebAuthnP256(credential, webAuthnRpId ? { rpId: webAuthnRpId } : undefined) +} + +function isRootWebAuthnAccountProvider(value: unknown): value is RootWebAuthnAccountProvider { + return Boolean( + value && + typeof value === 'object' && + 'getAccount' in value && + typeof value.getAccount === 'function' && + 'request' in value && + typeof value.request === 'function', + ) +} + +async function waitForProviderAccount( + provider: RootWebAuthnAccountProvider, + address: `0x${string}`, + timeoutMs = 5_000, +) { + const deadline = Date.now() + timeoutMs + const normalizedAddress = address.toLowerCase() + + while (Date.now() < deadline) { + const accounts = await provider.request({ method: 'eth_accounts' }) + if (accounts.some((account) => account.toLowerCase() === normalizedAddress)) return + + await new Promise((resolve) => setTimeout(resolve, 100)) + } + + throw new Error(`webauthn account ${address} not ready`) +} + +async function waitForStoredCredential( + address: `0x${string}`, + timeoutMs = 5_000, +): Promise { + const deadline = Date.now() + timeoutMs + const normalizedAddress = address.toLowerCase() + + while (Date.now() < deadline) { + const credential = await config.storage?.getItem('webAuthn.activeCredential') + if (credential) { + const account = accountFromCredential(credential as RootWebAuthnCredential) + if (account.address.toLowerCase() === normalizedAddress) { + return credential as RootWebAuthnCredential + } + } + + await new Promise((resolve) => setTimeout(resolve, 100)) + } + + throw new Error(`webauthn credential for ${address} not ready`) +} diff --git a/src/lib/useZoneAuthorization.ts b/src/lib/useZoneAuthorization.ts index e236c7d6..5fd3bea1 100644 --- a/src/lib/useZoneAuthorization.ts +++ b/src/lib/useZoneAuthorization.ts @@ -94,7 +94,7 @@ export function useZoneAuthorization(parameters: { authorizeMutation, error: authorizeMutation.error ?? statusQuery.error, isAuthorized: statusQuery.data !== null && statusQuery.data !== undefined, - isChecking: statusQuery.isPending, + isChecking: statusQuery.fetchStatus === 'fetching', statusQuery, } }