@tatchi-xyz/sdk 0.17.0 → 0.18.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.
Files changed (122) hide show
  1. package/dist/cjs/core/EmailRecovery/emailRecoveryPendingStore.js +69 -0
  2. package/dist/cjs/core/EmailRecovery/emailRecoveryPendingStore.js.map +1 -0
  3. package/dist/cjs/core/EmailRecovery/index.js +32 -20
  4. package/dist/cjs/core/EmailRecovery/index.js.map +1 -1
  5. package/dist/cjs/core/TatchiPasskey/emailRecovery.js +519 -448
  6. package/dist/cjs/core/TatchiPasskey/emailRecovery.js.map +1 -1
  7. package/dist/cjs/core/TatchiPasskey/index.js +1 -0
  8. package/dist/cjs/core/TatchiPasskey/index.js.map +1 -1
  9. package/dist/cjs/core/TatchiPasskey/relay.js +23 -1
  10. package/dist/cjs/core/TatchiPasskey/relay.js.map +1 -1
  11. package/dist/cjs/core/WalletIframe/client/IframeTransport.js +0 -7
  12. package/dist/cjs/core/WalletIframe/client/IframeTransport.js.map +1 -1
  13. package/dist/cjs/core/WalletIframe/client/router.js +6 -2
  14. package/dist/cjs/core/WalletIframe/client/router.js.map +1 -1
  15. package/dist/cjs/core/rpcCalls.js +8 -0
  16. package/dist/cjs/core/rpcCalls.js.map +1 -1
  17. package/dist/cjs/index.js +6 -2
  18. package/dist/cjs/index.js.map +1 -1
  19. package/dist/cjs/react/components/AccountMenuButton/{LinkedDevicesModal-B6api181.css → LinkedDevicesModal-CSSowiHP.css} +1 -1
  20. package/dist/{esm/react/components/AccountMenuButton/LinkedDevicesModal-B6api181.css.map → cjs/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map} +1 -1
  21. package/dist/cjs/react/components/AccountMenuButton/{ProfileDropdown-B-DrG_u5.css → ProfileDropdown-CEPMZ1gY.css} +1 -1
  22. package/dist/{esm/react/components/AccountMenuButton/ProfileDropdown-B-DrG_u5.css.map → cjs/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map} +1 -1
  23. package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-BnZDUeCL.css → Web3AuthProfileButton-DopOg7Xc.css} +1 -1
  24. package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-BnZDUeCL.css.map → Web3AuthProfileButton-DopOg7Xc.css.map} +1 -1
  25. package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-CAGCi8MY.css → TouchIcon-BQWentvJ.css} +1 -1
  26. package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-CAGCi8MY.css.map → TouchIcon-BQWentvJ.css.map} +1 -1
  27. package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-CNNxVj4L.css → PasskeyAuthMenu-DwrzWMYx.css} +1 -1
  28. package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-CNNxVj4L.css.map → PasskeyAuthMenu-DwrzWMYx.css.map} +1 -1
  29. package/dist/cjs/react/components/{ShowQRCode-nZhZSaba.css → ShowQRCode-CCN4h6Uv.css} +1 -1
  30. package/dist/cjs/react/components/{ShowQRCode-nZhZSaba.css.map → ShowQRCode-CCN4h6Uv.css.map} +1 -1
  31. package/dist/cjs/react/hooks/usePreconnectWalletAssets.js +27 -32
  32. package/dist/cjs/react/hooks/usePreconnectWalletAssets.js.map +1 -1
  33. package/dist/cjs/react/sdk/src/core/EmailRecovery/emailRecoveryPendingStore.js +69 -0
  34. package/dist/cjs/react/sdk/src/core/EmailRecovery/emailRecoveryPendingStore.js.map +1 -0
  35. package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js +32 -20
  36. package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
  37. package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js +519 -448
  38. package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
  39. package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js +1 -0
  40. package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
  41. package/dist/cjs/react/sdk/src/core/TatchiPasskey/relay.js +23 -1
  42. package/dist/cjs/react/sdk/src/core/TatchiPasskey/relay.js.map +1 -1
  43. package/dist/cjs/react/sdk/src/core/WalletIframe/client/IframeTransport.js +0 -7
  44. package/dist/cjs/react/sdk/src/core/WalletIframe/client/IframeTransport.js.map +1 -1
  45. package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js +6 -2
  46. package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
  47. package/dist/cjs/react/sdk/src/core/rpcCalls.js +8 -0
  48. package/dist/cjs/react/sdk/src/core/rpcCalls.js.map +1 -1
  49. package/dist/esm/core/EmailRecovery/emailRecoveryPendingStore.js +63 -0
  50. package/dist/esm/core/EmailRecovery/emailRecoveryPendingStore.js.map +1 -0
  51. package/dist/esm/core/EmailRecovery/index.js +28 -21
  52. package/dist/esm/core/EmailRecovery/index.js.map +1 -1
  53. package/dist/esm/core/TatchiPasskey/emailRecovery.js +519 -448
  54. package/dist/esm/core/TatchiPasskey/emailRecovery.js.map +1 -1
  55. package/dist/esm/core/TatchiPasskey/index.js +2 -1
  56. package/dist/esm/core/TatchiPasskey/index.js.map +1 -1
  57. package/dist/esm/core/TatchiPasskey/relay.js +23 -1
  58. package/dist/esm/core/TatchiPasskey/relay.js.map +1 -1
  59. package/dist/esm/core/WalletIframe/client/IframeTransport.js +0 -7
  60. package/dist/esm/core/WalletIframe/client/IframeTransport.js.map +1 -1
  61. package/dist/esm/core/WalletIframe/client/router.js +7 -3
  62. package/dist/esm/core/WalletIframe/client/router.js.map +1 -1
  63. package/dist/esm/core/rpcCalls.js +8 -1
  64. package/dist/esm/core/rpcCalls.js.map +1 -1
  65. package/dist/esm/index.js +4 -1
  66. package/dist/esm/index.js.map +1 -1
  67. package/dist/esm/react/components/AccountMenuButton/{LinkedDevicesModal-B6api181.css → LinkedDevicesModal-CSSowiHP.css} +1 -1
  68. package/dist/{cjs/react/components/AccountMenuButton/LinkedDevicesModal-B6api181.css.map → esm/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map} +1 -1
  69. package/dist/esm/react/components/AccountMenuButton/{ProfileDropdown-B-DrG_u5.css → ProfileDropdown-CEPMZ1gY.css} +1 -1
  70. package/dist/{cjs/react/components/AccountMenuButton/ProfileDropdown-B-DrG_u5.css.map → esm/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map} +1 -1
  71. package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-BnZDUeCL.css → Web3AuthProfileButton-DopOg7Xc.css} +1 -1
  72. package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-BnZDUeCL.css.map → Web3AuthProfileButton-DopOg7Xc.css.map} +1 -1
  73. package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-CAGCi8MY.css → TouchIcon-BQWentvJ.css} +1 -1
  74. package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-CAGCi8MY.css.map → TouchIcon-BQWentvJ.css.map} +1 -1
  75. package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-CNNxVj4L.css → PasskeyAuthMenu-DwrzWMYx.css} +1 -1
  76. package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-CNNxVj4L.css.map → PasskeyAuthMenu-DwrzWMYx.css.map} +1 -1
  77. package/dist/esm/react/components/{ShowQRCode-nZhZSaba.css → ShowQRCode-CCN4h6Uv.css} +1 -1
  78. package/dist/esm/react/components/{ShowQRCode-nZhZSaba.css.map → ShowQRCode-CCN4h6Uv.css.map} +1 -1
  79. package/dist/esm/react/hooks/usePreconnectWalletAssets.js +27 -32
  80. package/dist/esm/react/hooks/usePreconnectWalletAssets.js.map +1 -1
  81. package/dist/esm/react/sdk/src/core/EmailRecovery/emailRecoveryPendingStore.js +63 -0
  82. package/dist/esm/react/sdk/src/core/EmailRecovery/emailRecoveryPendingStore.js.map +1 -0
  83. package/dist/esm/react/sdk/src/core/EmailRecovery/index.js +28 -21
  84. package/dist/esm/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
  85. package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js +519 -448
  86. package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
  87. package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js +2 -1
  88. package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
  89. package/dist/esm/react/sdk/src/core/TatchiPasskey/relay.js +23 -1
  90. package/dist/esm/react/sdk/src/core/TatchiPasskey/relay.js.map +1 -1
  91. package/dist/esm/react/sdk/src/core/WalletIframe/client/IframeTransport.js +0 -7
  92. package/dist/esm/react/sdk/src/core/WalletIframe/client/IframeTransport.js.map +1 -1
  93. package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js +7 -3
  94. package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
  95. package/dist/esm/react/sdk/src/core/rpcCalls.js +8 -1
  96. package/dist/esm/react/sdk/src/core/rpcCalls.js.map +1 -1
  97. package/dist/esm/sdk/offline-export-app.js.map +1 -1
  98. package/dist/esm/sdk/{router-BLFegW7J.js → router-DuGYOd3G.js} +6 -9
  99. package/dist/esm/sdk/{rpcCalls-DEv9x5-f.js → rpcCalls-BQrJMTdg.js} +2 -2
  100. package/dist/esm/sdk/{rpcCalls-OhgEeFig.js → rpcCalls-YVeUVMk2.js} +8 -1
  101. package/dist/esm/sdk/wallet-iframe-host.js +624 -471
  102. package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
  103. package/dist/types/src/core/EmailRecovery/emailRecoveryPendingStore.d.ts +25 -0
  104. package/dist/types/src/core/EmailRecovery/emailRecoveryPendingStore.d.ts.map +1 -0
  105. package/dist/types/src/core/EmailRecovery/index.d.ts +1 -0
  106. package/dist/types/src/core/EmailRecovery/index.d.ts.map +1 -1
  107. package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts +35 -6
  108. package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts.map +1 -1
  109. package/dist/types/src/core/TatchiPasskey/index.d.ts +2 -2
  110. package/dist/types/src/core/TatchiPasskey/index.d.ts.map +1 -1
  111. package/dist/types/src/core/TatchiPasskey/relay.d.ts +2 -1
  112. package/dist/types/src/core/TatchiPasskey/relay.d.ts.map +1 -1
  113. package/dist/types/src/core/WalletIframe/client/IframeTransport.d.ts.map +1 -1
  114. package/dist/types/src/core/WalletIframe/client/router.d.ts +3 -3
  115. package/dist/types/src/core/WalletIframe/client/router.d.ts.map +1 -1
  116. package/dist/types/src/core/rpcCalls.d.ts +9 -0
  117. package/dist/types/src/core/rpcCalls.d.ts.map +1 -1
  118. package/dist/types/src/index.d.ts +1 -0
  119. package/dist/types/src/index.d.ts.map +1 -1
  120. package/dist/types/src/react/hooks/usePreconnectWalletAssets.d.ts.map +1 -1
  121. package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
  122. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"emailRecovery.js","names":["e: any","rec: PendingEmailRecovery","err","vrfStatus","vrfActiveForAccount","err: any"],"sources":["../../../../../../../src/core/TatchiPasskey/emailRecovery.ts"],"sourcesContent":["import type { PasskeyManagerContext } from './index';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport { validateNearAccountId } from '../../utils/validation';\nimport { toAccountId, type AccountId } from '../types/accountIds';\nimport {\n EmailRecoveryPhase,\n EmailRecoveryStatus,\n type EmailRecoverySSEEvent,\n type EventCallback,\n type AfterCall,\n} from '../types/sdkSentEvents';\nimport type { TatchiConfigs } from '../types/tatchi';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport {\n createRandomVRFChallenge,\n type EncryptedVRFKeypair,\n type ServerEncryptedVrfKeypair,\n type VRFChallenge,\n} from '../types/vrf-worker';\nimport type { WebAuthnRegistrationCredential } from '../types';\nimport type { ConfirmationConfig } from '../types/signer-worker';\nimport { DEFAULT_WAIT_STATUS } from '../types/rpc';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\nimport { getLoginSession } from './login';\n\nexport type PendingEmailRecoveryStatus =\n | 'awaiting-email'\n | 'awaiting-add-key'\n | 'finalizing'\n | 'complete'\n | 'error';\n\nexport type PendingEmailRecovery = {\n accountId: AccountId;\n recoveryEmail: string;\n deviceNumber: number;\n nearPublicKey: string;\n requestId: string;\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n createdAt: number;\n status: PendingEmailRecoveryStatus;\n};\n\nexport interface EmailRecoveryFlowOptions {\n onEvent?: EventCallback<EmailRecoverySSEEvent>;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<void>;\n /**\n * Preferred grouping for per-call confirmer copy.\n */\n confirmerText?: { title?: string; body?: string };\n // Per-call confirmation configuration (non-persistent)\n confirmationConfig?: Partial<ConfirmationConfig>;\n}\n\nfunction getEmailRecoveryConfig(configs: TatchiConfigs): {\n minBalanceYocto: string;\n pollingIntervalMs: number;\n maxPollingDurationMs: number;\n pendingTtlMs: number;\n mailtoAddress: string;\n dkimVerifierAccountId: string;\n verificationViewMethod: string;\n} {\n const relayerEmailCfg = configs.relayer.emailRecovery;\n const minBalanceYocto = String(relayerEmailCfg.minBalanceYocto);\n const pollingIntervalMs = Number(relayerEmailCfg.pollingIntervalMs);\n const maxPollingDurationMs = Number(relayerEmailCfg.maxPollingDurationMs);\n const pendingTtlMs = Number(relayerEmailCfg.pendingTtlMs);\n const mailtoAddress = String(relayerEmailCfg.mailtoAddress);\n const dkimVerifierAccountId = String(relayerEmailCfg.dkimVerifierAccountId);\n const verificationViewMethod = String(relayerEmailCfg.verificationViewMethod);\n return {\n minBalanceYocto,\n pollingIntervalMs,\n maxPollingDurationMs,\n pendingTtlMs,\n mailtoAddress,\n dkimVerifierAccountId,\n verificationViewMethod,\n };\n}\n\nexport function generateEmailRecoveryRequestId(): string {\n // 6-character A–Z0–9 identifier, suitable for short-lived correlation.\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n const length = 6;\n const bytes = new Uint8Array(length);\n (globalThis.crypto || window.crypto).getRandomValues(bytes);\n let out = '';\n for (let i = 0; i < length; i++) {\n out += alphabet[bytes[i] % alphabet.length];\n }\n return out;\n}\n\nexport class EmailRecoveryFlow {\n private context: PasskeyManagerContext;\n private options?: EmailRecoveryFlowOptions;\n private pending: PendingEmailRecovery | null = null;\n private phase: EmailRecoveryPhase = EmailRecoveryPhase.STEP_1_PREPARATION;\n private pollingTimer: any;\n private pollIntervalResolver?: (value?: void | PromiseLike<void>) => void;\n private pollingStartedAt: number | null = null;\n private cancelled = false;\n private error?: Error;\n\n constructor(context: PasskeyManagerContext, options?: EmailRecoveryFlowOptions) {\n this.context = context;\n this.options = options;\n }\n\n setOptions(options?: EmailRecoveryFlowOptions) {\n if (!options) return;\n this.options = { ...(this.options || {}), ...options };\n }\n private emit(event: EmailRecoverySSEEvent) {\n this.options?.onEvent?.(event);\n }\n\n private emitError(step: number, message: string): Error {\n const err = new Error(message);\n this.phase = EmailRecoveryPhase.ERROR;\n this.error = err;\n this.emit({\n step,\n phase: EmailRecoveryPhase.ERROR,\n status: EmailRecoveryStatus.ERROR,\n message,\n error: message,\n } as EmailRecoverySSEEvent & { error: string });\n this.options?.onError?.(err);\n return err;\n }\n\n private getConfig() {\n return getEmailRecoveryConfig(this.context.configs);\n }\n\n private getPendingIndexKey(accountId: AccountId): string {\n return `pendingEmailRecovery:${accountId}`;\n }\n\n private getPendingRecordKey(accountId: AccountId, nearPublicKey: string): string {\n return `${this.getPendingIndexKey(accountId)}:${nearPublicKey}`;\n }\n\n private async checkVerificationStatus(\n rec: PendingEmailRecovery\n ): Promise<{ completed: boolean; success: boolean; errorMessage?: string; transactionHash?: string } | null> {\n const { dkimVerifierAccountId, verificationViewMethod } = this.getConfig();\n if (!dkimVerifierAccountId) return null;\n\n try {\n type VerificationResult = {\n verified: boolean;\n account_id?: string;\n new_public_key?: string;\n transaction_hash?: string;\n error_code?: string;\n error_message?: string;\n };\n\n const result = await this.context.nearClient.view<\n { request_id: string },\n VerificationResult | null\n >({\n account: dkimVerifierAccountId,\n method: verificationViewMethod,\n args: { request_id: rec.requestId },\n });\n\n if (!result) {\n return { completed: false, success: false };\n }\n\n if (!result.verified) {\n const errorMessage = result.error_message || result.error_code || 'Email verification failed on relayer/contract';\n return {\n completed: true,\n success: false,\n errorMessage,\n transactionHash: result.transaction_hash,\n };\n }\n\n // Optional safety checks: ensure the bound account/key match expectations when available.\n if (result.account_id && result.account_id !== rec.accountId) {\n return {\n completed: true,\n success: false,\n errorMessage: 'Email verification account_id does not match requested account.',\n transactionHash: result.transaction_hash,\n };\n }\n if (result.new_public_key && result.new_public_key !== rec.nearPublicKey) {\n return {\n completed: true,\n success: false,\n errorMessage: 'Email verification new_public_key does not match expected recovery key.',\n transactionHash: result.transaction_hash,\n };\n }\n\n return {\n completed: true,\n success: true,\n transactionHash: result.transaction_hash\n };\n } catch (err) {\n // If the view method is not available or fails, fall back to access key polling.\n // eslint-disable-next-line no-console\n console.warn('[EmailRecoveryFlow] get_verification_result view failed; falling back to access key polling', err);\n return null;\n }\n }\n\n private async loadPending(\n accountId: AccountId,\n nearPublicKey?: string\n ): Promise<PendingEmailRecovery | null> {\n const { pendingTtlMs } = this.getConfig();\n\n const indexKey = this.getPendingIndexKey(accountId);\n const indexedNearPublicKey = await IndexedDBManager.clientDB.getAppState<string>(indexKey);\n const resolvedNearPublicKey = nearPublicKey ?? indexedNearPublicKey;\n if (!resolvedNearPublicKey) {\n return null;\n }\n\n const recordKey = this.getPendingRecordKey(accountId, resolvedNearPublicKey);\n const record = await IndexedDBManager.clientDB.getAppState<PendingEmailRecovery>(recordKey);\n const shouldClearIndex = indexedNearPublicKey === resolvedNearPublicKey;\n if (!record) {\n if (shouldClearIndex) {\n await IndexedDBManager.clientDB.setAppState(indexKey, undefined as any).catch(() => { });\n }\n return null;\n }\n\n if (Date.now() - record.createdAt > pendingTtlMs) {\n await IndexedDBManager.clientDB.setAppState(recordKey, undefined as any).catch(() => { });\n if (shouldClearIndex) {\n await IndexedDBManager.clientDB.setAppState(indexKey, undefined as any).catch(() => { });\n }\n return null;\n }\n\n // Keep the per-account pointer updated so `finalizeEmailRecovery({ accountId })` can resume.\n await IndexedDBManager.clientDB.setAppState(indexKey, record.nearPublicKey).catch(() => { });\n return record;\n }\n\n private async savePending(rec: PendingEmailRecovery): Promise<void> {\n const key = this.getPendingRecordKey(rec.accountId, rec.nearPublicKey);\n await IndexedDBManager.clientDB.setAppState(key, rec);\n await IndexedDBManager.clientDB.setAppState(this.getPendingIndexKey(rec.accountId), rec.nearPublicKey).catch(() => { });\n this.pending = rec;\n }\n\n private async clearPending(accountId: AccountId, nearPublicKey?: string): Promise<void> {\n const indexKey = this.getPendingIndexKey(accountId);\n const idx = await IndexedDBManager.clientDB.getAppState<string>(indexKey).catch(() => undefined);\n\n const resolvedNearPublicKey = nearPublicKey || idx || '';\n if (resolvedNearPublicKey) {\n await IndexedDBManager.clientDB\n .setAppState(this.getPendingRecordKey(accountId, resolvedNearPublicKey), undefined as any)\n .catch(() => { });\n }\n\n if (!nearPublicKey || idx === nearPublicKey) {\n await IndexedDBManager.clientDB.setAppState(indexKey, undefined as any).catch(() => { });\n }\n\n if (\n this.pending\n && this.pending.accountId === accountId\n && (!nearPublicKey || this.pending.nearPublicKey === nearPublicKey)\n ) {\n this.pending = null;\n }\n }\n\n getState() {\n return {\n phase: this.phase,\n pending: this.pending,\n error: this.error,\n };\n }\n\n async buildMailtoUrl(args: { accountId: string; nearPublicKey?: string }): Promise<string> {\n const { accountId, nearPublicKey } = args;\n this.cancelled = false;\n this.error = undefined;\n\n const validation = validateNearAccountId(accountId as AccountId);\n if (!validation.valid) {\n const err = this.emitError(3, `Invalid NEAR account ID: ${validation.error}`);\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const nearAccountId = toAccountId(accountId as string);\n let rec = this.pending;\n if (!rec || rec.accountId !== nearAccountId || (nearPublicKey && rec.nearPublicKey !== nearPublicKey)) {\n rec = await this.loadPending(nearAccountId, nearPublicKey);\n this.pending = rec;\n }\n\n if (!rec) {\n const err = this.emitError(3, 'No pending email recovery record found for this account');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n if (rec.status === 'error') {\n const err = this.emitError(3, 'Pending email recovery is in an error state; please restart the flow');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n if (rec.status === 'finalizing' || rec.status === 'complete') {\n const err = this.emitError(3, 'Recovery email has already been processed on-chain for this request');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const mailtoUrl =\n rec.status === 'awaiting-email'\n ? await this.buildMailtoUrlAndUpdateStatus(rec)\n : this.buildMailtoUrlInternal(rec);\n this.phase = EmailRecoveryPhase.STEP_3_AWAIT_EMAIL;\n this.emit({\n step: 3,\n phase: EmailRecoveryPhase.STEP_3_AWAIT_EMAIL,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'New device key created; please send the recovery email from your registered address.',\n data: {\n accountId: rec.accountId,\n recoveryEmail: rec.recoveryEmail,\n nearPublicKey: rec.nearPublicKey,\n requestId: rec.requestId,\n mailtoUrl,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n await this.options?.afterCall?.(true, undefined as any);\n return mailtoUrl;\n }\n\n async start(args: { accountId: string; recoveryEmail: string }): Promise<{ mailtoUrl: string; nearPublicKey: string }> {\n const { accountId, recoveryEmail } = args;\n this.cancelled = false;\n this.error = undefined;\n this.phase = EmailRecoveryPhase.STEP_1_PREPARATION;\n\n this.emit({\n step: 1,\n phase: EmailRecoveryPhase.STEP_1_PREPARATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Preparing email recovery...',\n });\n\n const validation = validateNearAccountId(accountId as AccountId);\n if (!validation.valid) {\n const err = this.emitError(1, `Invalid NEAR account ID: ${validation.error}`);\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const nearAccountId = toAccountId(accountId as string);\n const { minBalanceYocto } = this.getConfig();\n const STORAGE_PRICE_PER_BYTE = BigInt('10000000000000000000'); // 1e19 yocto NEAR per byte\n\n try {\n const accountView = await this.context.nearClient.viewAccount(nearAccountId);\n const amount = BigInt(accountView.amount || '0');\n const locked = BigInt((accountView as any).locked || '0');\n const storageUsage = BigInt((accountView as any).storage_usage || 0);\n const storageCost = storageUsage * STORAGE_PRICE_PER_BYTE;\n const rawAvailable = amount - locked - storageCost;\n const available = rawAvailable > 0 ? rawAvailable : BigInt(0);\n if (available < BigInt(minBalanceYocto)) {\n const err = this.emitError(\n 1,\n `This account does not have enough NEAR to finalize recovery. Available: ${available.toString()} yocto; required: ${String(minBalanceYocto)}. Please top up and try again.`\n );\n await this.options?.afterCall?.(false);\n throw err;\n }\n } catch (e: any) {\n const err = this.emitError(1, e?.message || 'Failed to fetch account balance for recovery');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const canonicalEmail = String(recoveryEmail || '').trim().toLowerCase();\n if (!canonicalEmail) {\n const err = this.emitError(1, 'Recovery email is required for email-based account recovery');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n // Determine deviceNumber from on-chain authenticators\n let deviceNumber = 1;\n try {\n const { syncAuthenticatorsContractCall } = await import('../rpcCalls');\n const authenticators = await syncAuthenticatorsContractCall(\n this.context.nearClient,\n this.context.configs.contractId,\n nearAccountId\n );\n const numbers = authenticators\n .map((a: any) => a?.authenticator?.deviceNumber)\n .filter((n: any) => typeof n === 'number' && Number.isFinite(n)) as number[];\n const max = numbers.length > 0 ? Math.max(...numbers) : 0;\n deviceNumber = max + 1;\n } catch {\n deviceNumber = 1;\n }\n\n this.phase = EmailRecoveryPhase.STEP_2_TOUCH_ID_REGISTRATION;\n this.emit({\n step: 2,\n phase: EmailRecoveryPhase.STEP_2_TOUCH_ID_REGISTRATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Collecting passkey for email recovery...',\n });\n\n try {\n const confirmerText = {\n title: this.options?.confirmerText?.title ?? 'Register New Recovery Account',\n body: this.options?.confirmerText?.body ?? 'Create a recovery account and send an encrypted email to recover your account.',\n };\n const confirm = await this.context.webAuthnManager.requestRegistrationCredentialConfirmation({\n nearAccountId,\n deviceNumber,\n confirmerText,\n confirmationConfigOverride: this.options?.confirmationConfig,\n });\n if (!confirm.confirmed || !confirm.credential) {\n const err = this.emitError(2, 'User cancelled email recovery TouchID confirmation');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const vrfDerivationResult = await this.context.webAuthnManager.deriveVrfKeypair({\n credential: confirm.credential,\n nearAccountId,\n });\n\n if (!vrfDerivationResult.success || !vrfDerivationResult.encryptedVrfKeypair) {\n const err = this.emitError(2, 'Failed to derive VRF keypair from PRF for email recovery');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const nearKeyResult = await this.context.webAuthnManager.deriveNearKeypairAndEncryptFromSerialized({\n nearAccountId,\n credential: confirm.credential,\n options: { deviceNumber },\n });\n\n if (!nearKeyResult.success || !nearKeyResult.publicKey) {\n const err = this.emitError(2, 'Failed to derive NEAR keypair for email recovery');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const rec: PendingEmailRecovery = {\n accountId: nearAccountId,\n recoveryEmail: canonicalEmail,\n deviceNumber,\n nearPublicKey: nearKeyResult.publicKey,\n requestId: generateEmailRecoveryRequestId(),\n encryptedVrfKeypair: vrfDerivationResult.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: vrfDerivationResult.serverEncryptedVrfKeypair || null,\n vrfPublicKey: vrfDerivationResult.vrfPublicKey,\n credential: confirm.credential,\n vrfChallenge: confirm.vrfChallenge || undefined,\n createdAt: Date.now(),\n status: 'awaiting-email',\n };\n\n const mailtoUrl = await this.buildMailtoUrlAndUpdateStatus(rec);\n\n this.phase = EmailRecoveryPhase.STEP_3_AWAIT_EMAIL;\n this.emit({\n step: 3,\n phase: EmailRecoveryPhase.STEP_3_AWAIT_EMAIL,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'New device key created; please send the recovery email from your registered address.',\n data: {\n accountId: rec.accountId,\n recoveryEmail: rec.recoveryEmail,\n nearPublicKey: rec.nearPublicKey,\n requestId: rec.requestId,\n mailtoUrl,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n await this.options?.afterCall?.(true, undefined as any);\n\n return { mailtoUrl, nearPublicKey: rec.nearPublicKey };\n } catch (e: any) {\n const err = this.emitError(2, e?.message || 'Email recovery TouchID/derivation failed');\n await this.options?.afterCall?.(false);\n throw err;\n }\n }\n\n private buildMailtoUrlInternal(rec: PendingEmailRecovery): string {\n const { mailtoAddress } = this.getConfig();\n const to = encodeURIComponent(mailtoAddress);\n const subject = encodeURIComponent(`recover-${rec.requestId} ${rec.accountId} ${rec.nearPublicKey}`);\n const body = encodeURIComponent(`Recovering account ${rec.accountId} with a new passkey.`);\n return `mailto:${to}?subject=${subject}&body=${body}`;\n }\n\n private async buildMailtoUrlAndUpdateStatus(rec: PendingEmailRecovery): Promise<string> {\n rec.status = 'awaiting-add-key';\n await this.savePending(rec);\n return this.buildMailtoUrlInternal(rec);\n }\n\n async startPolling(args: { accountId: string; nearPublicKey?: string }): Promise<void> {\n const { accountId, nearPublicKey } = args;\n this.cancelled = false;\n this.error = undefined;\n\n const validation = validateNearAccountId(accountId as AccountId);\n if (!validation.valid) {\n const err = this.emitError(4, `Invalid NEAR account ID: ${validation.error}`);\n await this.options?.afterCall?.(false);\n throw err;\n }\n const nearAccountId = toAccountId(accountId as string);\n\n let rec = this.pending;\n if (!rec || rec.accountId !== nearAccountId || (nearPublicKey && rec.nearPublicKey !== nearPublicKey)) {\n rec = await this.loadPending(nearAccountId, nearPublicKey);\n this.pending = rec;\n }\n if (!rec) {\n const err = this.emitError(4, 'No pending email recovery record found for this account');\n await this.options?.afterCall?.(false);\n throw err;\n }\n if (rec.status === 'error') {\n const err = this.emitError(4, 'Pending email recovery is in an error state; please restart the flow');\n await this.options?.afterCall?.(false);\n throw err;\n }\n if (rec.status === 'complete' || rec.status === 'finalizing') {\n await this.options?.afterCall?.(true, undefined as any);\n return;\n }\n if (rec.status === 'awaiting-email') {\n await this.buildMailtoUrlAndUpdateStatus(rec);\n }\n\n await this.pollUntilAddKey(rec);\n await this.options?.afterCall?.(true, undefined as any);\n }\n\n stopPolling(): void {\n this.cancelled = true;\n if (this.pollingTimer) {\n clearTimeout(this.pollingTimer);\n this.pollingTimer = undefined;\n }\n if (this.pollIntervalResolver) {\n this.pollIntervalResolver();\n this.pollIntervalResolver = undefined;\n }\n }\n\n /**\n * Best-effort cancellation and local state reset so callers can retry.\n * This does not remove any passkey created in the browser/OS (WebAuthn has no delete API),\n * but it will stop polling and clear the pending IndexedDB record for the given key.\n */\n async cancelAndReset(args?: { accountId?: string; nearPublicKey?: string }): Promise<void> {\n this.stopPolling();\n\n const normalizedAccountId = (args?.accountId || this.pending?.accountId || '').toString().trim();\n const nearPublicKey = (args?.nearPublicKey || this.pending?.nearPublicKey || '').toString().trim();\n\n if (normalizedAccountId) {\n try {\n await this.clearPending(toAccountId(normalizedAccountId), nearPublicKey);\n } catch {\n // best-effort\n }\n }\n\n this.pending = null;\n this.error = undefined;\n this.phase = EmailRecoveryPhase.STEP_1_PREPARATION;\n }\n\n async finalize(args: { accountId: string; nearPublicKey?: string }): Promise<void> {\n const { accountId, nearPublicKey } = args;\n this.cancelled = false;\n this.error = undefined;\n\n const validation = validateNearAccountId(accountId as AccountId);\n if (!validation.valid) {\n const err = this.emitError(4, `Invalid NEAR account ID: ${validation.error}`);\n await this.options?.afterCall?.(false);\n throw err;\n }\n const nearAccountId = toAccountId(accountId as string);\n\n let rec = this.pending;\n if (!rec || rec.accountId !== nearAccountId || (nearPublicKey && rec.nearPublicKey !== nearPublicKey)) {\n rec = await this.loadPending(nearAccountId, nearPublicKey);\n this.pending = rec;\n }\n if (!rec) {\n const err = this.emitError(4, 'No pending email recovery record found for this account');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n this.emit({\n step: 0,\n phase: EmailRecoveryPhase.RESUMED_FROM_PENDING,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Resuming email recovery from pending state...',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n status: rec.status,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n if (rec.status === 'complete') {\n this.phase = EmailRecoveryPhase.STEP_6_COMPLETE;\n this.emit({\n step: 6,\n phase: EmailRecoveryPhase.STEP_6_COMPLETE,\n status: EmailRecoveryStatus.SUCCESS,\n message: 'Email recovery already completed for this key.',\n });\n await this.options?.afterCall?.(true, undefined as any);\n return;\n }\n\n // Ensure verification has completed successfully before finalizing registration.\n await this.pollUntilAddKey(rec);\n await this.finalizeRegistration(rec);\n await this.options?.afterCall?.(true, undefined as any);\n }\n\n private async pollUntilAddKey(rec: PendingEmailRecovery): Promise<void> {\n const { pollingIntervalMs, maxPollingDurationMs, dkimVerifierAccountId } = this.getConfig();\n if (!dkimVerifierAccountId) {\n const err = this.emitError(4, 'Email recovery verification contract (dkimVerifierAccountId) is not configured');\n await this.options?.afterCall?.(false);\n throw err;\n }\n this.phase = EmailRecoveryPhase.STEP_4_POLLING_VERIFICATION_RESULT;\n this.pollingStartedAt = Date.now();\n let pollCount = 0;\n\n while (!this.cancelled) {\n pollCount += 1;\n const elapsed = Date.now() - (this.pollingStartedAt || 0);\n if (elapsed > maxPollingDurationMs) {\n const err = this.emitError(4, 'Timed out waiting for recovery email to be processed on-chain');\n rec.status = 'error';\n await this.savePending(rec);\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const verification = await this.checkVerificationStatus(rec);\n const completed = verification?.completed === true;\n const success = verification?.success === true;\n\n this.emit({\n step: 4,\n phase: EmailRecoveryPhase.STEP_4_POLLING_VERIFICATION_RESULT,\n status: EmailRecoveryStatus.PROGRESS,\n message: completed && success\n ? `Email verified for request ${rec.requestId}; finalizing registration`\n : `Waiting for email verification for request ${rec.requestId}`,\n data: {\n accountId: rec.accountId,\n requestId: rec.requestId,\n nearPublicKey: rec.nearPublicKey,\n transactionHash: verification?.transactionHash,\n elapsedMs: elapsed,\n pollCount,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n if (completed) {\n if (!success) {\n const err = this.emitError(4, verification?.errorMessage || 'Email verification failed');\n rec.status = 'error';\n await this.savePending(rec);\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n rec.status = 'finalizing';\n await this.savePending(rec);\n return;\n }\n\n // If cancellation happens mid-iteration (e.g. while awaiting a view call),\n // don't wait an extra pollingIntervalMs before unwinding.\n if (this.cancelled) break;\n\n await new Promise<void>(resolve => {\n this.pollIntervalResolver = resolve;\n this.pollingTimer = setTimeout(() => {\n this.pollIntervalResolver = undefined;\n this.pollingTimer = undefined;\n resolve();\n }, pollingIntervalMs);\n }).finally(() => {\n this.pollIntervalResolver = undefined;\n });\n }\n\n const err = this.emitError(4, 'Email recovery polling was cancelled');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n private async finalizeRegistration(rec: PendingEmailRecovery): Promise<void> {\n this.phase = EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION;\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Finalizing email recovery registration...',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n const nonceManager = this.context.webAuthnManager.getNonceManager();\n const accountId = toAccountId(rec.accountId);\n nonceManager.initializeUser(accountId, rec.nearPublicKey);\n\n try {\n if (!rec.vrfChallenge) {\n const err = this.emitError(5, 'Missing VRF challenge for email recovery registration');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const registrationResult = await this.context.webAuthnManager.signDevice2RegistrationWithStoredKey({\n nearAccountId: accountId,\n credential: rec.credential,\n vrfChallenge: rec.vrfChallenge,\n deterministicVrfPublicKey: rec.vrfPublicKey,\n deviceNumber: rec.deviceNumber,\n });\n\n if (!registrationResult.success || !registrationResult.signedTransaction) {\n const err = this.emitError(5, registrationResult.error || 'Failed to sign email recovery registration transaction');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const signedTx = registrationResult.signedTransaction;\n\n try {\n // Wait for finality so subsequent contract-gated signing (verify_authentication_response)\n // can reliably see the newly registered device/passkey.\n const txResult = await this.context.nearClient.sendTransaction(signedTx, DEFAULT_WAIT_STATUS.linkDeviceRegistration);\n try {\n const txHash = (txResult as any)?.transaction?.hash || (txResult as any)?.transaction_hash;\n if (txHash) {\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Registration transaction confirmed',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n transactionHash: txHash,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n // Store the new user record here, so `getLastUser()`\n // is aware of the new device context before any subsequent actions occur.\n try {\n // 1. Store the new user record (Device N) so that `getLastUser()` finds it\n // and `ensureCurrentPasskey` selects the correct credential for operations.\n await IndexedDBManager.clientDB.storeWebAuthnUserData({\n nearAccountId: accountId,\n deviceNumber: rec.deviceNumber,\n clientNearPublicKey: rec.nearPublicKey,\n passkeyCredential: {\n id: rec.credential.id,\n rawId: rec.credential.rawId,\n },\n encryptedVrfKeypair: rec.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: rec.serverEncryptedVrfKeypair || undefined,\n });\n\n // 2. Sync authenticators immediately for allowCredentials list\n const { syncAuthenticatorsContractCall } = await import('../rpcCalls');\n const authenticators = await syncAuthenticatorsContractCall(\n this.context.nearClient,\n this.context.configs.contractId,\n accountId\n );\n\n // Map RPC result to DB schema\n const mappedAuthenticators = authenticators.map(({ authenticator }) => ({\n credentialId: authenticator.credentialId,\n credentialPublicKey: authenticator.credentialPublicKey,\n transports: authenticator.transports,\n name: authenticator.name,\n registered: authenticator.registered.toISOString(),\n vrfPublicKey: authenticator.vrfPublicKeys?.[0] || '',\n deviceNumber: authenticator.deviceNumber,\n }));\n\n await IndexedDBManager.clientDB.syncAuthenticatorsFromContract(accountId, mappedAuthenticators);\n\n // 3. Set as active user\n await IndexedDBManager.clientDB.setLastUser(accountId, rec.deviceNumber);\n } catch (syncErr) {\n console.warn('[EmailRecoveryFlow] Failed to sync authenticators after recovery:', syncErr);\n // Non-fatal; user can still proceed but might need a refresh for some features\n }\n }\n } catch {\n // best-effort; do not fail flow\n }\n } catch (e: any) {\n const msg = String(e?.message || '');\n const err = this.emitError(\n 5,\n msg || 'Failed to broadcast email recovery registration transaction (insufficient funds or RPC error)'\n );\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n try {\n const txNonce = (signedTx.transaction as any)?.nonce;\n if (txNonce != null) {\n await nonceManager.updateNonceFromBlockchain(\n this.context.nearClient,\n String(txNonce)\n );\n }\n } catch {\n // best-effort; do not fail flow\n }\n\n const { webAuthnManager } = this.context;\n\n await webAuthnManager.storeUserData({\n nearAccountId: accountId,\n deviceNumber: rec.deviceNumber,\n clientNearPublicKey: rec.nearPublicKey,\n lastUpdated: Date.now(),\n passkeyCredential: {\n id: rec.credential.id,\n rawId: rec.credential.rawId,\n },\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: rec.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: rec.encryptedVrfKeypair.chacha20NonceB64u,\n },\n serverEncryptedVrfKeypair: rec.serverEncryptedVrfKeypair || undefined,\n } as any);\n\n try {\n const attestationB64u = rec.credential.response.attestationObject;\n const credentialPublicKey = await webAuthnManager.extractCosePublicKey(attestationB64u);\n await webAuthnManager.storeAuthenticator({\n nearAccountId: accountId,\n deviceNumber: rec.deviceNumber,\n credentialId: rec.credential.rawId,\n credentialPublicKey,\n transports: ['internal'],\n name: `Device ${rec.deviceNumber} Passkey for ${rec.accountId.split('.')[0]}`,\n registered: new Date().toISOString(),\n syncedAt: new Date().toISOString(),\n vrfPublicKey: rec.vrfPublicKey,\n });\n } catch {\n // best-effort; do not fail flow\n }\n\n await this.attemptAutoLogin(rec);\n\n rec.status = 'complete';\n await this.savePending(rec);\n await this.clearPending(rec.accountId, rec.nearPublicKey);\n\n this.phase = EmailRecoveryPhase.STEP_6_COMPLETE;\n this.emit({\n step: 6,\n phase: EmailRecoveryPhase.STEP_6_COMPLETE,\n status: EmailRecoveryStatus.SUCCESS,\n message: 'Email recovery completed successfully',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n } catch (e: any) {\n const err = this.emitError(5, e?.message || 'Email recovery finalization failed');\n await this.options?.afterCall?.(false);\n throw err;\n }\n }\n\n private async attemptAutoLogin(rec: PendingEmailRecovery): Promise<void> {\n try {\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Attempting auto-login with recovered device...',\n data: { autoLogin: 'progress' },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n const { webAuthnManager } = this.context;\n const accountId = toAccountId(rec.accountId);\n const deviceNumber = parseDeviceNumber(rec.deviceNumber, { min: 1 });\n if (deviceNumber === null) {\n throw new Error(`Invalid deviceNumber for auto-login: ${String(rec.deviceNumber)}`);\n }\n\n // Try Shamir 3-pass unlock first if configured and available\n if (\n rec.serverEncryptedVrfKeypair &&\n rec.serverEncryptedVrfKeypair.serverKeyId &&\n this.context.configs.vrfWorkerConfigs?.shamir3pass?.relayServerUrl\n ) {\n try {\n const unlockResult = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId: accountId,\n kek_s_b64u: rec.serverEncryptedVrfKeypair.kek_s_b64u,\n ciphertextVrfB64u: rec.serverEncryptedVrfKeypair.ciphertextVrfB64u,\n serverKeyId: rec.serverEncryptedVrfKeypair.serverKeyId,\n });\n\n if (unlockResult.success) {\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const vrfActiveForAccount =\n vrfStatus.active\n && vrfStatus.nearAccountId\n && String(vrfStatus.nearAccountId) === String(accountId);\n if (!vrfActiveForAccount) {\n throw new Error('VRF session inactive after Shamir3Pass unlock');\n }\n\n await webAuthnManager.setLastUser(accountId, deviceNumber);\n await webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n try { await getLoginSession(this.context, accountId); } catch { }\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.SUCCESS,\n message: `Welcome ${accountId}`,\n data: { autoLogin: 'success' },\n });\n return;\n }\n } catch (err) {\n // fall through to TouchID unlock\n console.warn('[EmailRecoveryFlow] Shamir 3-pass unlock failed, falling back to TouchID', err);\n }\n }\n\n // TouchID fallback unlock\n // Use a random challenge (no VRF required) to collect PRF outputs and unlock the stored VRF keypair.\n const authChallenge = createRandomVRFChallenge() as VRFChallenge;\n\n const storedCredentialId = String(rec.credential?.rawId || rec.credential?.id || '').trim();\n const credentialIds = storedCredentialId ? [storedCredentialId] : [];\n const authenticators = credentialIds.length > 0\n ? []\n : await webAuthnManager.getAuthenticatorsByUser(accountId);\n const authCredential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId: accountId,\n challenge: authChallenge,\n credentialIds: credentialIds.length > 0 ? credentialIds : authenticators.map((a) => a.credentialId),\n });\n\n if (storedCredentialId && authCredential.rawId !== storedCredentialId) {\n throw new Error('Wrong passkey selected during recovery auto-login; please use the newly recovered passkey.');\n }\n\n const vrfUnlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: accountId,\n encryptedVrfKeypair: rec.encryptedVrfKeypair,\n credential: authCredential,\n });\n\n if (!vrfUnlockResult.success) {\n throw new Error(vrfUnlockResult.error || 'VRF unlock failed during auto-login');\n }\n\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const vrfActiveForAccount =\n vrfStatus.active\n && vrfStatus.nearAccountId\n && String(vrfStatus.nearAccountId) === String(accountId);\n if (!vrfActiveForAccount) {\n throw new Error('VRF session inactive after TouchID unlock');\n }\n\n await webAuthnManager.setLastUser(accountId, deviceNumber);\n await webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n try { await getLoginSession(this.context, accountId); } catch { }\n\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.SUCCESS,\n message: `Welcome ${accountId}`,\n data: { autoLogin: 'success' },\n });\n\n } catch (err: any) {\n console.warn('[EmailRecoveryFlow] Auto-login failed after recovery', err);\n try {\n // Avoid leaving a stale/incompatible VRF keypair in-memory (can surface later as\n // \"Contract verification failed\" during signing). User can still log in manually.\n await this.context.webAuthnManager.clearVrfSession();\n } catch { }\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.ERROR,\n message: 'Auto-login failed; please log in manually on this device.',\n data: { error: err?.message || String(err), autoLogin: 'error' },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"emailRecovery.js","names":["e: any","elapsedMs","rec: PendingEmailRecovery","err","payload: StoreUserDataPayload","err: any"],"sources":["../../../../../../../src/core/TatchiPasskey/emailRecovery.ts"],"sourcesContent":["import type { PasskeyManagerContext } from './index';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport { validateNearAccountId } from '../../utils/validation';\nimport { toAccountId, type AccountId } from '../types/accountIds';\nimport {\n EmailRecoveryPhase,\n EmailRecoveryStatus,\n type EmailRecoverySSEEvent,\n type EventCallback,\n type AfterCall,\n} from '../types/sdkSentEvents';\nimport type { TatchiConfigs } from '../types/tatchi';\nimport {\n createRandomVRFChallenge,\n type EncryptedVRFKeypair,\n type ServerEncryptedVrfKeypair,\n type VRFChallenge,\n} from '../types/vrf-worker';\nimport type { WebAuthnRegistrationCredential } from '../types';\nimport type { ConfirmationConfig } from '../types/signer-worker';\nimport { DEFAULT_WAIT_STATUS } from '../types/rpc';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\nimport { getLoginSession } from './login';\nimport type { SignedTransaction } from '../NearClient';\nimport { EmailRecoveryPendingStore, type PendingStore } from '../EmailRecovery';\n\nexport type PendingEmailRecoveryStatus =\n | 'awaiting-email'\n | 'awaiting-add-key'\n | 'finalizing'\n | 'complete'\n | 'error';\n\nexport type PendingEmailRecovery = {\n accountId: AccountId;\n recoveryEmail: string;\n deviceNumber: number;\n nearPublicKey: string;\n requestId: string;\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n createdAt: number;\n status: PendingEmailRecoveryStatus;\n};\n\ntype PollTickResult<T> = { done: false } | { done: true; value: T };\n\ntype PollUntilResult<T> =\n | { status: 'completed'; value: T; elapsedMs: number; pollCount: number }\n | { status: 'timedOut'; elapsedMs: number; pollCount: number }\n | { status: 'cancelled'; elapsedMs: number; pollCount: number };\n\ntype VerificationOutcome =\n | { outcome: 'verified' }\n | { outcome: 'failed'; errorMessage: string };\n\ntype AutoLoginResult =\n | { success: true; method: 'shamir' | 'touchid' }\n | { success: false; reason: string };\n\ntype StoreUserDataPayload = Parameters<PasskeyManagerContext['webAuthnManager']['storeUserData']>[0];\n\ntype AccountViewLike = {\n amount: bigint | string;\n locked: bigint | string;\n storage_usage: number | bigint;\n};\n\ntype CollectedRecoveryCredential = {\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n};\n\ntype DerivedRecoveryKeys = {\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n nearPublicKey: string;\n};\n\nexport interface EmailRecoveryFlowOptions {\n onEvent?: EventCallback<EmailRecoverySSEEvent>;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<void>;\n pendingStore?: PendingStore;\n confirmerText?: { title?: string; body?: string };\n confirmationConfig?: Partial<ConfirmationConfig>;\n}\n\nfunction getEmailRecoveryConfig(configs: TatchiConfigs): {\n minBalanceYocto: string;\n pollingIntervalMs: number;\n maxPollingDurationMs: number;\n pendingTtlMs: number;\n mailtoAddress: string;\n dkimVerifierAccountId: string;\n verificationViewMethod: string;\n} {\n const relayerEmailCfg = configs.relayer.emailRecovery;\n const minBalanceYocto = String(relayerEmailCfg.minBalanceYocto);\n const pollingIntervalMs = Number(relayerEmailCfg.pollingIntervalMs);\n const maxPollingDurationMs = Number(relayerEmailCfg.maxPollingDurationMs);\n const pendingTtlMs = Number(relayerEmailCfg.pendingTtlMs);\n const mailtoAddress = String(relayerEmailCfg.mailtoAddress);\n const dkimVerifierAccountId = String(relayerEmailCfg.dkimVerifierAccountId);\n const verificationViewMethod = String(relayerEmailCfg.verificationViewMethod);\n return {\n minBalanceYocto,\n pollingIntervalMs,\n maxPollingDurationMs,\n pendingTtlMs,\n mailtoAddress,\n dkimVerifierAccountId,\n verificationViewMethod,\n };\n}\n\nexport function generateEmailRecoveryRequestId(): string {\n // 6-character A–Z0–9 identifier, suitable for short-lived correlation.\n const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';\n const length = 6;\n const bytes = new Uint8Array(length);\n (globalThis.crypto || window.crypto).getRandomValues(bytes);\n let out = '';\n for (let i = 0; i < length; i++) {\n out += alphabet[bytes[i] % alphabet.length];\n }\n return out;\n}\n\nexport class EmailRecoveryFlow {\n private context: PasskeyManagerContext;\n private options?: EmailRecoveryFlowOptions;\n private pendingStore: PendingStore;\n private pending: PendingEmailRecovery | null = null;\n private phase: EmailRecoveryPhase = EmailRecoveryPhase.STEP_1_PREPARATION;\n private pollingTimer: any;\n private pollIntervalResolver?: (value?: void | PromiseLike<void>) => void;\n private pollingStartedAt: number | null = null;\n private cancelled = false;\n private error?: Error;\n\n constructor(context: PasskeyManagerContext, options?: EmailRecoveryFlowOptions) {\n this.context = context;\n this.options = options;\n this.pendingStore = options?.pendingStore ?? new EmailRecoveryPendingStore({\n getPendingTtlMs: () => this.getConfig().pendingTtlMs,\n });\n }\n\n setOptions(options?: EmailRecoveryFlowOptions) {\n if (!options) return;\n this.options = { ...(this.options || {}), ...options };\n if (options.pendingStore) {\n this.pendingStore = options.pendingStore;\n }\n }\n private emit(event: EmailRecoverySSEEvent) {\n this.options?.onEvent?.(event);\n }\n\n private emitError(step: number, message: string): Error {\n const err = new Error(message);\n this.phase = EmailRecoveryPhase.ERROR;\n this.error = err;\n this.emit({\n step,\n phase: EmailRecoveryPhase.ERROR,\n status: EmailRecoveryStatus.ERROR,\n message,\n error: message,\n } as EmailRecoverySSEEvent & { error: string });\n this.options?.onError?.(err);\n return err;\n }\n\n private async fail(step: number, message: string): Promise<never> {\n const err = this.emitError(step, message);\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n private async assertValidAccountIdOrFail(step: number, accountId: string): Promise<AccountId> {\n const validation = validateNearAccountId(accountId as AccountId);\n if (!validation.valid) {\n await this.fail(step, `Invalid NEAR account ID: ${validation.error}`);\n }\n return toAccountId(accountId as string);\n }\n\n private async resolvePendingOrFail(\n step: number,\n args: { accountId: AccountId; nearPublicKey?: string },\n options?: {\n allowErrorStatus?: boolean;\n missingMessage?: string;\n errorStatusMessage?: string;\n }\n ): Promise<PendingEmailRecovery> {\n const {\n allowErrorStatus = true,\n missingMessage = 'No pending email recovery record found for this account',\n errorStatusMessage = 'Pending email recovery is in an error state; please restart the flow',\n } = options ?? {};\n\n let rec = this.pending;\n if (!rec || rec.accountId !== args.accountId || (args.nearPublicKey && rec.nearPublicKey !== args.nearPublicKey)) {\n rec = await this.loadPending(args.accountId, args.nearPublicKey);\n this.pending = rec;\n }\n\n if (!rec) {\n await this.fail(step, missingMessage);\n }\n\n const resolved = rec as PendingEmailRecovery;\n if (!allowErrorStatus && resolved.status === 'error') {\n await this.fail(step, errorStatusMessage);\n }\n\n return resolved;\n }\n\n private getConfig() {\n return getEmailRecoveryConfig(this.context.configs);\n }\n\n private toBigInt(value: bigint | number | string | null | undefined): bigint {\n if (typeof value === 'bigint') return value;\n if (typeof value === 'number') return BigInt(value);\n if (typeof value === 'string' && value.length > 0) return BigInt(value);\n return BigInt(0);\n }\n\n private computeAvailableBalance(accountView: AccountViewLike): bigint {\n const STORAGE_PRICE_PER_BYTE = BigInt('10000000000000000000'); // 1e19 yocto NEAR per byte\n const amount = this.toBigInt(accountView.amount);\n const locked = this.toBigInt(accountView.locked);\n const storageUsage = this.toBigInt(accountView.storage_usage);\n const storageCost = storageUsage * STORAGE_PRICE_PER_BYTE;\n const rawAvailable = amount - locked - storageCost;\n return rawAvailable > 0 ? rawAvailable : BigInt(0);\n }\n\n private async assertSufficientBalance(nearAccountId: AccountId): Promise<void> {\n const { minBalanceYocto } = this.getConfig();\n\n try {\n const accountView = await this.context.nearClient.viewAccount(nearAccountId);\n const available = this.computeAvailableBalance(accountView);\n if (available < BigInt(minBalanceYocto)) {\n await this.fail(\n 1,\n `This account does not have enough NEAR to finalize recovery. Available: ${available.toString()} yocto; required: ${String(minBalanceYocto)}. Please top up and try again.`\n );\n }\n } catch (e: any) {\n await this.fail(1, e?.message || 'Failed to fetch account balance for recovery');\n }\n }\n\n private async getCanonicalRecoveryEmailOrFail(recoveryEmail: string): Promise<string> {\n const canonicalEmail = String(recoveryEmail || '').trim().toLowerCase();\n if (!canonicalEmail) {\n await this.fail(1, 'Recovery email is required for email-based account recovery');\n }\n return canonicalEmail;\n }\n\n private async getNextDeviceNumberFromContract(nearAccountId: AccountId): Promise<number> {\n try {\n const { syncAuthenticatorsContractCall } = await import('../rpcCalls');\n const authenticators = await syncAuthenticatorsContractCall(\n this.context.nearClient,\n this.context.configs.contractId,\n nearAccountId\n );\n const numbers = authenticators\n .map((a: any) => a?.authenticator?.deviceNumber)\n .filter((n: any) => typeof n === 'number' && Number.isFinite(n)) as number[];\n const max = numbers.length > 0 ? Math.max(...numbers) : 0;\n return max + 1;\n } catch {\n return 1;\n }\n }\n\n private async collectRecoveryCredentialOrFail(\n nearAccountId: AccountId,\n deviceNumber: number\n ): Promise<CollectedRecoveryCredential> {\n const confirmerText = {\n title: this.options?.confirmerText?.title ?? 'Register New Recovery Account',\n body: this.options?.confirmerText?.body ?? 'Create a recovery account and send an encrypted email to recover your account.',\n };\n const confirm = await this.context.webAuthnManager.requestRegistrationCredentialConfirmation({\n nearAccountId,\n deviceNumber,\n confirmerText,\n confirmationConfigOverride: this.options?.confirmationConfig,\n });\n\n if (!confirm.confirmed || !confirm.credential) {\n await this.fail(2, 'User cancelled email recovery TouchID confirmation');\n }\n\n return {\n credential: confirm.credential,\n vrfChallenge: confirm.vrfChallenge || undefined,\n };\n }\n\n private async deriveRecoveryKeysOrFail(\n nearAccountId: AccountId,\n deviceNumber: number,\n credential: WebAuthnRegistrationCredential\n ): Promise<DerivedRecoveryKeys> {\n const vrfDerivationResult = await this.context.webAuthnManager.deriveVrfKeypair({\n credential,\n nearAccountId,\n });\n\n if (!vrfDerivationResult.success || !vrfDerivationResult.encryptedVrfKeypair) {\n await this.fail(2, 'Failed to derive VRF keypair from PRF for email recovery');\n }\n\n const nearKeyResult = await this.context.webAuthnManager.deriveNearKeypairAndEncryptFromSerialized({\n nearAccountId,\n credential,\n options: { deviceNumber },\n });\n\n if (!nearKeyResult.success || !nearKeyResult.publicKey) {\n await this.fail(2, 'Failed to derive NEAR keypair for email recovery');\n }\n\n return {\n encryptedVrfKeypair: vrfDerivationResult.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: vrfDerivationResult.serverEncryptedVrfKeypair || null,\n vrfPublicKey: vrfDerivationResult.vrfPublicKey,\n nearPublicKey: nearKeyResult.publicKey,\n };\n }\n\n private emitAwaitEmail(rec: PendingEmailRecovery, mailtoUrl: string): void {\n this.phase = EmailRecoveryPhase.STEP_3_AWAIT_EMAIL;\n this.emit({\n step: 3,\n phase: EmailRecoveryPhase.STEP_3_AWAIT_EMAIL,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'New device key created; please send the recovery email from your registered address.',\n data: {\n accountId: rec.accountId,\n recoveryEmail: rec.recoveryEmail,\n nearPublicKey: rec.nearPublicKey,\n requestId: rec.requestId,\n mailtoUrl,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n }\n\n private emitAutoLoginEvent(\n status: EmailRecoveryStatus,\n message: string,\n data: Record<string, unknown>\n ): void {\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status,\n message,\n data,\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n }\n\n private async checkViaDkimViewMethod(\n rec: PendingEmailRecovery\n ): Promise<{ completed: boolean; success: boolean; errorMessage?: string; transactionHash?: string } | null> {\n const { dkimVerifierAccountId, verificationViewMethod } = this.getConfig();\n if (!dkimVerifierAccountId) return null;\n\n try {\n const { getEmailRecoveryVerificationResult } = await import('../rpcCalls');\n const result = await getEmailRecoveryVerificationResult(\n this.context.nearClient,\n dkimVerifierAccountId,\n verificationViewMethod,\n rec.requestId\n );\n\n if (!result) {\n return { completed: false, success: false };\n }\n\n if (!result.verified) {\n const errorMessage = result.error_message || result.error_code || 'Email verification failed on relayer/contract';\n return {\n completed: true,\n success: false,\n errorMessage,\n transactionHash: result.transaction_hash,\n };\n }\n\n // Optional safety checks: ensure the bound account/key match expectations when available.\n if (result.account_id && result.account_id !== rec.accountId) {\n return {\n completed: true,\n success: false,\n errorMessage: 'Email verification account_id does not match requested account.',\n transactionHash: result.transaction_hash,\n };\n }\n if (result.new_public_key && result.new_public_key !== rec.nearPublicKey) {\n return {\n completed: true,\n success: false,\n errorMessage: 'Email verification new_public_key does not match expected recovery key.',\n transactionHash: result.transaction_hash,\n };\n }\n\n return {\n completed: true,\n success: true,\n transactionHash: result.transaction_hash\n };\n } catch (err) {\n // Treat view errors as retryable; keep polling the view method.\n // eslint-disable-next-line no-console\n console.warn('[EmailRecoveryFlow] get_verification_result view failed; will retry', err);\n return null;\n }\n }\n\n private buildPollingEventData(\n rec: PendingEmailRecovery,\n details: { transactionHash?: string; elapsedMs: number; pollCount: number }\n ): Record<string, unknown> {\n return {\n accountId: rec.accountId,\n requestId: rec.requestId,\n nearPublicKey: rec.nearPublicKey,\n transactionHash: details.transactionHash,\n elapsedMs: details.elapsedMs,\n pollCount: details.pollCount,\n };\n }\n\n private async sleepForPollInterval(ms: number): Promise<void> {\n await new Promise<void>(resolve => {\n this.pollIntervalResolver = resolve;\n this.pollingTimer = setTimeout(() => {\n this.pollIntervalResolver = undefined;\n this.pollingTimer = undefined;\n resolve();\n }, ms);\n }).finally(() => {\n this.pollIntervalResolver = undefined;\n });\n }\n\n private async pollUntil<T>(args: {\n intervalMs: number;\n timeoutMs: number;\n isCancelled: () => boolean;\n tick: (ctx: { elapsedMs: number; pollCount: number }) => Promise<PollTickResult<T>>;\n sleep?: (ms: number) => Promise<void>;\n now?: () => number;\n }): Promise<PollUntilResult<T>> {\n const now = args.now ?? Date.now;\n const sleep = args.sleep ?? this.sleepForPollInterval.bind(this);\n const startedAt = now();\n let pollCount = 0;\n\n while (!args.isCancelled()) {\n pollCount += 1;\n const elapsedMs = now() - startedAt;\n if (elapsedMs > args.timeoutMs) {\n return { status: 'timedOut', elapsedMs, pollCount };\n }\n\n const result = await args.tick({ elapsedMs, pollCount });\n if (result.done) {\n return { status: 'completed', value: result.value, elapsedMs, pollCount };\n }\n\n if (args.isCancelled()) {\n return { status: 'cancelled', elapsedMs, pollCount };\n }\n\n await sleep(args.intervalMs);\n }\n\n const elapsedMs = now() - startedAt;\n return { status: 'cancelled', elapsedMs, pollCount };\n }\n\n private async loadPending(\n accountId: AccountId,\n nearPublicKey?: string\n ): Promise<PendingEmailRecovery | null> {\n return this.pendingStore.get(accountId, nearPublicKey);\n }\n\n private async savePending(rec: PendingEmailRecovery): Promise<void> {\n await this.pendingStore.set(rec);\n this.pending = rec;\n }\n\n private async clearPending(accountId: AccountId, nearPublicKey?: string): Promise<void> {\n await this.pendingStore.clear(accountId, nearPublicKey);\n\n if (\n this.pending\n && this.pending.accountId === accountId\n && (!nearPublicKey || this.pending.nearPublicKey === nearPublicKey)\n ) {\n this.pending = null;\n }\n }\n\n getState() {\n return {\n phase: this.phase,\n pending: this.pending,\n error: this.error,\n };\n }\n\n async buildMailtoUrl(args: { accountId: string; nearPublicKey?: string }): Promise<string> {\n const { accountId, nearPublicKey } = args;\n this.cancelled = false;\n this.error = undefined;\n\n const nearAccountId = await this.assertValidAccountIdOrFail(3, accountId);\n const rec = await this.resolvePendingOrFail(\n 3,\n { accountId: nearAccountId, nearPublicKey },\n { allowErrorStatus: false }\n );\n\n if (rec.status === 'finalizing' || rec.status === 'complete') {\n await this.fail(3, 'Recovery email has already been processed on-chain for this request');\n }\n\n const mailtoUrl =\n rec.status === 'awaiting-email'\n ? await this.buildMailtoUrlAndUpdateStatus(rec)\n : this.buildMailtoUrlInternal(rec);\n this.emitAwaitEmail(rec, mailtoUrl);\n await this.options?.afterCall?.(true, undefined);\n return mailtoUrl;\n }\n\n async start(args: { accountId: string; recoveryEmail: string }): Promise<{ mailtoUrl: string; nearPublicKey: string }> {\n const { accountId, recoveryEmail } = args;\n this.cancelled = false;\n this.error = undefined;\n this.phase = EmailRecoveryPhase.STEP_1_PREPARATION;\n\n this.emit({\n step: 1,\n phase: EmailRecoveryPhase.STEP_1_PREPARATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Preparing email recovery...',\n });\n\n const nearAccountId = await this.assertValidAccountIdOrFail(1, accountId);\n await this.assertSufficientBalance(nearAccountId);\n const canonicalEmail = await this.getCanonicalRecoveryEmailOrFail(recoveryEmail);\n\n // Determine deviceNumber from on-chain authenticators\n const deviceNumber = await this.getNextDeviceNumberFromContract(nearAccountId);\n\n this.phase = EmailRecoveryPhase.STEP_2_TOUCH_ID_REGISTRATION;\n this.emit({\n step: 2,\n phase: EmailRecoveryPhase.STEP_2_TOUCH_ID_REGISTRATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Collecting passkey for email recovery...',\n });\n\n try {\n const confirm = await this.collectRecoveryCredentialOrFail(nearAccountId, deviceNumber);\n const derivedKeys = await this.deriveRecoveryKeysOrFail(nearAccountId, deviceNumber, confirm.credential);\n\n const rec: PendingEmailRecovery = {\n accountId: nearAccountId,\n recoveryEmail: canonicalEmail,\n deviceNumber,\n nearPublicKey: derivedKeys.nearPublicKey,\n requestId: generateEmailRecoveryRequestId(),\n encryptedVrfKeypair: derivedKeys.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: derivedKeys.serverEncryptedVrfKeypair,\n vrfPublicKey: derivedKeys.vrfPublicKey,\n credential: confirm.credential,\n vrfChallenge: confirm.vrfChallenge || undefined,\n createdAt: Date.now(),\n status: 'awaiting-email',\n };\n\n const mailtoUrl = await this.buildMailtoUrlAndUpdateStatus(rec);\n\n this.emitAwaitEmail(rec, mailtoUrl);\n\n await this.options?.afterCall?.(true, undefined);\n\n return { mailtoUrl, nearPublicKey: rec.nearPublicKey };\n } catch (e: any) {\n const err = this.emitError(2, e?.message || 'Email recovery TouchID/derivation failed');\n await this.options?.afterCall?.(false);\n throw err;\n }\n }\n\n private buildMailtoUrlInternal(rec: PendingEmailRecovery): string {\n const { mailtoAddress } = this.getConfig();\n const to = encodeURIComponent(mailtoAddress);\n const subject = encodeURIComponent(`recover-${rec.requestId} ${rec.accountId} ${rec.nearPublicKey}`);\n const body = encodeURIComponent(`Recovering account ${rec.accountId} with a new passkey.`);\n return `mailto:${to}?subject=${subject}&body=${body}`;\n }\n\n private async buildMailtoUrlAndUpdateStatus(rec: PendingEmailRecovery): Promise<string> {\n rec.status = 'awaiting-add-key';\n await this.savePending(rec);\n return this.buildMailtoUrlInternal(rec);\n }\n\n async startPolling(args: { accountId: string; nearPublicKey?: string }): Promise<void> {\n const { accountId, nearPublicKey } = args;\n this.cancelled = false;\n this.error = undefined;\n\n const nearAccountId = await this.assertValidAccountIdOrFail(4, accountId);\n const rec = await this.resolvePendingOrFail(\n 4,\n { accountId: nearAccountId, nearPublicKey },\n { allowErrorStatus: false }\n );\n if (rec.status === 'complete' || rec.status === 'finalizing') {\n await this.options?.afterCall?.(true, undefined);\n return;\n }\n if (rec.status === 'awaiting-email') {\n await this.buildMailtoUrlAndUpdateStatus(rec);\n }\n\n await this.pollUntilAddKey(rec);\n await this.options?.afterCall?.(true, undefined);\n }\n\n stopPolling(): void {\n this.cancelled = true;\n if (this.pollingTimer) {\n clearTimeout(this.pollingTimer);\n this.pollingTimer = undefined;\n }\n if (this.pollIntervalResolver) {\n this.pollIntervalResolver();\n this.pollIntervalResolver = undefined;\n }\n }\n\n /**\n * Best-effort cancellation and local state reset so callers can retry.\n * This does not remove any passkey created in the browser/OS (WebAuthn has no delete API),\n * but it will stop polling and clear the pending IndexedDB record for the given key.\n */\n async cancelAndReset(args?: { accountId?: string; nearPublicKey?: string }): Promise<void> {\n this.stopPolling();\n\n const normalizedAccountId = (args?.accountId || this.pending?.accountId || '').toString().trim();\n const nearPublicKey = (args?.nearPublicKey || this.pending?.nearPublicKey || '').toString().trim();\n\n if (normalizedAccountId) {\n try {\n await this.clearPending(toAccountId(normalizedAccountId), nearPublicKey);\n } catch {\n // best-effort\n }\n }\n\n this.pending = null;\n this.error = undefined;\n this.phase = EmailRecoveryPhase.STEP_1_PREPARATION;\n }\n\n async finalize(args: { accountId: string; nearPublicKey?: string }): Promise<void> {\n const { accountId, nearPublicKey } = args;\n this.cancelled = false;\n this.error = undefined;\n\n const nearAccountId = await this.assertValidAccountIdOrFail(4, accountId);\n const rec = await this.resolvePendingOrFail(\n 4,\n { accountId: nearAccountId, nearPublicKey },\n { allowErrorStatus: true }\n );\n\n this.emit({\n step: 0,\n phase: EmailRecoveryPhase.RESUMED_FROM_PENDING,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Resuming email recovery from pending state...',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n status: rec.status,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n if (rec.status === 'complete') {\n this.phase = EmailRecoveryPhase.STEP_6_COMPLETE;\n this.emit({\n step: 6,\n phase: EmailRecoveryPhase.STEP_6_COMPLETE,\n status: EmailRecoveryStatus.SUCCESS,\n message: 'Email recovery already completed for this key.',\n });\n await this.options?.afterCall?.(true, undefined);\n return;\n }\n\n // Ensure verification has completed successfully before finalizing registration.\n await this.pollUntilAddKey(rec);\n await this.finalizeRegistration(rec);\n await this.options?.afterCall?.(true, undefined);\n }\n\n private async pollUntilAddKey(rec: PendingEmailRecovery): Promise<void> {\n const { pollingIntervalMs, maxPollingDurationMs, dkimVerifierAccountId } = this.getConfig();\n if (!dkimVerifierAccountId) {\n const err = this.emitError(4, 'Email recovery verification contract (dkimVerifierAccountId) is not configured');\n await this.options?.afterCall?.(false);\n throw err;\n }\n this.phase = EmailRecoveryPhase.STEP_4_POLLING_VERIFICATION_RESULT;\n this.pollingStartedAt = Date.now();\n\n const pollResult = await this.pollUntil<VerificationOutcome>({\n intervalMs: pollingIntervalMs,\n timeoutMs: maxPollingDurationMs,\n isCancelled: () => this.cancelled,\n tick: async ({ elapsedMs, pollCount }) => {\n const verification = await this.checkViaDkimViewMethod(rec);\n const completed = verification?.completed === true;\n const success = verification?.success === true;\n\n this.emit({\n step: 4,\n phase: EmailRecoveryPhase.STEP_4_POLLING_VERIFICATION_RESULT,\n status: EmailRecoveryStatus.PROGRESS,\n message: completed && success\n ? `Email verified for request ${rec.requestId}; finalizing registration`\n : `Waiting for email verification for request ${rec.requestId}`,\n data: this.buildPollingEventData(rec, {\n transactionHash: verification?.transactionHash,\n elapsedMs,\n pollCount,\n }),\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n if (!completed) {\n return { done: false };\n }\n\n if (!success) {\n return {\n done: true,\n value: {\n outcome: 'failed',\n errorMessage: verification?.errorMessage || 'Email verification failed',\n },\n };\n }\n\n return { done: true, value: { outcome: 'verified' } };\n },\n });\n\n if (pollResult.status === 'completed') {\n if (pollResult.value.outcome === 'failed') {\n const err = this.emitError(4, pollResult.value.errorMessage);\n rec.status = 'error';\n await this.savePending(rec);\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n rec.status = 'finalizing';\n await this.savePending(rec);\n return;\n }\n\n if (pollResult.status === 'timedOut') {\n const err = this.emitError(4, 'Timed out waiting for recovery email to be processed on-chain');\n rec.status = 'error';\n await this.savePending(rec);\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n const err = this.emitError(4, 'Email recovery polling was cancelled');\n await this.options?.afterCall?.(false);\n throw err;\n }\n\n private initializeNonceManager(\n rec: PendingEmailRecovery\n ): {\n nonceManager: ReturnType<PasskeyManagerContext['webAuthnManager']['getNonceManager']>;\n accountId: AccountId;\n } {\n const nonceManager = this.context.webAuthnManager.getNonceManager();\n const accountId = toAccountId(rec.accountId);\n nonceManager.initializeUser(accountId, rec.nearPublicKey);\n return { nonceManager, accountId };\n }\n\n private async signRegistrationTx(rec: PendingEmailRecovery, accountId: AccountId): Promise<SignedTransaction> {\n const vrfChallenge = rec.vrfChallenge;\n if (!vrfChallenge) {\n return this.fail(5, 'Missing VRF challenge for email recovery registration');\n }\n\n const registrationResult = await this.context.webAuthnManager.signDevice2RegistrationWithStoredKey({\n nearAccountId: accountId,\n credential: rec.credential,\n vrfChallenge,\n deterministicVrfPublicKey: rec.vrfPublicKey,\n deviceNumber: rec.deviceNumber,\n });\n\n if (!registrationResult.success || !registrationResult.signedTransaction) {\n await this.fail(5, registrationResult.error || 'Failed to sign email recovery registration transaction');\n }\n\n return registrationResult.signedTransaction;\n }\n\n private async broadcastRegistrationTxAndWaitFinal(\n rec: PendingEmailRecovery,\n signedTx: SignedTransaction\n ): Promise<string | undefined> {\n try {\n const txResult = await this.context.nearClient.sendTransaction(\n signedTx,\n DEFAULT_WAIT_STATUS.linkDeviceRegistration\n );\n\n try {\n const txHash = (txResult as any)?.transaction?.hash || (txResult as any)?.transaction_hash;\n if (txHash) {\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Registration transaction confirmed',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n transactionHash: txHash,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n }\n return txHash;\n } catch {\n // best-effort; do not fail flow\n }\n } catch (e: any) {\n const msg = String(e?.message || '');\n await this.fail(\n 5,\n msg || 'Failed to broadcast email recovery registration transaction (insufficient funds or RPC error)'\n );\n }\n\n return undefined;\n }\n\n private async persistRecoveredUserRecordBestEffort(\n rec: PendingEmailRecovery,\n accountId: AccountId\n ): Promise<boolean> {\n try {\n await IndexedDBManager.clientDB.storeWebAuthnUserData({\n nearAccountId: accountId,\n deviceNumber: rec.deviceNumber,\n clientNearPublicKey: rec.nearPublicKey,\n passkeyCredential: {\n id: rec.credential.id,\n rawId: rec.credential.rawId,\n },\n encryptedVrfKeypair: rec.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: rec.serverEncryptedVrfKeypair || undefined,\n });\n return true;\n } catch (err) {\n console.warn('[EmailRecoveryFlow] Failed to store recovery user record:', err);\n return false;\n }\n }\n\n private mapAuthenticatorsFromContract(authenticators: Array<{ authenticator: any }>) {\n return authenticators.map(({ authenticator }) => ({\n credentialId: authenticator.credentialId,\n credentialPublicKey: authenticator.credentialPublicKey,\n transports: authenticator.transports,\n name: authenticator.name,\n registered: authenticator.registered.toISOString(),\n vrfPublicKey: authenticator.vrfPublicKeys?.[0] || '',\n deviceNumber: authenticator.deviceNumber,\n }));\n }\n\n private async syncAuthenticatorsBestEffort(accountId: AccountId): Promise<boolean> {\n try {\n const { syncAuthenticatorsContractCall } = await import('../rpcCalls');\n const authenticators = await syncAuthenticatorsContractCall(\n this.context.nearClient,\n this.context.configs.contractId,\n accountId\n );\n\n const mappedAuthenticators = this.mapAuthenticatorsFromContract(authenticators);\n await IndexedDBManager.clientDB.syncAuthenticatorsFromContract(accountId, mappedAuthenticators);\n return true;\n } catch (err) {\n console.warn('[EmailRecoveryFlow] Failed to sync authenticators after recovery:', err);\n return false;\n }\n }\n\n private async setLastUserBestEffort(accountId: AccountId, deviceNumber: number): Promise<boolean> {\n try {\n await IndexedDBManager.clientDB.setLastUser(accountId, deviceNumber);\n return true;\n } catch (err) {\n console.warn('[EmailRecoveryFlow] Failed to set last user after recovery:', err);\n return false;\n }\n }\n\n private async updateNonceBestEffort(\n nonceManager: ReturnType<PasskeyManagerContext['webAuthnManager']['getNonceManager']>,\n signedTx: SignedTransaction\n ): Promise<void> {\n try {\n const txNonce = (signedTx.transaction as any)?.nonce;\n if (txNonce != null) {\n await nonceManager.updateNonceFromBlockchain(\n this.context.nearClient,\n String(txNonce)\n );\n }\n } catch {\n // best-effort; do not fail flow\n }\n }\n\n private async persistRecoveredUserData(rec: PendingEmailRecovery, accountId: AccountId): Promise<void> {\n const { webAuthnManager } = this.context;\n\n const payload: StoreUserDataPayload = {\n nearAccountId: accountId,\n deviceNumber: rec.deviceNumber,\n clientNearPublicKey: rec.nearPublicKey,\n lastUpdated: Date.now(),\n passkeyCredential: {\n id: rec.credential.id,\n rawId: rec.credential.rawId,\n },\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: rec.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: rec.encryptedVrfKeypair.chacha20NonceB64u,\n },\n serverEncryptedVrfKeypair: rec.serverEncryptedVrfKeypair || undefined,\n };\n\n await webAuthnManager.storeUserData(payload);\n }\n\n private async persistAuthenticatorBestEffort(rec: PendingEmailRecovery, accountId: AccountId): Promise<void> {\n try {\n const { webAuthnManager } = this.context;\n const attestationB64u = rec.credential.response.attestationObject;\n const credentialPublicKey = await webAuthnManager.extractCosePublicKey(attestationB64u);\n await webAuthnManager.storeAuthenticator({\n nearAccountId: accountId,\n deviceNumber: rec.deviceNumber,\n credentialId: rec.credential.rawId,\n credentialPublicKey,\n transports: ['internal'],\n name: `Device ${rec.deviceNumber} Passkey for ${rec.accountId.split('.')[0]}`,\n registered: new Date().toISOString(),\n syncedAt: new Date().toISOString(),\n vrfPublicKey: rec.vrfPublicKey,\n });\n } catch {\n // best-effort; do not fail flow\n }\n }\n\n private async markCompleteAndClearPending(rec: PendingEmailRecovery): Promise<void> {\n rec.status = 'complete';\n await this.savePending(rec);\n await this.clearPending(rec.accountId, rec.nearPublicKey);\n }\n\n private async assertVrfActiveForAccount(accountId: AccountId, message: string): Promise<void> {\n const vrfStatus = await this.context.webAuthnManager.checkVrfStatus();\n const vrfActiveForAccount =\n vrfStatus.active\n && vrfStatus.nearAccountId\n && String(vrfStatus.nearAccountId) === String(accountId);\n if (!vrfActiveForAccount) {\n throw new Error(message);\n }\n }\n\n private async finalizeLocalLoginState(accountId: AccountId, deviceNumber: number): Promise<void> {\n const { webAuthnManager } = this.context;\n await webAuthnManager.setLastUser(accountId, deviceNumber);\n await webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n try { await getLoginSession(this.context, accountId); } catch { }\n }\n\n private async tryShamirUnlock(\n rec: PendingEmailRecovery,\n accountId: AccountId,\n deviceNumber: number\n ): Promise<boolean> {\n if (\n !rec.serverEncryptedVrfKeypair\n || !rec.serverEncryptedVrfKeypair.serverKeyId\n || !this.context.configs.vrfWorkerConfigs?.shamir3pass?.relayServerUrl\n ) {\n return false;\n }\n\n try {\n const { webAuthnManager } = this.context;\n const unlockResult = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId: accountId,\n kek_s_b64u: rec.serverEncryptedVrfKeypair.kek_s_b64u,\n ciphertextVrfB64u: rec.serverEncryptedVrfKeypair.ciphertextVrfB64u,\n serverKeyId: rec.serverEncryptedVrfKeypair.serverKeyId,\n });\n\n if (!unlockResult.success) {\n return false;\n }\n\n await this.assertVrfActiveForAccount(accountId, 'VRF session inactive after Shamir3Pass unlock');\n await this.finalizeLocalLoginState(accountId, deviceNumber);\n return true;\n } catch (err) {\n console.warn('[EmailRecoveryFlow] Shamir 3-pass unlock failed, falling back to TouchID', err);\n return false;\n }\n }\n\n private async tryTouchIdUnlock(\n rec: PendingEmailRecovery,\n accountId: AccountId,\n deviceNumber: number\n ): Promise<{ success: boolean; reason?: string }> {\n try {\n const { webAuthnManager } = this.context;\n const authChallenge = createRandomVRFChallenge() as VRFChallenge;\n\n const storedCredentialId = String(rec.credential?.rawId || rec.credential?.id || '').trim();\n const credentialIds = storedCredentialId ? [storedCredentialId] : [];\n const authenticators = credentialIds.length > 0\n ? []\n : await webAuthnManager.getAuthenticatorsByUser(accountId);\n const authCredential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId: accountId,\n challenge: authChallenge,\n credentialIds: credentialIds.length > 0 ? credentialIds : authenticators.map((a) => a.credentialId),\n });\n\n if (storedCredentialId && authCredential.rawId !== storedCredentialId) {\n return {\n success: false,\n reason: 'Wrong passkey selected during recovery auto-login; please use the newly recovered passkey.',\n };\n }\n\n const vrfUnlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: accountId,\n encryptedVrfKeypair: rec.encryptedVrfKeypair,\n credential: authCredential,\n });\n\n if (!vrfUnlockResult.success) {\n return { success: false, reason: vrfUnlockResult.error || 'VRF unlock failed during auto-login' };\n }\n\n await this.assertVrfActiveForAccount(accountId, 'VRF session inactive after TouchID unlock');\n await this.finalizeLocalLoginState(accountId, deviceNumber);\n return { success: true };\n } catch (err: any) {\n return { success: false, reason: err?.message || String(err) };\n }\n }\n\n private async handleAutoLoginFailure(reason: string, err?: unknown): Promise<AutoLoginResult> {\n console.warn('[EmailRecoveryFlow] Auto-login failed after recovery', err ?? reason);\n try {\n await this.context.webAuthnManager.clearVrfSession();\n } catch { }\n return { success: false, reason };\n }\n\n private async finalizeRegistration(rec: PendingEmailRecovery): Promise<void> {\n this.phase = EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION;\n this.emit({\n step: 5,\n phase: EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n status: EmailRecoveryStatus.PROGRESS,\n message: 'Finalizing email recovery registration...',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n\n try {\n const { nonceManager, accountId } = this.initializeNonceManager(rec);\n const signedTx = await this.signRegistrationTx(rec, accountId);\n const txHash = await this.broadcastRegistrationTxAndWaitFinal(rec, signedTx);\n\n if (txHash) {\n const storedUser = await this.persistRecoveredUserRecordBestEffort(rec, accountId);\n if (storedUser) {\n const syncedAuthenticators = await this.syncAuthenticatorsBestEffort(accountId);\n if (syncedAuthenticators) {\n await this.setLastUserBestEffort(accountId, rec.deviceNumber);\n }\n }\n }\n\n await this.updateNonceBestEffort(nonceManager, signedTx);\n await this.persistRecoveredUserData(rec, accountId);\n await this.persistAuthenticatorBestEffort(rec, accountId);\n\n this.emitAutoLoginEvent(EmailRecoveryStatus.PROGRESS, 'Attempting auto-login with recovered device...', {\n autoLogin: 'progress',\n });\n\n const autoLoginResult = await this.attemptAutoLogin(rec);\n if (autoLoginResult.success) {\n this.emitAutoLoginEvent(EmailRecoveryStatus.SUCCESS, `Welcome ${accountId}`, {\n autoLogin: 'success',\n });\n } else {\n this.emitAutoLoginEvent(EmailRecoveryStatus.ERROR, 'Auto-login failed; please log in manually on this device.', {\n error: autoLoginResult.reason,\n autoLogin: 'error',\n });\n }\n\n await this.markCompleteAndClearPending(rec);\n\n this.phase = EmailRecoveryPhase.STEP_6_COMPLETE;\n this.emit({\n step: 6,\n phase: EmailRecoveryPhase.STEP_6_COMPLETE,\n status: EmailRecoveryStatus.SUCCESS,\n message: 'Email recovery completed successfully',\n data: {\n accountId: rec.accountId,\n nearPublicKey: rec.nearPublicKey,\n },\n } as EmailRecoverySSEEvent & { data: Record<string, unknown> });\n } catch (e: any) {\n const err = this.emitError(5, e?.message || 'Email recovery finalization failed');\n await this.options?.afterCall?.(false);\n throw err;\n }\n }\n\n private async attemptAutoLogin(rec: PendingEmailRecovery): Promise<AutoLoginResult> {\n try {\n const accountId = toAccountId(rec.accountId);\n const deviceNumber = parseDeviceNumber(rec.deviceNumber, { min: 1 });\n if (deviceNumber === null) {\n return this.handleAutoLoginFailure(\n `Invalid deviceNumber for auto-login: ${String(rec.deviceNumber)}`\n );\n }\n\n const shamirUnlocked = await this.tryShamirUnlock(rec, accountId, deviceNumber);\n if (shamirUnlocked) {\n return { success: true, method: 'shamir' };\n }\n\n const touchIdResult = await this.tryTouchIdUnlock(rec, accountId, deviceNumber);\n if (touchIdResult.success) {\n return { success: true, method: 'touchid' };\n }\n\n return this.handleAutoLoginFailure(touchIdResult.reason || 'Auto-login failed');\n } catch (err: any) {\n return this.handleAutoLoginFailure(err?.message || String(err), err);\n }\n }\n}\n"],"mappings":"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"}
@@ -22,7 +22,7 @@ import { signNEP413Message } from "./signNEP413.js";
22
22
  import { signDelegateAction } from "./delegateAction.js";
23
23
  import { sendDelegateActionViaRelayer } from "./relay.js";
24
24
  import { isOffline, openOfflineExport } from "../OfflineExport/overlay.js";
25
- import { bytesToHex, getLocalRecoveryEmails, prepareRecoveryEmails } from "../EmailRecovery/index.js";
25
+ import { bytesToHex, getLocalRecoveryEmails, init_EmailRecovery, prepareRecoveryEmails } from "../EmailRecovery/index.js";
26
26
  import { ScanQRCodeFlow, ScanQRCodeFlowState } from "../../utils/qrScanner.js";
27
27
  import { emailRecovery_exports, init_emailRecovery } from "./emailRecovery.js";
28
28
 
@@ -34,6 +34,7 @@ init_IndexedDBManager();
34
34
  init_actions();
35
35
  init_errors();
36
36
  init_rpcCalls();
37
+ init_EmailRecovery();
37
38
  init_defaultConfigs();
38
39
  let warnedAboutSameOriginWallet = false;
39
40
  /**