@tatchi-xyz/sdk 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/EmailRecovery/index.js +25 -0
- package/dist/cjs/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js +70 -34
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/core/types/emailRecovery.js +33 -0
- package/dist/cjs/core/types/emailRecovery.js.map +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BCrFe5p3.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → cjs/react/components/AccountMenuButton/LinkedDevicesModal-BCrFe5p3.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-CRJrtxDb.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → cjs/react/components/AccountMenuButton/ProfileDropdown-CRJrtxDb.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-DXFRw8ND.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-DXFRw8ND.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-DNgbAK_i.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-DNgbAK_i.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DRwSoF8q.css} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DRwSoF8q.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +107 -21
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CL4gsszN.css} +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CL4gsszN.css.map} +1 -1
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js +25 -0
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js +70 -34
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js +33 -0
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/core/EmailRecovery/index.js +25 -1
- package/dist/esm/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/emailRecovery.js +71 -35
- package/dist/esm/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/core/types/emailRecovery.js +26 -0
- package/dist/esm/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BCrFe5p3.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → esm/react/components/AccountMenuButton/LinkedDevicesModal-BCrFe5p3.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-CRJrtxDb.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → esm/react/components/AccountMenuButton/ProfileDropdown-CRJrtxDb.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-DXFRw8ND.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-DXFRw8ND.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-DNgbAK_i.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-DNgbAK_i.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DRwSoF8q.css} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DRwSoF8q.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +107 -21
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CL4gsszN.css} +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CL4gsszN.css.map} +1 -1
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js +25 -1
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js +71 -35
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js +26 -0
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/sdk/wallet-iframe-host.js +111 -33
- package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts +8 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts +3 -1
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts.map +1 -1
- package/dist/types/src/core/types/emailRecovery.d.ts +10 -0
- package/dist/types/src/core/types/emailRecovery.d.ts.map +1 -0
- package/dist/types/src/core/types/index.d.ts +1 -0
- package/dist/types/src/core/types/index.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
- package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"EmailRecoverySlide.js","names":["EmailRecoverySlide: React.FC<EmailRecoverySlideProps>","localEmails: string[]","info: EmailRecoveryAccountInfo | null","err: any"],"sources":["../../../../../../src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.tsx"],"sourcesContent":["import React from 'react';\n\nimport type { EmailRecoverySSEEvent } from '@/core/types/sdkSentEvents';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport { IndexedDBManager } from '@/core/IndexedDBManager';\nimport { toAccountId } from '@/core/types/accountIds';\n\nexport interface EmailRecoverySlideProps {\n tatchiPasskey: TatchiPasskey;\n accountId: string;\n refreshLoginState?: (nearAccountId?: string) => Promise<void>;\n emailRecoveryOptions?: {\n onEvent?: (event: EmailRecoverySSEEvent) => void;\n onError?: (error: Error) => void;\n };\n}\n\ntype EmailRecoveryPolicy = {\n minRequiredEmails?: number;\n maxAgeMs?: number;\n};\n\ntype EmailRecoveryAccountInfo = {\n emailsCount: number;\n};\n\ntype MailtoUiState = 'ready' | 'opening';\n\nexport const EmailRecoverySlide: React.FC<EmailRecoverySlideProps> = ({ tatchiPasskey, accountId, refreshLoginState, emailRecoveryOptions }) => {\n const mountedRef = React.useRef(true);\n const mailtoAttemptTimerRef = React.useRef<number | null>(null);\n const cancelRequestedRef = React.useRef(false);\n React.useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n mailtoAttemptTimerRef.current = null;\n }\n };\n }, []);\n\n const [isBusy, setIsBusy] = React.useState(false);\n const [accountIdInput, setAccountIdInput] = React.useState('');\n const [recoveryEmailInput, setRecoveryEmailInput] = React.useState('');\n const [pendingMailtoUrl, setPendingMailtoUrl] = React.useState<string | null>(null);\n const [mailtoUiState, setMailtoUiState] = React.useState<MailtoUiState>('ready');\n const [statusText, setStatusText] = React.useState<string | null>(null);\n const [pollingElapsedMs, setPollingElapsedMs] = React.useState<number | null>(null);\n const [errorText, setErrorText] = React.useState<string | null>(null);\n const [accountInfo, setAccountInfo] = React.useState<EmailRecoveryAccountInfo | null>(null);\n const [accountInfoLoading, setAccountInfoLoading] = React.useState(false);\n const [accountInfoError, setAccountInfoError] = React.useState<string | null>(null);\n const [localRecoveryEmails, setLocalRecoveryEmails] = React.useState<string[]>([]);\n const [explorerToast, setExplorerToast] = React.useState<{ url: string; accountId?: string; transactionHash?: string } | null>(null);\n\n const lastPrefilledAccountIdRef = React.useRef<string>('');\n const lastPrefilledRecoveryEmailRef = React.useRef<string>('');\n\n React.useEffect(() => {\n const next = (accountId || '').trim();\n if (!next) return;\n if (accountIdInput.trim() === '' || accountIdInput === lastPrefilledAccountIdRef.current) {\n lastPrefilledAccountIdRef.current = next;\n setAccountIdInput(next);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [accountId]);\n\n React.useEffect(() => {\n setPendingMailtoUrl(null);\n setMailtoUiState('ready');\n cancelRequestedRef.current = false;\n setStatusText(null);\n setPollingElapsedMs(null);\n setErrorText(null);\n setAccountInfo(null);\n setAccountInfoError(null);\n setLocalRecoveryEmails([]);\n setExplorerToast(null);\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n mailtoAttemptTimerRef.current = null;\n }\n }, [accountId]);\n\n const safeSet = <T,>(setter: React.Dispatch<React.SetStateAction<T>>) => {\n return (value: React.SetStateAction<T>) => {\n if (!mountedRef.current) return;\n setter(value);\n };\n };\n\n const safeSetPendingMailtoUrl = React.useMemo(() => safeSet(setPendingMailtoUrl), []);\n const safeSetStatusText = React.useMemo(() => safeSet(setStatusText), []);\n const safeSetPollingElapsedMs = React.useMemo(() => safeSet(setPollingElapsedMs), []);\n const safeSetErrorText = React.useMemo(() => safeSet(setErrorText), []);\n const safeSetIsBusy = React.useMemo(() => safeSet(setIsBusy), []);\n const safeSetAccountInfo = React.useMemo(() => safeSet(setAccountInfo), []);\n const safeSetAccountInfoLoading = React.useMemo(() => safeSet(setAccountInfoLoading), []);\n const safeSetAccountInfoError = React.useMemo(() => safeSet(setAccountInfoError), []);\n const safeSetLocalRecoveryEmails = React.useMemo(() => safeSet(setLocalRecoveryEmails), []);\n const safeSetExplorerToast = React.useMemo(() => safeSet(setExplorerToast), []);\n const safeSetMailtoUiState = React.useMemo(() => safeSet(setMailtoUiState), []);\n\n const onEvent = React.useCallback(\n (ev: EmailRecoverySSEEvent) => {\n if (cancelRequestedRef.current) return;\n safeSetStatusText(ev?.message || null);\n emailRecoveryOptions?.onEvent?.(ev);\n\n const data = (ev as any)?.data || {};\n const rawTxHash = data?.transactionHash ?? data?.transaction_hash;\n const txHash = typeof rawTxHash === 'string' ? rawTxHash.trim() : '';\n if (txHash) {\n const base = String(tatchiPasskey.configs?.nearExplorerUrl || 'https://testnet.nearblocks.io').replace(/\\/$/, '');\n const url = base.includes('nearblocks.io')\n ? `${base}/txns/${txHash}`\n : `${base}/transactions/${txHash}`;\n safeSetExplorerToast({ url, transactionHash: txHash });\n }\n const elapsedRaw = data?.elapsedMs ?? data?.elapsed_ms;\n if (elapsedRaw == null) safeSetPollingElapsedMs(null);\n const elapsed = elapsedRaw == null ? Number.NaN : Number(elapsedRaw);\n if (!Number.isNaN(elapsed)) safeSetPollingElapsedMs(elapsed);\n\n if (ev?.phase === 'email-recovery-error' || (ev as any)?.status === 'error') {\n const raw = (ev as any)?.error || ev?.message || 'Email recovery failed';\n safeSetErrorText(String(raw));\n }\n },\n [emailRecoveryOptions, safeSetErrorText, safeSetExplorerToast, safeSetPollingElapsedMs, safeSetStatusText, tatchiPasskey],\n );\n\n const showExplorerToast = React.useCallback(\n (rawAccountId: string) => {\n const normalized = (rawAccountId || '').trim();\n if (!normalized) return;\n const base = String(tatchiPasskey.configs?.nearExplorerUrl || 'https://testnet.nearblocks.io').replace(/\\/$/, '');\n const url = base.includes('nearblocks.io')\n ? `${base}/address/${normalized}`\n : `${base}/accounts/${normalized}`;\n\n safeSetExplorerToast({ url, accountId: normalized });\n },\n [safeSetExplorerToast, tatchiPasskey],\n );\n\n const launchMailto = React.useCallback((rawMailtoUrl: string) => {\n const url = String(rawMailtoUrl || '').trim();\n if (!url) return;\n\n if (typeof window !== 'undefined') {\n try {\n window.location.href = url;\n } catch {}\n }\n }, []);\n\n const attemptOpenMailtoFromUserGesture = React.useCallback(\n (rawMailtoUrl: string) => {\n const url = String(rawMailtoUrl || '').trim();\n if (!url) return;\n\n safeSetMailtoUiState('opening');\n\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n }\n\n // If the browser never blurs/hides (i.e. mailto blocked or cancelled), re-enable so users can retry.\n mailtoAttemptTimerRef.current = window.setTimeout(() => {\n safeSetMailtoUiState(prev => (prev === 'opening' ? 'ready' : prev));\n mailtoAttemptTimerRef.current = null;\n }, 2_000);\n\n launchMailto(url);\n },\n [launchMailto, safeSetMailtoUiState],\n );\n\n const attemptOpenMailtoAuto = React.useCallback(\n (rawMailtoUrl: string) => {\n const url = String(rawMailtoUrl || '').trim();\n if (!url) return;\n // Best-effort only: do not change `mailtoUiState` so users can immediately click the CTA.\n launchMailto(url);\n },\n [launchMailto],\n );\n\n React.useEffect(() => {\n if (mailtoUiState !== 'opening') return;\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n\n // Heuristic signals that the mail client likely opened. Treat as a hint only:\n // re-enable immediately so the CTA remains retryable even if this is a false-positive.\n const markMaybeOpened = () => {\n safeSetMailtoUiState('ready');\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n mailtoAttemptTimerRef.current = null;\n }\n };\n\n const onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') markMaybeOpened();\n };\n\n window.addEventListener('blur', markMaybeOpened);\n window.addEventListener('pagehide', markMaybeOpened);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n window.removeEventListener('blur', markMaybeOpened);\n window.removeEventListener('pagehide', markMaybeOpened);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n };\n }, [mailtoUiState, safeSetMailtoUiState]);\n\n const fetchLocalRecoveryEmailsFromIndexedDB = React.useCallback(\n async (rawAccountId: string): Promise<string[]> => {\n const normalized = (rawAccountId || '').trim();\n if (!normalized) {\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: empty accountId');\n return [];\n }\n\n try {\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: loading from IndexedDB', { accountId: normalized });\n const records = await IndexedDBManager.getRecoveryEmails(toAccountId(normalized));\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: raw IndexedDB records', {\n accountId: normalized,\n count: Array.isArray(records) ? records.length : 0,\n records,\n });\n if (!Array.isArray(records) || records.length === 0) return [];\n\n const sorted = [...records].sort((a, b) => (b?.addedAt || 0) - (a?.addedAt || 0));\n const emails = sorted\n .map(r => String(r?.email || '').trim().toLowerCase())\n .filter(e => !!e && e.includes('@'));\n\n const uniq = Array.from(new Set(emails));\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: parsed emails', {\n accountId: normalized,\n emails: uniq,\n });\n return uniq;\n } catch (err) {\n // best-effort; treat as no saved emails (e.g., IndexedDB unavailable)\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: failed to read IndexedDB', {\n accountId: normalized,\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n },\n [],\n );\n\n const deriveEmailsFromRecoveryRecords = React.useCallback((records: unknown): string[] => {\n if (!Array.isArray(records) || records.length === 0) return [];\n const emails = records\n .map((r: any) => String(r?.email || '').trim().toLowerCase())\n .filter((e: string) => !!e && e.includes('@'));\n return Array.from(new Set(emails));\n }, []);\n\n React.useEffect(() => {\n const normalized = (accountIdInput || '').trim();\n if (!normalized) {\n setAccountInfo(null);\n setAccountInfoError(null);\n setAccountInfoLoading(false);\n safeSetLocalRecoveryEmails([]);\n return;\n }\n\n let cancelled = false;\n // Show loading state immediately (don't wait for debounce).\n safeSetAccountInfoLoading(true);\n safeSetAccountInfoError(null);\n const handle = window.setTimeout(() => {\n void (async () => {\n try {\n const isWalletIframeMode = !!tatchiPasskey.configs?.iframeWallet?.walletOrigin;\n\n // Legacy mode: suggest recovery emails from local IndexedDB mapping (best-effort).\n let localEmails: string[] = [];\n if (!isWalletIframeMode) {\n localEmails = await fetchLocalRecoveryEmailsFromIndexedDB(normalized);\n if (!cancelled) {\n console.log('[EmailRecoverySlide] local saved emails (IndexedDB)', { accountId: normalized, localEmails });\n }\n }\n\n const records = await tatchiPasskey.getRecoveryEmails(normalized);\n const resolvedEmails = isWalletIframeMode\n ? deriveEmailsFromRecoveryRecords(records)\n : localEmails;\n\n if (!cancelled) {\n safeSetLocalRecoveryEmails(resolvedEmails);\n console.log('[EmailRecoverySlide] recovery email suggestions (state)', { accountId: normalized, emails: resolvedEmails });\n\n if (\n resolvedEmails.length === 1 &&\n (recoveryEmailInput.trim() === '' || recoveryEmailInput === lastPrefilledRecoveryEmailRef.current)\n ) {\n lastPrefilledRecoveryEmailRef.current = resolvedEmails[0];\n setRecoveryEmailInput(resolvedEmails[0]);\n }\n }\n\n const info: EmailRecoveryAccountInfo | null = records\n ? { emailsCount: Array.isArray(records) ? records.length : 0 }\n : null;\n if (cancelled) return;\n safeSetAccountInfo(info);\n } catch (err: any) {\n if (cancelled) return;\n safeSetAccountInfo(null);\n safeSetAccountInfoError(err?.message || 'Failed to load email recovery settings for this account');\n } finally {\n if (!cancelled) safeSetAccountInfoLoading(false);\n }\n })();\n }, 350);\n\n return () => {\n cancelled = true;\n window.clearTimeout(handle);\n };\n }, [\n accountIdInput,\n deriveEmailsFromRecoveryRecords,\n fetchLocalRecoveryEmailsFromIndexedDB,\n recoveryEmailInput,\n safeSetAccountInfo,\n safeSetAccountInfoError,\n safeSetAccountInfoLoading,\n safeSetLocalRecoveryEmails,\n tatchiPasskey,\n ]);\n\n const handleStart = React.useCallback(async () => {\n const normalizedAccountId = (accountIdInput || '').trim();\n if (!normalizedAccountId) {\n safeSetErrorText('Enter an account ID.');\n return;\n }\n\n const emailCandidate = (recoveryEmailInput || '').trim().toLowerCase();\n if (!emailCandidate) {\n safeSetErrorText('Enter the recovery email to send from.');\n return;\n }\n\n safeSetIsBusy(true);\n cancelRequestedRef.current = false;\n safeSetErrorText(null);\n safeSetStatusText(null);\n safeSetPollingElapsedMs(null);\n safeSetPendingMailtoUrl(null);\n safeSetMailtoUiState('ready');\n\n let didForwardError = false;\n try {\n const result = await tatchiPasskey.startEmailRecovery({\n accountId: normalizedAccountId,\n recoveryEmail: emailCandidate,\n options: {\n onEvent,\n onError: (err: Error) => {\n if (cancelRequestedRef.current) return;\n safeSetErrorText(err?.message || 'Failed to start email recovery');\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n afterCall: async () => {},\n } as any,\n });\n\n safeSetPendingMailtoUrl(result.mailtoUrl);\n safeSetStatusText('Recovery email draft ready. If it didn’t open automatically, click “Open recovery email draft”. Waiting for verification…');\n\n // Best-effort open. If blocked/cancelled, the CTA remains immediately clickable for a user-gesture retry.\n attemptOpenMailtoAuto(result.mailtoUrl);\n\n // Start polling immediately after attempting to open the email prompt.\n const finalizePromise = tatchiPasskey.finalizeEmailRecovery({\n accountId: normalizedAccountId,\n nearPublicKey: result.nearPublicKey,\n options: {\n onEvent,\n onError: (err: Error) => {\n if (cancelRequestedRef.current) return;\n safeSetErrorText(err?.message || 'Failed to finalize email recovery');\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n afterCall: async () => {},\n } as any,\n });\n\n showExplorerToast(normalizedAccountId);\n\n await finalizePromise;\n\n // Best-effort auto-login: the core flow attempts it, but if it couldn't (e.g. missing Shamir\n // auto-unlock and user cancelled TouchID), try once more here.\n let loginOk = false;\n const session = await tatchiPasskey.getLoginSession(normalizedAccountId).catch(() => null);\n if (session?.login?.isLoggedIn) {\n loginOk = true;\n } else {\n safeSetStatusText('Email recovery completed. Logging you in…');\n loginOk = await tatchiPasskey.loginAndCreateSession(normalizedAccountId)\n .then(() => true)\n .catch(() => false);\n }\n\n if (refreshLoginState) {\n await refreshLoginState(normalizedAccountId).catch(() => {});\n }\n\n safeSetStatusText(loginOk ? 'Email recovery completed on this device.' : 'Email recovery completed. Please log in on this device.');\n safeSetPendingMailtoUrl(null);\n safeSetMailtoUiState('ready');\n safeSetPollingElapsedMs(null);\n } catch (err: any) {\n if (cancelRequestedRef.current) {\n safeSetErrorText('Email recovery cancelled. Please try again.');\n safeSetStatusText(null);\n safeSetPollingElapsedMs(null);\n safeSetPendingMailtoUrl(null);\n safeSetMailtoUiState('ready');\n return;\n }\n safeSetErrorText(err?.message || 'Failed to start email recovery');\n if (!didForwardError && err instanceof Error) {\n emailRecoveryOptions?.onError?.(err);\n }\n } finally {\n safeSetIsBusy(false);\n }\n }, [\n accountIdInput,\n emailRecoveryOptions,\n recoveryEmailInput,\n onEvent,\n refreshLoginState,\n showExplorerToast,\n safeSetErrorText,\n safeSetIsBusy,\n safeSetPendingMailtoUrl,\n safeSetPollingElapsedMs,\n safeSetStatusText,\n safeSetMailtoUiState,\n attemptOpenMailtoAuto,\n tatchiPasskey,\n ]);\n\n const summaryLine = accountInfoLoading\n ? 'Checking if account has recovery emails configured...'\n : accountInfo && !accountInfoError\n ? `Recovery emails configured: ${accountInfo.emailsCount}`\n : '\\u00A0';\n\n const noRecoveryEmailsConfigured =\n !accountInfoLoading && !accountInfoError && !!accountInfo && accountInfo.emailsCount === 0;\n\n return (\n <div className=\"w3a-email-recovery-slide\">\n <div className=\"w3a-email-recovery-title\">Recover Account with Email</div>\n <div className=\"w3a-email-recovery-help\">\n Send a special recovery email from your recovery email address.\n Your account will be recovered with a new key once the email is verified.\n </div>\n\n <div className=\"w3a-email-recovery-field\">\n <div className=\"w3a-input-pill w3a-email-recovery-input-pill\">\n <div className=\"w3a-input-wrap\">\n <input\n type=\"text\"\n value={accountIdInput}\n onChange={(e) => setAccountIdInput(e.target.value)}\n placeholder=\"NEAR account ID (e.g. alice.testnet)\"\n className=\"w3a-input\"\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"text\"\n disabled={isBusy}\n />\n </div>\n </div>\n </div>\n\n <div className=\"w3a-email-recovery-summary\" aria-live=\"polite\">\n <div>{summaryLine}</div>\n </div>\n\n <div className=\"w3a-email-recovery-field\">\n <div className=\"w3a-input-pill w3a-email-recovery-input-pill\">\n <div className=\"w3a-input-wrap\">\n <input\n type=\"email\"\n value={recoveryEmailInput}\n onChange={(e) => setRecoveryEmailInput(e.target.value)}\n placeholder=\"Recovery email to send from\"\n className=\"w3a-input\"\n list={localRecoveryEmails.length > 0 ? 'w3a-email-recovery-saved-emails' : undefined}\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"email\"\n disabled={isBusy || noRecoveryEmailsConfigured}\n />\n </div>\n </div>\n </div>\n\n {localRecoveryEmails.length > 0 && (\n <div className=\"w3a-email-recovery-summary\" aria-live=\"polite\">\n <div>Saved on this device:</div>\n <div className=\"w3a-email-recovery-saved-emails\">\n {localRecoveryEmails.map((email) => (\n <button\n key={email}\n type=\"button\"\n className=\"w3a-email-recovery-email-chip\"\n onClick={() => setRecoveryEmailInput(email)}\n disabled={isBusy}\n >\n {email}\n </button>\n ))}\n </div>\n </div>\n )}\n\n {localRecoveryEmails.length > 0 && (\n <datalist id=\"w3a-email-recovery-saved-emails\">\n {localRecoveryEmails.map((email) => (\n <option key={email} value={email} />\n ))}\n </datalist>\n )}\n\n <div className=\"w3a-email-recovery-actions\">\n {(!pendingMailtoUrl || !isBusy) && (\n <button\n onClick={handleStart}\n className=\"w3a-link-device-btn w3a-link-device-btn-primary\"\n disabled={isBusy || noRecoveryEmailsConfigured}\n >\n {noRecoveryEmailsConfigured ? 'No recovery emails configured' : (isBusy ? 'Working…' : 'Start Email Recovery')}\n </button>\n )}\n\n {pendingMailtoUrl && (\n <button\n type=\"button\"\n onClick={() => attemptOpenMailtoFromUserGesture(pendingMailtoUrl)}\n className=\"w3a-link-device-btn w3a-link-device-btn-primary\"\n disabled={mailtoUiState === 'opening'}\n aria-busy={mailtoUiState === 'opening'}\n >\n {mailtoUiState === 'opening' && <span className=\"w3a-spinner\" aria-hidden=\"true\" />}\n {mailtoUiState === 'opening' ? 'Opening email…' : 'Open recovery email draft'}\n </button>\n )}\n </div>\n\n {(errorText || statusText || explorerToast) && (\n <div className={`w3a-email-recovery-status${errorText ? ' is-error' : ''}`}>\n {errorText ? errorText : statusText}\n {pollingElapsedMs != null && !Number.isNaN(pollingElapsedMs) && pollingElapsedMs > 0 && (\n <span className=\"w3a-email-recovery-elapsed\">\n (~{Math.round(pollingElapsedMs / 1000)}s).\n </span>\n )}\n {explorerToast && (\n <>\n <br />\n <a className=\"w3a-email-recovery-link\" href={explorerToast.url} target=\"_blank\" rel=\"noopener noreferrer\">\n View on explorer\n </a>\n </>\n )}\n </div>\n )}\n </div>\n );\n};\n\nexport default EmailRecoverySlide;\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"EmailRecoverySlide.js","names":["EmailRecoverySlide: React.FC<EmailRecoverySlideProps>","localEmails: string[]","info: EmailRecoveryAccountInfo | null","err: any"],"sources":["../../../../../../src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.tsx"],"sourcesContent":["import React from 'react';\n\nimport type { EmailRecoverySSEEvent } from '@/core/types/sdkSentEvents';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport { IndexedDBManager } from '@/core/IndexedDBManager';\nimport { toAccountId } from '@/core/types/accountIds';\nimport { EmailRecoveryErrorCode } from '@/core/types/emailRecovery';\n\nexport interface EmailRecoverySlideProps {\n tatchiPasskey: TatchiPasskey;\n accountId: string;\n refreshLoginState?: (nearAccountId?: string) => Promise<void>;\n emailRecoveryOptions?: {\n onEvent?: (event: EmailRecoverySSEEvent) => void;\n onError?: (error: Error) => void;\n };\n}\n\ntype EmailRecoveryPolicy = {\n minRequiredEmails?: number;\n maxAgeMs?: number;\n};\n\ntype EmailRecoveryAccountInfo = {\n emailsCount: number;\n};\n\ntype MailtoUiState = 'ready' | 'opening';\n\nfunction getEmailRecoveryErrorCode(err: unknown): EmailRecoveryErrorCode | null {\n const code = (err as { code?: unknown } | null)?.code;\n if (typeof code !== 'string') return null;\n return Object.values(EmailRecoveryErrorCode).includes(code as EmailRecoveryErrorCode)\n ? (code as EmailRecoveryErrorCode)\n : null;\n}\n\nfunction getEmailRecoveryUiError(err: unknown): { message: string; canRestart: boolean } {\n const fallback = err instanceof Error ? err.message : String(err || '');\n const code = getEmailRecoveryErrorCode(err);\n switch (code) {\n case EmailRecoveryErrorCode.VRF_CHALLENGE_EXPIRED:\n return {\n message: fallback || 'Timed out finalizing registration (VRF challenge expired). Please restart email recovery and try again.',\n canRestart: true,\n };\n case EmailRecoveryErrorCode.REGISTRATION_NOT_VERIFIED:\n return {\n message: fallback || 'Registration did not verify on-chain. Please restart email recovery and try again.',\n canRestart: true,\n };\n default:\n return { message: fallback || 'Email recovery failed', canRestart: false };\n }\n}\n\nexport const EmailRecoverySlide: React.FC<EmailRecoverySlideProps> = ({ tatchiPasskey, accountId, refreshLoginState, emailRecoveryOptions }) => {\n const mountedRef = React.useRef(true);\n const mailtoAttemptTimerRef = React.useRef<number | null>(null);\n const cancelRequestedRef = React.useRef(false);\n React.useEffect(() => {\n mountedRef.current = true;\n return () => {\n mountedRef.current = false;\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n mailtoAttemptTimerRef.current = null;\n }\n };\n }, []);\n\n const [isBusy, setIsBusy] = React.useState(false);\n const [accountIdInput, setAccountIdInput] = React.useState('');\n const [recoveryEmailInput, setRecoveryEmailInput] = React.useState('');\n const [pendingMailtoUrl, setPendingMailtoUrl] = React.useState<string | null>(null);\n const [pendingNearPublicKey, setPendingNearPublicKey] = React.useState<string | null>(null);\n const [mailtoUiState, setMailtoUiState] = React.useState<MailtoUiState>('ready');\n const [statusText, setStatusText] = React.useState<string | null>(null);\n const [pollingElapsedMs, setPollingElapsedMs] = React.useState<number | null>(null);\n const [errorText, setErrorText] = React.useState<string | null>(null);\n const [canRestart, setCanRestart] = React.useState(false);\n const [accountInfo, setAccountInfo] = React.useState<EmailRecoveryAccountInfo | null>(null);\n const [accountInfoLoading, setAccountInfoLoading] = React.useState(false);\n const [accountInfoError, setAccountInfoError] = React.useState<string | null>(null);\n const [localRecoveryEmails, setLocalRecoveryEmails] = React.useState<string[]>([]);\n const [explorerToast, setExplorerToast] = React.useState<{ url: string; accountId?: string; transactionHash?: string } | null>(null);\n\n const lastPrefilledAccountIdRef = React.useRef<string>('');\n const lastPrefilledRecoveryEmailRef = React.useRef<string>('');\n\n React.useEffect(() => {\n const next = (accountId || '').trim();\n if (!next) return;\n if (accountIdInput.trim() === '' || accountIdInput === lastPrefilledAccountIdRef.current) {\n lastPrefilledAccountIdRef.current = next;\n setAccountIdInput(next);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [accountId]);\n\n React.useEffect(() => {\n setPendingMailtoUrl(null);\n setPendingNearPublicKey(null);\n setMailtoUiState('ready');\n cancelRequestedRef.current = false;\n setStatusText(null);\n setPollingElapsedMs(null);\n setErrorText(null);\n setCanRestart(false);\n setAccountInfo(null);\n setAccountInfoError(null);\n setLocalRecoveryEmails([]);\n setExplorerToast(null);\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n mailtoAttemptTimerRef.current = null;\n }\n }, [accountId]);\n\n const safeSet = <T,>(setter: React.Dispatch<React.SetStateAction<T>>) => {\n return (value: React.SetStateAction<T>) => {\n if (!mountedRef.current) return;\n setter(value);\n };\n };\n\n const safeSetPendingMailtoUrl = React.useMemo(() => safeSet(setPendingMailtoUrl), []);\n const safeSetPendingNearPublicKey = React.useMemo(() => safeSet(setPendingNearPublicKey), []);\n const safeSetStatusText = React.useMemo(() => safeSet(setStatusText), []);\n const safeSetPollingElapsedMs = React.useMemo(() => safeSet(setPollingElapsedMs), []);\n const safeSetErrorText = React.useMemo(() => safeSet(setErrorText), []);\n const safeSetIsBusy = React.useMemo(() => safeSet(setIsBusy), []);\n const safeSetCanRestart = React.useMemo(() => safeSet(setCanRestart), []);\n const safeSetAccountInfo = React.useMemo(() => safeSet(setAccountInfo), []);\n const safeSetAccountInfoLoading = React.useMemo(() => safeSet(setAccountInfoLoading), []);\n const safeSetAccountInfoError = React.useMemo(() => safeSet(setAccountInfoError), []);\n const safeSetLocalRecoveryEmails = React.useMemo(() => safeSet(setLocalRecoveryEmails), []);\n const safeSetExplorerToast = React.useMemo(() => safeSet(setExplorerToast), []);\n const safeSetMailtoUiState = React.useMemo(() => safeSet(setMailtoUiState), []);\n\n const onEvent = React.useCallback(\n (ev: EmailRecoverySSEEvent) => {\n if (cancelRequestedRef.current) return;\n safeSetStatusText(ev?.message || null);\n emailRecoveryOptions?.onEvent?.(ev);\n\n const data = (ev as any)?.data || {};\n const rawTxHash = data?.transactionHash ?? data?.transaction_hash;\n const txHash = typeof rawTxHash === 'string' ? rawTxHash.trim() : '';\n if (txHash) {\n const base = String(tatchiPasskey.configs?.nearExplorerUrl || 'https://testnet.nearblocks.io').replace(/\\/$/, '');\n const url = base.includes('nearblocks.io')\n ? `${base}/txns/${txHash}`\n : `${base}/transactions/${txHash}`;\n safeSetExplorerToast({ url, transactionHash: txHash });\n }\n const elapsedRaw = data?.elapsedMs ?? data?.elapsed_ms;\n if (elapsedRaw == null) safeSetPollingElapsedMs(null);\n const elapsed = elapsedRaw == null ? Number.NaN : Number(elapsedRaw);\n if (!Number.isNaN(elapsed)) safeSetPollingElapsedMs(elapsed);\n\n if (ev?.phase === 'email-recovery-error' || (ev as any)?.status === 'error') {\n const raw = (ev as any)?.error || ev?.message || 'Email recovery failed';\n safeSetErrorText(String(raw));\n safeSetCanRestart(false);\n }\n },\n [emailRecoveryOptions, safeSetCanRestart, safeSetErrorText, safeSetExplorerToast, safeSetPollingElapsedMs, safeSetStatusText, tatchiPasskey],\n );\n\n const showExplorerToast = React.useCallback(\n (rawAccountId: string) => {\n const normalized = (rawAccountId || '').trim();\n if (!normalized) return;\n const base = String(tatchiPasskey.configs?.nearExplorerUrl || 'https://testnet.nearblocks.io').replace(/\\/$/, '');\n const url = base.includes('nearblocks.io')\n ? `${base}/address/${normalized}`\n : `${base}/accounts/${normalized}`;\n\n safeSetExplorerToast({ url, accountId: normalized });\n },\n [safeSetExplorerToast, tatchiPasskey],\n );\n\n const launchMailto = React.useCallback((rawMailtoUrl: string) => {\n const url = String(rawMailtoUrl || '').trim();\n if (!url) return;\n\n if (typeof window !== 'undefined') {\n try {\n window.location.href = url;\n } catch {}\n }\n }, []);\n\n const attemptOpenMailtoFromUserGesture = React.useCallback(\n (rawMailtoUrl: string) => {\n const url = String(rawMailtoUrl || '').trim();\n if (!url) return;\n\n safeSetMailtoUiState('opening');\n\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n }\n\n // If the browser never blurs/hides (i.e. mailto blocked or cancelled), re-enable so users can retry.\n mailtoAttemptTimerRef.current = window.setTimeout(() => {\n safeSetMailtoUiState(prev => (prev === 'opening' ? 'ready' : prev));\n mailtoAttemptTimerRef.current = null;\n }, 2_000);\n\n launchMailto(url);\n },\n [launchMailto, safeSetMailtoUiState],\n );\n\n const attemptOpenMailtoAuto = React.useCallback(\n (rawMailtoUrl: string) => {\n const url = String(rawMailtoUrl || '').trim();\n if (!url) return;\n // Best-effort only: do not change `mailtoUiState` so users can immediately click the CTA.\n launchMailto(url);\n },\n [launchMailto],\n );\n\n React.useEffect(() => {\n if (mailtoUiState !== 'opening') return;\n if (typeof window === 'undefined' || typeof document === 'undefined') return;\n\n // Heuristic signals that the mail client likely opened. Treat as a hint only:\n // re-enable immediately so the CTA remains retryable even if this is a false-positive.\n const markMaybeOpened = () => {\n safeSetMailtoUiState('ready');\n if (mailtoAttemptTimerRef.current != null) {\n window.clearTimeout(mailtoAttemptTimerRef.current);\n mailtoAttemptTimerRef.current = null;\n }\n };\n\n const onVisibilityChange = () => {\n if (document.visibilityState === 'hidden') markMaybeOpened();\n };\n\n window.addEventListener('blur', markMaybeOpened);\n window.addEventListener('pagehide', markMaybeOpened);\n document.addEventListener('visibilitychange', onVisibilityChange);\n\n return () => {\n window.removeEventListener('blur', markMaybeOpened);\n window.removeEventListener('pagehide', markMaybeOpened);\n document.removeEventListener('visibilitychange', onVisibilityChange);\n };\n }, [mailtoUiState, safeSetMailtoUiState]);\n\n const fetchLocalRecoveryEmailsFromIndexedDB = React.useCallback(\n async (rawAccountId: string): Promise<string[]> => {\n const normalized = (rawAccountId || '').trim();\n if (!normalized) {\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: empty accountId');\n return [];\n }\n\n try {\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: loading from IndexedDB', { accountId: normalized });\n const records = await IndexedDBManager.getRecoveryEmails(toAccountId(normalized));\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: raw IndexedDB records', {\n accountId: normalized,\n count: Array.isArray(records) ? records.length : 0,\n records,\n });\n if (!Array.isArray(records) || records.length === 0) return [];\n\n const sorted = [...records].sort((a, b) => (b?.addedAt || 0) - (a?.addedAt || 0));\n const emails = sorted\n .map(r => String(r?.email || '').trim().toLowerCase())\n .filter(e => !!e && e.includes('@'));\n\n const uniq = Array.from(new Set(emails));\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: parsed emails', {\n accountId: normalized,\n emails: uniq,\n });\n return uniq;\n } catch (err) {\n // best-effort; treat as no saved emails (e.g., IndexedDB unavailable)\n console.log('[EmailRecoverySlide] fetchLocalRecoveryEmails: failed to read IndexedDB', {\n accountId: normalized,\n error: err instanceof Error ? err.message : String(err),\n });\n return [];\n }\n },\n [],\n );\n\n const deriveEmailsFromRecoveryRecords = React.useCallback((records: unknown): string[] => {\n if (!Array.isArray(records) || records.length === 0) return [];\n const emails = records\n .map((r: any) => String(r?.email || '').trim().toLowerCase())\n .filter((e: string) => !!e && e.includes('@'));\n return Array.from(new Set(emails));\n }, []);\n\n React.useEffect(() => {\n const normalized = (accountIdInput || '').trim();\n if (!normalized) {\n setAccountInfo(null);\n setAccountInfoError(null);\n setAccountInfoLoading(false);\n safeSetLocalRecoveryEmails([]);\n return;\n }\n\n let cancelled = false;\n // Show loading state immediately (don't wait for debounce).\n safeSetAccountInfoLoading(true);\n safeSetAccountInfoError(null);\n const handle = window.setTimeout(() => {\n void (async () => {\n try {\n const isWalletIframeMode = !!tatchiPasskey.configs?.iframeWallet?.walletOrigin;\n\n // Legacy mode: suggest recovery emails from local IndexedDB mapping (best-effort).\n let localEmails: string[] = [];\n if (!isWalletIframeMode) {\n localEmails = await fetchLocalRecoveryEmailsFromIndexedDB(normalized);\n if (!cancelled) {\n console.log('[EmailRecoverySlide] local saved emails (IndexedDB)', { accountId: normalized, localEmails });\n }\n }\n\n const records = await tatchiPasskey.getRecoveryEmails(normalized);\n const resolvedEmails = isWalletIframeMode\n ? deriveEmailsFromRecoveryRecords(records)\n : localEmails;\n\n if (!cancelled) {\n safeSetLocalRecoveryEmails(resolvedEmails);\n console.log('[EmailRecoverySlide] recovery email suggestions (state)', { accountId: normalized, emails: resolvedEmails });\n\n if (\n resolvedEmails.length === 1 &&\n (recoveryEmailInput.trim() === '' || recoveryEmailInput === lastPrefilledRecoveryEmailRef.current)\n ) {\n lastPrefilledRecoveryEmailRef.current = resolvedEmails[0];\n setRecoveryEmailInput(resolvedEmails[0]);\n }\n }\n\n const info: EmailRecoveryAccountInfo | null = records\n ? { emailsCount: Array.isArray(records) ? records.length : 0 }\n : null;\n if (cancelled) return;\n safeSetAccountInfo(info);\n } catch (err: any) {\n if (cancelled) return;\n safeSetAccountInfo(null);\n safeSetAccountInfoError(err?.message || 'Failed to load email recovery settings for this account');\n } finally {\n if (!cancelled) safeSetAccountInfoLoading(false);\n }\n })();\n }, 350);\n\n return () => {\n cancelled = true;\n window.clearTimeout(handle);\n };\n }, [\n accountIdInput,\n deriveEmailsFromRecoveryRecords,\n fetchLocalRecoveryEmailsFromIndexedDB,\n recoveryEmailInput,\n safeSetAccountInfo,\n safeSetAccountInfoError,\n safeSetAccountInfoLoading,\n safeSetLocalRecoveryEmails,\n tatchiPasskey,\n ]);\n\n const handleStart = React.useCallback(async () => {\n const normalizedAccountId = (accountIdInput || '').trim();\n if (!normalizedAccountId) {\n safeSetErrorText('Enter an account ID.');\n return;\n }\n\n const emailCandidate = (recoveryEmailInput || '').trim().toLowerCase();\n if (!emailCandidate) {\n safeSetErrorText('Enter the recovery email to send from.');\n return;\n }\n\n safeSetIsBusy(true);\n cancelRequestedRef.current = false;\n safeSetErrorText(null);\n safeSetCanRestart(false);\n safeSetStatusText(null);\n safeSetPollingElapsedMs(null);\n safeSetPendingMailtoUrl(null);\n safeSetPendingNearPublicKey(null);\n safeSetMailtoUiState('ready');\n\n let didForwardError = false;\n try {\n const result = await tatchiPasskey.startEmailRecovery({\n accountId: normalizedAccountId,\n recoveryEmail: emailCandidate,\n options: {\n onEvent,\n onError: (err: Error) => {\n if (cancelRequestedRef.current) return;\n safeSetErrorText(err?.message || 'Failed to start email recovery');\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n afterCall: async () => {},\n } as any,\n });\n\n safeSetPendingMailtoUrl(result.mailtoUrl);\n safeSetPendingNearPublicKey(result.nearPublicKey);\n safeSetStatusText('Recovery email draft ready. If it didn’t open automatically, click “Open recovery email draft”. Waiting for verification…');\n\n // Best-effort open. If blocked/cancelled, the CTA remains immediately clickable for a user-gesture retry.\n attemptOpenMailtoAuto(result.mailtoUrl);\n\n // Start polling immediately after attempting to open the email prompt.\n const finalizePromise = tatchiPasskey.finalizeEmailRecovery({\n accountId: normalizedAccountId,\n nearPublicKey: result.nearPublicKey,\n options: {\n onEvent,\n onError: (err: Error) => {\n if (cancelRequestedRef.current) return;\n const uiError = getEmailRecoveryUiError(err);\n safeSetErrorText(uiError.message || 'Failed to finalize email recovery');\n safeSetCanRestart(uiError.canRestart);\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n afterCall: async () => {},\n } as any,\n });\n\n showExplorerToast(normalizedAccountId);\n\n await finalizePromise;\n\n // Best-effort auto-login: the core flow attempts it, but if it couldn't (e.g. missing Shamir\n // auto-unlock and user cancelled TouchID), try once more here.\n let loginOk = false;\n const session = await tatchiPasskey.getLoginSession(normalizedAccountId).catch(() => null);\n if (session?.login?.isLoggedIn) {\n loginOk = true;\n } else {\n safeSetStatusText('Email recovery completed. Logging you in…');\n loginOk = await tatchiPasskey.loginAndCreateSession(normalizedAccountId)\n .then(() => true)\n .catch(() => false);\n }\n\n if (refreshLoginState) {\n await refreshLoginState(normalizedAccountId).catch(() => {});\n }\n\n safeSetStatusText(loginOk ? 'Email recovery completed on this device.' : 'Email recovery completed. Please log in on this device.');\n safeSetPendingMailtoUrl(null);\n safeSetMailtoUiState('ready');\n safeSetPollingElapsedMs(null);\n } catch (err: any) {\n if (cancelRequestedRef.current) {\n safeSetErrorText('Email recovery cancelled. Please try again.');\n safeSetStatusText(null);\n safeSetPollingElapsedMs(null);\n safeSetPendingMailtoUrl(null);\n safeSetPendingNearPublicKey(null);\n safeSetMailtoUiState('ready');\n safeSetCanRestart(false);\n return;\n }\n const uiError = getEmailRecoveryUiError(err);\n safeSetErrorText(uiError.message || 'Failed to start email recovery');\n safeSetCanRestart(uiError.canRestart);\n if (!didForwardError && err instanceof Error) {\n emailRecoveryOptions?.onError?.(err);\n }\n } finally {\n safeSetIsBusy(false);\n }\n }, [\n accountIdInput,\n emailRecoveryOptions,\n recoveryEmailInput,\n onEvent,\n refreshLoginState,\n showExplorerToast,\n safeSetErrorText,\n safeSetIsBusy,\n safeSetPendingMailtoUrl,\n safeSetPollingElapsedMs,\n safeSetStatusText,\n safeSetMailtoUiState,\n attemptOpenMailtoAuto,\n tatchiPasskey,\n ]);\n\n const handleRestart = React.useCallback(async () => {\n const normalizedAccountId = (accountIdInput || '').trim();\n if (!normalizedAccountId) return;\n\n safeSetIsBusy(true);\n try {\n cancelRequestedRef.current = true;\n await tatchiPasskey.cancelEmailRecovery({\n accountId: normalizedAccountId,\n nearPublicKey: pendingNearPublicKey || undefined,\n }).catch(() => {});\n safeSetErrorText(null);\n safeSetStatusText(null);\n safeSetPollingElapsedMs(null);\n safeSetPendingMailtoUrl(null);\n safeSetPendingNearPublicKey(null);\n safeSetMailtoUiState('ready');\n safeSetCanRestart(false);\n } finally {\n cancelRequestedRef.current = false;\n safeSetIsBusy(false);\n }\n }, [\n accountIdInput,\n pendingNearPublicKey,\n safeSetCanRestart,\n safeSetErrorText,\n safeSetIsBusy,\n safeSetMailtoUiState,\n safeSetPendingMailtoUrl,\n safeSetPendingNearPublicKey,\n safeSetPollingElapsedMs,\n safeSetStatusText,\n tatchiPasskey,\n ]);\n\n const summaryLine = accountInfoLoading\n ? 'Checking if account has recovery emails configured...'\n : accountInfo && !accountInfoError\n ? `Recovery emails configured: ${accountInfo.emailsCount}`\n : '\\u00A0';\n\n const noRecoveryEmailsConfigured =\n !accountInfoLoading && !accountInfoError && !!accountInfo && accountInfo.emailsCount === 0;\n\n return (\n <div className=\"w3a-email-recovery-slide\">\n <div className=\"w3a-email-recovery-title\">Recover Account with Email</div>\n <div className=\"w3a-email-recovery-help\">\n Send a recovery email from your registered email address.\n Your account will be recovered with a new key once the email is verified.\n </div>\n\n <div className=\"w3a-email-recovery-field\">\n <div className=\"w3a-input-pill w3a-email-recovery-input-pill\">\n <div className=\"w3a-input-wrap\">\n <input\n type=\"text\"\n value={accountIdInput}\n onChange={(e) => setAccountIdInput(e.target.value)}\n placeholder=\"NEAR account ID (e.g. alice.testnet)\"\n className=\"w3a-input\"\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"text\"\n disabled={isBusy}\n />\n </div>\n </div>\n </div>\n\n <div className=\"w3a-email-recovery-summary\" aria-live=\"polite\">\n <div>{summaryLine}</div>\n </div>\n\n <div className=\"w3a-email-recovery-field\">\n <div className=\"w3a-input-pill w3a-email-recovery-input-pill\">\n <div className=\"w3a-input-wrap\">\n <input\n type=\"email\"\n value={recoveryEmailInput}\n onChange={(e) => setRecoveryEmailInput(e.target.value)}\n placeholder=\"Recovery email to send from\"\n className=\"w3a-input\"\n list={localRecoveryEmails.length > 0 ? 'w3a-email-recovery-saved-emails' : undefined}\n autoCapitalize=\"none\"\n autoCorrect=\"off\"\n spellCheck={false}\n inputMode=\"email\"\n disabled={isBusy || noRecoveryEmailsConfigured}\n />\n </div>\n </div>\n </div>\n\n {localRecoveryEmails.length > 0 && (\n <div className=\"w3a-email-recovery-summary\" aria-live=\"polite\">\n <div>Saved on this device:</div>\n <div className=\"w3a-email-recovery-saved-emails\">\n {localRecoveryEmails.map((email) => (\n <button\n key={email}\n type=\"button\"\n className=\"w3a-email-recovery-email-chip\"\n onClick={() => setRecoveryEmailInput(email)}\n disabled={isBusy}\n >\n {email}\n </button>\n ))}\n </div>\n </div>\n )}\n\n {localRecoveryEmails.length > 0 && (\n <datalist id=\"w3a-email-recovery-saved-emails\">\n {localRecoveryEmails.map((email) => (\n <option key={email} value={email} />\n ))}\n </datalist>\n )}\n\n <div className=\"w3a-email-recovery-actions\">\n {(!pendingMailtoUrl || !isBusy) && (\n <button\n onClick={handleStart}\n className=\"w3a-link-device-btn w3a-link-device-btn-primary\"\n disabled={isBusy || noRecoveryEmailsConfigured}\n >\n {noRecoveryEmailsConfigured ? 'No recovery emails configured' : (isBusy ? 'Working…' : 'Start Email Recovery')}\n </button>\n )}\n\n {pendingMailtoUrl && (\n <button\n type=\"button\"\n onClick={() => attemptOpenMailtoFromUserGesture(pendingMailtoUrl)}\n className=\"w3a-link-device-btn w3a-link-device-btn-primary\"\n disabled={mailtoUiState === 'opening'}\n aria-busy={mailtoUiState === 'opening'}\n >\n {mailtoUiState === 'opening' && <span className=\"w3a-spinner\" aria-hidden=\"true\" />}\n {mailtoUiState === 'opening' ? 'Opening email…' : 'Open recovery email draft'}\n </button>\n )}\n\n {errorText && canRestart && (\n <button\n type=\"button\"\n onClick={handleRestart}\n className=\"w3a-link-device-btn\"\n disabled={isBusy}\n >\n Restart email recovery\n </button>\n )}\n </div>\n\n {(errorText || statusText || explorerToast) && (\n <div className={`w3a-email-recovery-status${errorText ? ' is-error' : ''}`}>\n {errorText ? errorText : statusText}\n {pollingElapsedMs != null && !Number.isNaN(pollingElapsedMs) && pollingElapsedMs > 0 && (\n <span className=\"w3a-email-recovery-elapsed\">\n (~{Math.round(pollingElapsedMs / 1000)}s).\n </span>\n )}\n {explorerToast && (\n <>\n <a className=\"w3a-email-recovery-link\" href={explorerToast.url} target=\"_blank\" rel=\"noopener noreferrer\">\n View on explorer\n </a>\n </>\n )}\n </div>\n )}\n </div>\n );\n};\n\nexport default EmailRecoverySlide;\n"],"mappings":"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"}
|
package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CL4gsszN.css.map}
RENAMED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ShowQRCode-
|
|
1
|
+
{"version":3,"file":"ShowQRCode-CL4gsszN.css","names":[],"sources":["../../../../src/react/components/ShowQRCode.css"],"sourcesContent":[".qr-code-container {\n padding-top: 1rem;\n}\n\n/* Modal Header */\n.qr-header {\n position: relative;\n display: grid;\n place-content: center;\n padding: 0rem;\n}\n\n.qr-title {\n font-size: 1.5rem;\n font-weight: 700;\n color: var(--w3a-colors-textPrimary, #1e293b);\n padding-top: 0rem;\n margin: 0;\n}\n\n.qr-body {\n padding: 0rem;\n flex: 1;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n.qr-code-section {\n width: 100%;\n display: flex;\n flex-direction: column;\n align-items: center;\n}\n\n/* QR Code Display */\n.qr-code-display {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 0rem;\n padding: 1rem;\n border-radius: 1.5rem;\n width: 100%;\n max-width: 300px;\n}\n\n.qr-instruction, .qr-status {\n margin: 0;\n font-size: 1rem;\n font-weight: 500;\n line-height: 1.5;\n color: var(--w3a-colors-textSecondary, #1e293b);\n text-align: center;\n}\n\n.qr-status {\n margin-bottom: 0.5rem;\n}\n\n.animated-ellipsis {\n display: inline-block;\n width: 1.2em;\n text-align: left;\n}\n\n.animated-ellipsis::after {\n content: \"...\";\n animation: ellipsis 1.5s infinite;\n}\n\n@keyframes ellipsis {\n 0% { content: \"...\"; }\n 25% { content: \"\"; }\n 50% { content: \".\"; }\n 75% { content: \"..\"; }\n 100% { content: \"...\"; }\n}\n\n.qr-code-image {\n max-width: 200px;\n height: auto;\n border-radius: var(--w3a-border-radius-md, 0.5rem);\n}\n\n.qr-loading {\n text-align: center;\n padding: 2rem;\n color: var(--w3a-colors-textSecondary, #64748b);\n}\n\n/* Responsive Design */\n@media (max-width: 768px) {\n .qr-modal-content {\n margin: 1rem;\n max-width: calc(100vw - 2rem);\n }\n\n @supports (width: 1dvw) {\n .qr-modal-content { max-width: calc(100dvw - 2rem); }\n }\n\n .qr-code-image {\n max-width: 200px;\n }\n\n .qr-code-display {\n padding: 1rem;\n }\n}\n"],"mappings":"AAAA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA"}
|
|
@@ -1,9 +1,32 @@
|
|
|
1
1
|
import { __esm } from "../../../../_virtual/rolldown_runtime.js";
|
|
2
2
|
import { init_accountIds, toAccountId } from "../types/accountIds.js";
|
|
3
3
|
import { IndexedDBManager, init_IndexedDBManager } from "../IndexedDBManager/index.js";
|
|
4
|
+
import { base64Decode, init_base64 } from "../../utils/base64.js";
|
|
4
5
|
import { EmailRecoveryPendingStore, init_emailRecoveryPendingStore } from "./emailRecoveryPendingStore.js";
|
|
5
6
|
|
|
6
7
|
//#region src/core/EmailRecovery/index.ts
|
|
8
|
+
function getTxSuccessValueBase64(outcome) {
|
|
9
|
+
const status = outcome.status;
|
|
10
|
+
if (!status || typeof status !== "object") return null;
|
|
11
|
+
if (!("SuccessValue" in status)) return null;
|
|
12
|
+
const value = status.SuccessValue;
|
|
13
|
+
return typeof value === "string" && value.length > 0 ? value : null;
|
|
14
|
+
}
|
|
15
|
+
function parseLinkDeviceRegisterUserResponse(outcome) {
|
|
16
|
+
try {
|
|
17
|
+
const successValueB64 = getTxSuccessValueBase64(outcome);
|
|
18
|
+
if (!successValueB64) return null;
|
|
19
|
+
const bytes = base64Decode(successValueB64);
|
|
20
|
+
const text = new TextDecoder().decode(bytes);
|
|
21
|
+
if (!text.trim()) return null;
|
|
22
|
+
const parsed = JSON.parse(text);
|
|
23
|
+
if (!parsed || typeof parsed !== "object") return null;
|
|
24
|
+
const candidate = parsed;
|
|
25
|
+
return typeof candidate.verified === "boolean" ? candidate : null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
7
30
|
async function hashRecoveryEmails(emails, accountId) {
|
|
8
31
|
const encoder = new TextEncoder();
|
|
9
32
|
const salt = (accountId || "").trim().toLowerCase();
|
|
@@ -54,6 +77,7 @@ var canonicalizeEmail, bytesToHex;
|
|
|
54
77
|
var init_EmailRecovery = __esm({ "src/core/EmailRecovery/index.ts": (() => {
|
|
55
78
|
init_accountIds();
|
|
56
79
|
init_IndexedDBManager();
|
|
80
|
+
init_base64();
|
|
57
81
|
init_emailRecoveryPendingStore();
|
|
58
82
|
canonicalizeEmail = (email) => {
|
|
59
83
|
const raw = String(email || "").trim();
|
|
@@ -77,5 +101,5 @@ var init_EmailRecovery = __esm({ "src/core/EmailRecovery/index.ts": (() => {
|
|
|
77
101
|
|
|
78
102
|
//#endregion
|
|
79
103
|
init_EmailRecovery();
|
|
80
|
-
export { bytesToHex, getLocalRecoveryEmails, init_EmailRecovery, prepareRecoveryEmails };
|
|
104
|
+
export { bytesToHex, getLocalRecoveryEmails, init_EmailRecovery, parseLinkDeviceRegisterUserResponse, prepareRecoveryEmails };
|
|
81
105
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["hashed: number[][]","pairs: RecoveryEmailEntry[]"],"sources":["../../../../../../../src/core/EmailRecovery/index.ts"],"sourcesContent":["import type { AccountId } from '../types/accountIds';\nimport { toAccountId } from '../types/accountIds';\nimport { IndexedDBManager, type RecoveryEmailRecord } from '../IndexedDBManager';\nexport { EmailRecoveryPendingStore, type PendingStore } from './emailRecoveryPendingStore';\n\nexport type RecoveryEmailEntry = {\n hashHex: string;\n email: string;\n};\n\nexport { type RecoveryEmailRecord };\n\nexport const canonicalizeEmail = (email: string): string => {\n const raw = String(email || '').trim();\n if (!raw) return '';\n\n // Handle cases where a full header line is passed in (e.g. \"From: ...\").\n const withoutHeaderName = raw.replace(/^[a-z0-9-]+\\s*:\\s*/i, '').trim();\n\n const emailRegex =\n /([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)/;\n\n // Prefer the common \"Name <email@domain>\" format when present, but still\n // validate/extract the actual address via regex.\n const angleMatch = withoutHeaderName.match(/<([^>]+)>/);\n const candidates = [\n angleMatch?.[1],\n withoutHeaderName,\n ].filter((v): v is string => typeof v === 'string' && v.length > 0);\n\n for (const candidate of candidates) {\n const cleaned = candidate.replace(/^mailto:\\s*/i, '');\n const match = cleaned.match(emailRegex);\n if (match?.[1]) {\n return match[1].trim().toLowerCase();\n }\n }\n\n return withoutHeaderName.toLowerCase();\n};\n\nexport const bytesToHex = (bytes: number[] | Uint8Array): string => {\n const arr = bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);\n return `0x${Array.from(arr)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')}`;\n};\n\nasync function hashRecoveryEmails(emails: string[], accountId: AccountId): Promise<number[][]> {\n const encoder = new TextEncoder();\n const salt = (accountId || '').trim().toLowerCase();\n const normalized = (emails || [])\n .map(e => e.trim())\n .filter(e => e.length > 0);\n\n const hashed: number[][] = [];\n\n for (const email of normalized) {\n try {\n const canonicalEmail = canonicalizeEmail(email);\n const input = `${canonicalEmail}|${salt}`;\n const data = encoder.encode(input);\n const digest = await crypto.subtle.digest('SHA-256', data);\n const bytes = new Uint8Array(digest);\n hashed.push(Array.from(bytes));\n } catch {\n const bytes = encoder.encode(email.toLowerCase());\n hashed.push(Array.from(bytes));\n }\n }\n\n return hashed;\n}\n\n/**\n * Canonicalize and hash recovery emails for an account, and persist the mapping\n * (hashHex → canonical email) in IndexedDB on a best-effort basis.\n */\nexport async function prepareRecoveryEmails(nearAccountId: AccountId, recoveryEmails: string[]): Promise<{\n hashes: number[][];\n pairs: RecoveryEmailEntry[];\n}> {\n const accountId = toAccountId(nearAccountId);\n\n const trimmedEmails = (recoveryEmails || []).map(e => e.trim()).filter(e => e.length > 0);\n const canonicalEmails = trimmedEmails.map(canonicalizeEmail);\n const recoveryEmailHashes = await hashRecoveryEmails(recoveryEmails, accountId);\n\n const pairs: RecoveryEmailEntry[] = recoveryEmailHashes.map((hashBytes, idx) => ({\n hashHex: bytesToHex(hashBytes),\n email: canonicalEmails[idx],\n }));\n\n void (async () => {\n try {\n await IndexedDBManager.upsertRecoveryEmails(accountId, pairs);\n } catch (error) {\n console.warn('[EmailRecovery] Failed to persist local recovery emails', error);\n }\n })();\n\n return { hashes: recoveryEmailHashes, pairs };\n}\n\nexport async function getLocalRecoveryEmails(nearAccountId: AccountId): Promise<RecoveryEmailRecord[]> {\n return IndexedDBManager.getRecoveryEmails(nearAccountId);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","names":["hashed: number[][]","pairs: RecoveryEmailEntry[]"],"sources":["../../../../../../../src/core/EmailRecovery/index.ts"],"sourcesContent":["import type { AccountId } from '../types/accountIds';\nimport { toAccountId } from '../types/accountIds';\nimport { IndexedDBManager, type RecoveryEmailRecord } from '../IndexedDBManager';\nimport type { FinalExecutionOutcome } from '@near-js/types';\nimport { base64Decode } from '../../utils/base64';\nexport { EmailRecoveryPendingStore, type PendingStore } from './emailRecoveryPendingStore';\n\nexport type RecoveryEmailEntry = {\n hashHex: string;\n email: string;\n};\n\nexport { type RecoveryEmailRecord };\n\nexport type LinkDeviceRegisterUserResponse = {\n verified?: boolean;\n registration_info?: unknown;\n registrationInfo?: unknown;\n error?: unknown;\n};\n\nfunction getTxSuccessValueBase64(outcome: FinalExecutionOutcome): string | null {\n const status = outcome.status;\n if (!status || typeof status !== 'object') return null;\n if (!('SuccessValue' in status)) return null;\n const value = status.SuccessValue;\n return typeof value === 'string' && value.length > 0 ? value : null;\n}\n\nexport function parseLinkDeviceRegisterUserResponse(\n outcome: FinalExecutionOutcome\n): LinkDeviceRegisterUserResponse | null {\n try {\n const successValueB64 = getTxSuccessValueBase64(outcome);\n if (!successValueB64) return null;\n\n const bytes = base64Decode(successValueB64);\n const text = new TextDecoder().decode(bytes);\n if (!text.trim()) return null;\n\n const parsed = JSON.parse(text) as unknown;\n if (!parsed || typeof parsed !== 'object') return null;\n const candidate = parsed as LinkDeviceRegisterUserResponse;\n return typeof candidate.verified === 'boolean' ? candidate : null;\n } catch {\n return null;\n }\n}\n\nexport const canonicalizeEmail = (email: string): string => {\n const raw = String(email || '').trim();\n if (!raw) return '';\n\n // Handle cases where a full header line is passed in (e.g. \"From: ...\").\n const withoutHeaderName = raw.replace(/^[a-z0-9-]+\\s*:\\s*/i, '').trim();\n\n const emailRegex =\n /([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(?:\\.[a-zA-Z0-9-]+)*)/;\n\n // Prefer the common \"Name <email@domain>\" format when present, but still\n // validate/extract the actual address via regex.\n const angleMatch = withoutHeaderName.match(/<([^>]+)>/);\n const candidates = [\n angleMatch?.[1],\n withoutHeaderName,\n ].filter((v): v is string => typeof v === 'string' && v.length > 0);\n\n for (const candidate of candidates) {\n const cleaned = candidate.replace(/^mailto:\\s*/i, '');\n const match = cleaned.match(emailRegex);\n if (match?.[1]) {\n return match[1].trim().toLowerCase();\n }\n }\n\n return withoutHeaderName.toLowerCase();\n};\n\nexport const bytesToHex = (bytes: number[] | Uint8Array): string => {\n const arr = bytes instanceof Uint8Array ? bytes : Uint8Array.from(bytes);\n return `0x${Array.from(arr)\n .map(b => b.toString(16).padStart(2, '0'))\n .join('')}`;\n};\n\nasync function hashRecoveryEmails(emails: string[], accountId: AccountId): Promise<number[][]> {\n const encoder = new TextEncoder();\n const salt = (accountId || '').trim().toLowerCase();\n const normalized = (emails || [])\n .map(e => e.trim())\n .filter(e => e.length > 0);\n\n const hashed: number[][] = [];\n\n for (const email of normalized) {\n try {\n const canonicalEmail = canonicalizeEmail(email);\n const input = `${canonicalEmail}|${salt}`;\n const data = encoder.encode(input);\n const digest = await crypto.subtle.digest('SHA-256', data);\n const bytes = new Uint8Array(digest);\n hashed.push(Array.from(bytes));\n } catch {\n const bytes = encoder.encode(email.toLowerCase());\n hashed.push(Array.from(bytes));\n }\n }\n\n return hashed;\n}\n\n/**\n * Canonicalize and hash recovery emails for an account, and persist the mapping\n * (hashHex → canonical email) in IndexedDB on a best-effort basis.\n */\nexport async function prepareRecoveryEmails(nearAccountId: AccountId, recoveryEmails: string[]): Promise<{\n hashes: number[][];\n pairs: RecoveryEmailEntry[];\n}> {\n const accountId = toAccountId(nearAccountId);\n\n const trimmedEmails = (recoveryEmails || []).map(e => e.trim()).filter(e => e.length > 0);\n const canonicalEmails = trimmedEmails.map(canonicalizeEmail);\n const recoveryEmailHashes = await hashRecoveryEmails(recoveryEmails, accountId);\n\n const pairs: RecoveryEmailEntry[] = recoveryEmailHashes.map((hashBytes, idx) => ({\n hashHex: bytesToHex(hashBytes),\n email: canonicalEmails[idx],\n }));\n\n void (async () => {\n try {\n await IndexedDBManager.upsertRecoveryEmails(accountId, pairs);\n } catch (error) {\n console.warn('[EmailRecovery] Failed to persist local recovery emails', error);\n }\n })();\n\n return { hashes: recoveryEmailHashes, pairs };\n}\n\nexport async function getLocalRecoveryEmails(nearAccountId: AccountId): Promise<RecoveryEmailRecord[]> {\n return IndexedDBManager.getRecoveryEmails(nearAccountId);\n}\n"],"mappings":";;;;;;;AAqBA,SAAS,wBAAwB,SAA+C;CAC9E,MAAM,SAAS,QAAQ;AACvB,KAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;AAClD,KAAI,EAAE,kBAAkB,QAAS,QAAO;CACxC,MAAM,QAAQ,OAAO;AACrB,QAAO,OAAO,UAAU,YAAY,MAAM,SAAS,IAAI,QAAQ;;AAGjE,SAAgB,oCACd,SACuC;AACvC,KAAI;EACF,MAAM,kBAAkB,wBAAwB;AAChD,MAAI,CAAC,gBAAiB,QAAO;EAE7B,MAAM,QAAQ,aAAa;EAC3B,MAAM,OAAO,IAAI,cAAc,OAAO;AACtC,MAAI,CAAC,KAAK,OAAQ,QAAO;EAEzB,MAAM,SAAS,KAAK,MAAM;AAC1B,MAAI,CAAC,UAAU,OAAO,WAAW,SAAU,QAAO;EAClD,MAAM,YAAY;AAClB,SAAO,OAAO,UAAU,aAAa,YAAY,YAAY;SACvD;AACN,SAAO;;;AAwCX,eAAe,mBAAmB,QAAkB,WAA2C;CAC7F,MAAM,UAAU,IAAI;CACpB,MAAM,QAAQ,aAAa,IAAI,OAAO;CACtC,MAAM,cAAc,UAAU,IAC3B,KAAI,MAAK,EAAE,QACX,QAAO,MAAK,EAAE,SAAS;CAE1B,MAAMA,SAAqB;AAE3B,MAAK,MAAM,SAAS,WAClB,KAAI;EACF,MAAM,iBAAiB,kBAAkB;EACzC,MAAM,QAAQ,GAAG,eAAe,GAAG;EACnC,MAAM,OAAO,QAAQ,OAAO;EAC5B,MAAM,SAAS,MAAM,OAAO,OAAO,OAAO,WAAW;EACrD,MAAM,QAAQ,IAAI,WAAW;AAC7B,SAAO,KAAK,MAAM,KAAK;SACjB;EACN,MAAM,QAAQ,QAAQ,OAAO,MAAM;AACnC,SAAO,KAAK,MAAM,KAAK;;AAI3B,QAAO;;;;;;AAOT,eAAsB,sBAAsB,eAA0B,gBAGnE;CACD,MAAM,YAAY,YAAY;CAE9B,MAAM,iBAAiB,kBAAkB,IAAI,KAAI,MAAK,EAAE,QAAQ,QAAO,MAAK,EAAE,SAAS;CACvF,MAAM,kBAAkB,cAAc,IAAI;CAC1C,MAAM,sBAAsB,MAAM,mBAAmB,gBAAgB;CAErE,MAAMC,QAA8B,oBAAoB,KAAK,WAAW,SAAS;EAC/E,SAAS,WAAW;EACpB,OAAO,gBAAgB;;AAGzB,EAAM,YAAY;AAChB,MAAI;AACF,SAAM,iBAAiB,qBAAqB,WAAW;WAChD,OAAO;AACd,WAAQ,KAAK,2DAA2D;;;AAI5E,QAAO;EAAE,QAAQ;EAAqB;;;AAGxC,eAAsB,uBAAuB,eAA0D;AACrG,QAAO,iBAAiB,kBAAkB;;;;;;;;CA7F/B,qBAAqB,UAA0B;EAC1D,MAAM,MAAM,OAAO,SAAS,IAAI;AAChC,MAAI,CAAC,IAAK,QAAO;EAGjB,MAAM,oBAAoB,IAAI,QAAQ,uBAAuB,IAAI;EAEjE,MAAM,aACJ;EAIF,MAAM,aAAa,kBAAkB,MAAM;EAC3C,MAAM,aAAa,CACjB,aAAa,IACb,mBACA,QAAQ,MAAmB,OAAO,MAAM,YAAY,EAAE,SAAS;AAEjE,OAAK,MAAM,aAAa,YAAY;GAClC,MAAM,UAAU,UAAU,QAAQ,gBAAgB;GAClD,MAAM,QAAQ,QAAQ,MAAM;AAC5B,OAAI,QAAQ,GACV,QAAO,MAAM,GAAG,OAAO;;AAI3B,SAAO,kBAAkB;;CAGd,cAAc,UAAyC;EAClE,MAAM,MAAM,iBAAiB,aAAa,QAAQ,WAAW,KAAK;AAClE,SAAO,KAAK,MAAM,KAAK,KACpB,KAAI,MAAK,EAAE,SAAS,IAAI,SAAS,GAAG,MACpC,KAAK"}
|
|
@@ -3,12 +3,14 @@ import { init_validation, validateNearAccountId } from "../../utils/validation.j
|
|
|
3
3
|
import { init_accountIds, toAccountId } from "../types/accountIds.js";
|
|
4
4
|
import { IndexedDBManager, init_IndexedDBManager } from "../IndexedDBManager/index.js";
|
|
5
5
|
import { createRandomVRFChallenge, init_vrf_worker } from "../types/vrf-worker.js";
|
|
6
|
+
import { errorMessage, init_errors } from "../../utils/errors.js";
|
|
6
7
|
import { EmailRecoveryPhase, EmailRecoveryStatus, init_sdkSentEvents } from "../types/sdkSentEvents.js";
|
|
7
8
|
import { DEFAULT_WAIT_STATUS, init_rpc } from "../types/rpc.js";
|
|
8
9
|
import { init_getDeviceNumber, parseDeviceNumber } from "../WebAuthnManager/SignerWorkerManager/getDeviceNumber.js";
|
|
9
10
|
import { getLoginSession, init_login } from "./login.js";
|
|
10
11
|
import { EmailRecoveryPendingStore } from "../EmailRecovery/emailRecoveryPendingStore.js";
|
|
11
|
-
import { init_EmailRecovery } from "../EmailRecovery/index.js";
|
|
12
|
+
import { init_EmailRecovery, parseLinkDeviceRegisterUserResponse } from "../EmailRecovery/index.js";
|
|
13
|
+
import { EmailRecoveryError, EmailRecoveryErrorCode, init_emailRecovery as init_emailRecovery$1 } from "../types/emailRecovery.js";
|
|
12
14
|
|
|
13
15
|
//#region src/core/TatchiPasskey/emailRecovery.ts
|
|
14
16
|
var emailRecovery_exports = {};
|
|
@@ -48,6 +50,7 @@ var EmailRecoveryFlow;
|
|
|
48
50
|
var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (() => {
|
|
49
51
|
init_IndexedDBManager();
|
|
50
52
|
init_validation();
|
|
53
|
+
init_errors();
|
|
51
54
|
init_accountIds();
|
|
52
55
|
init_sdkSentEvents();
|
|
53
56
|
init_vrf_worker();
|
|
@@ -55,6 +58,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
55
58
|
init_getDeviceNumber();
|
|
56
59
|
init_login();
|
|
57
60
|
init_EmailRecovery();
|
|
61
|
+
init_emailRecovery$1();
|
|
58
62
|
EmailRecoveryFlow = class {
|
|
59
63
|
context;
|
|
60
64
|
options;
|
|
@@ -82,8 +86,9 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
82
86
|
emit(event) {
|
|
83
87
|
this.options?.onEvent?.(event);
|
|
84
88
|
}
|
|
85
|
-
emitError(step,
|
|
86
|
-
const err = new Error(
|
|
89
|
+
emitError(step, messageOrError) {
|
|
90
|
+
const err = typeof messageOrError === "string" ? new Error(messageOrError) : messageOrError;
|
|
91
|
+
const message = err.message || (typeof messageOrError === "string" ? messageOrError : "Unknown error");
|
|
87
92
|
this.phase = EmailRecoveryPhase.ERROR;
|
|
88
93
|
this.error = err;
|
|
89
94
|
this.emit({
|
|
@@ -142,8 +147,8 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
142
147
|
const accountView = await this.context.nearClient.viewAccount(nearAccountId);
|
|
143
148
|
const available = this.computeAvailableBalance(accountView);
|
|
144
149
|
if (available < BigInt(minBalanceYocto)) await this.fail(1, `This account does not have enough NEAR to finalize recovery. Available: ${available.toString()} yocto; required: ${String(minBalanceYocto)}. Please top up and try again.`);
|
|
145
|
-
} catch (
|
|
146
|
-
await this.fail(1,
|
|
150
|
+
} catch (err) {
|
|
151
|
+
await this.fail(1, errorMessage(err) || "Failed to fetch account balance for recovery");
|
|
147
152
|
}
|
|
148
153
|
}
|
|
149
154
|
async getCanonicalRecoveryEmailOrFail(recoveryEmail) {
|
|
@@ -155,7 +160,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
155
160
|
try {
|
|
156
161
|
const { syncAuthenticatorsContractCall } = await import("../rpcCalls.js");
|
|
157
162
|
const authenticators = await syncAuthenticatorsContractCall(this.context.nearClient, this.context.configs.contractId, nearAccountId);
|
|
158
|
-
const numbers = authenticators.map((
|
|
163
|
+
const numbers = authenticators.map(({ authenticator }) => authenticator.deviceNumber).filter((n) => typeof n === "number" && Number.isFinite(n));
|
|
159
164
|
const max = numbers.length > 0 ? Math.max(...numbers) : 0;
|
|
160
165
|
return max + 1;
|
|
161
166
|
} catch {
|
|
@@ -234,11 +239,11 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
234
239
|
success: false
|
|
235
240
|
};
|
|
236
241
|
if (!result.verified) {
|
|
237
|
-
const errorMessage = result.error_message || result.error_code || "Email verification failed on relayer/contract";
|
|
242
|
+
const errorMessage$1 = result.error_message || result.error_code || "Email verification failed on relayer/contract";
|
|
238
243
|
return {
|
|
239
244
|
completed: true,
|
|
240
245
|
success: false,
|
|
241
|
-
errorMessage,
|
|
246
|
+
errorMessage: errorMessage$1,
|
|
242
247
|
transactionHash: result.transaction_hash
|
|
243
248
|
};
|
|
244
249
|
}
|
|
@@ -403,7 +408,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
403
408
|
nearPublicKey: rec.nearPublicKey
|
|
404
409
|
};
|
|
405
410
|
} catch (e) {
|
|
406
|
-
const err = this.emitError(2, e
|
|
411
|
+
const err = this.emitError(2, errorMessage(e) || "Email recovery TouchID/derivation failed");
|
|
407
412
|
await this.options?.afterCall?.(false);
|
|
408
413
|
throw err;
|
|
409
414
|
}
|
|
@@ -573,7 +578,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
573
578
|
accountId
|
|
574
579
|
};
|
|
575
580
|
}
|
|
576
|
-
async
|
|
581
|
+
async signNewDevice2RegistrationTx(rec, accountId) {
|
|
577
582
|
const vrfChallenge = rec.vrfChallenge;
|
|
578
583
|
if (!vrfChallenge) return this.fail(5, "Missing VRF challenge for email recovery registration");
|
|
579
584
|
const registrationResult = await this.context.webAuthnManager.signDevice2RegistrationWithStoredKey({
|
|
@@ -587,28 +592,56 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
587
592
|
return registrationResult.signedTransaction;
|
|
588
593
|
}
|
|
589
594
|
async broadcastRegistrationTxAndWaitFinal(rec, signedTx) {
|
|
595
|
+
let txResult;
|
|
590
596
|
try {
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
597
|
+
txResult = await this.context.nearClient.sendTransaction(signedTx, DEFAULT_WAIT_STATUS.linkDeviceRegistration);
|
|
598
|
+
} catch (err) {
|
|
599
|
+
const msg = errorMessage(err) || "Failed to broadcast email recovery registration transaction (insufficient funds or RPC error)";
|
|
600
|
+
throw new Error(msg);
|
|
601
|
+
}
|
|
602
|
+
const txHash = this.getTxHash(txResult);
|
|
603
|
+
const linkDeviceResult = parseLinkDeviceRegisterUserResponse(txResult);
|
|
604
|
+
if (linkDeviceResult?.verified === false) {
|
|
605
|
+
const logs = this.extractNearExecutionLogs(txResult);
|
|
606
|
+
const isStaleChallenge = logs.some((log) => /StaleChallenge|freshness validation failed/i.test(log));
|
|
607
|
+
const txHint = txHash ? ` (tx: ${txHash})` : "";
|
|
608
|
+
const code = isStaleChallenge ? EmailRecoveryErrorCode.VRF_CHALLENGE_EXPIRED : EmailRecoveryErrorCode.REGISTRATION_NOT_VERIFIED;
|
|
609
|
+
const message = isStaleChallenge ? `Timed out finalizing registration (VRF challenge expired). Please restart email recovery and try again${txHint}.` : `Registration did not verify on-chain. Please try again${txHint}.`;
|
|
610
|
+
throw new EmailRecoveryError(message, code, {
|
|
611
|
+
accountId: rec.accountId,
|
|
612
|
+
nearPublicKey: rec.nearPublicKey,
|
|
613
|
+
transactionHash: txHash,
|
|
614
|
+
logs,
|
|
615
|
+
result: linkDeviceResult
|
|
616
|
+
});
|
|
610
617
|
}
|
|
611
|
-
|
|
618
|
+
if (txHash) this.emit({
|
|
619
|
+
step: 5,
|
|
620
|
+
phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,
|
|
621
|
+
status: EmailRecoveryStatus.PROGRESS,
|
|
622
|
+
message: "Registration transaction confirmed",
|
|
623
|
+
data: {
|
|
624
|
+
accountId: rec.accountId,
|
|
625
|
+
nearPublicKey: rec.nearPublicKey,
|
|
626
|
+
transactionHash: txHash
|
|
627
|
+
}
|
|
628
|
+
});
|
|
629
|
+
return txHash;
|
|
630
|
+
}
|
|
631
|
+
getTxHash(outcome) {
|
|
632
|
+
const txUnknown = outcome.transaction;
|
|
633
|
+
if (txUnknown && typeof txUnknown === "object") {
|
|
634
|
+
const hash = txUnknown.hash;
|
|
635
|
+
if (typeof hash === "string" && hash.length > 0) return hash;
|
|
636
|
+
}
|
|
637
|
+
const fallback = outcome.transaction_hash;
|
|
638
|
+
return typeof fallback === "string" && fallback.length > 0 ? fallback : void 0;
|
|
639
|
+
}
|
|
640
|
+
extractNearExecutionLogs(outcome) {
|
|
641
|
+
const logs = [];
|
|
642
|
+
for (const entry of outcome.transaction_outcome.outcome.logs) logs.push(String(entry));
|
|
643
|
+
for (const receipt of outcome.receipts_outcome) for (const entry of receipt.outcome.logs) logs.push(String(entry));
|
|
644
|
+
return logs;
|
|
612
645
|
}
|
|
613
646
|
mapAuthenticatorsFromContract(authenticators) {
|
|
614
647
|
return authenticators.map(({ authenticator }) => ({
|
|
@@ -644,7 +677,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
644
677
|
}
|
|
645
678
|
async updateNonceBestEffort(nonceManager, signedTx) {
|
|
646
679
|
try {
|
|
647
|
-
const txNonce = signedTx.transaction
|
|
680
|
+
const txNonce = signedTx.transaction.nonce;
|
|
648
681
|
if (txNonce != null) await nonceManager.updateNonceFromBlockchain(this.context.nearClient, String(txNonce));
|
|
649
682
|
} catch {}
|
|
650
683
|
}
|
|
@@ -760,7 +793,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
760
793
|
} catch (err) {
|
|
761
794
|
return {
|
|
762
795
|
success: false,
|
|
763
|
-
reason: err
|
|
796
|
+
reason: errorMessage(err) || String(err)
|
|
764
797
|
};
|
|
765
798
|
}
|
|
766
799
|
}
|
|
@@ -788,7 +821,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
788
821
|
});
|
|
789
822
|
try {
|
|
790
823
|
const { nonceManager, accountId } = this.initializeNonceManager(rec);
|
|
791
|
-
const signedTx = await this.
|
|
824
|
+
const signedTx = await this.signNewDevice2RegistrationTx(rec, accountId);
|
|
792
825
|
const txHash = await this.broadcastRegistrationTxAndWaitFinal(rec, signedTx);
|
|
793
826
|
if (!txHash) console.warn("[EmailRecoveryFlow] Registration transaction confirmed without hash; continuing local persistence");
|
|
794
827
|
await this.persistRecoveredUserData(rec, accountId);
|
|
@@ -816,7 +849,10 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
816
849
|
}
|
|
817
850
|
});
|
|
818
851
|
} catch (e) {
|
|
819
|
-
|
|
852
|
+
rec.status = "error";
|
|
853
|
+
await this.savePending(rec).catch(() => {});
|
|
854
|
+
const original = e instanceof Error ? e : new Error(errorMessage(e) || "Email recovery finalization failed");
|
|
855
|
+
const err = this.emitError(5, original);
|
|
820
856
|
await this.options?.afterCall?.(false);
|
|
821
857
|
throw err;
|
|
822
858
|
}
|
|
@@ -838,7 +874,7 @@ var init_emailRecovery = __esm({ "src/core/TatchiPasskey/emailRecovery.ts": (()
|
|
|
838
874
|
};
|
|
839
875
|
return this.handleAutoLoginFailure(touchIdResult.reason || "Auto-login failed");
|
|
840
876
|
} catch (err) {
|
|
841
|
-
return this.handleAutoLoginFailure(err
|
|
877
|
+
return this.handleAutoLoginFailure(errorMessage(err) || String(err), err);
|
|
842
878
|
}
|
|
843
879
|
}
|
|
844
880
|
};
|