@tatchi-xyz/sdk 0.31.0 → 0.32.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 (197) hide show
  1. package/README.md +2 -0
  2. package/dist/cjs/core/IndexedDBManager/passkeyClientDB.js +2 -2
  3. package/dist/cjs/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
  4. package/dist/cjs/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
  5. package/dist/cjs/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
  6. package/dist/cjs/core/TatchiPasskey/login.js +1 -1
  7. package/dist/cjs/core/TatchiPasskey/login.js.map +1 -1
  8. package/dist/cjs/core/TatchiPasskey/registration.js +107 -63
  9. package/dist/cjs/core/TatchiPasskey/registration.js.map +1 -1
  10. package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js +1 -1
  11. package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
  12. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
  13. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
  14. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
  15. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
  16. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
  17. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
  18. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
  19. package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
  20. package/dist/cjs/core/WebAuthnManager/index.js +3 -3
  21. package/dist/cjs/core/WebAuthnManager/index.js.map +1 -1
  22. package/dist/cjs/core/defaultConfigs.js +3 -1
  23. package/dist/cjs/core/defaultConfigs.js.map +1 -1
  24. package/dist/cjs/core/types/sdkSentEvents.js +3 -2
  25. package/dist/cjs/core/types/sdkSentEvents.js.map +1 -1
  26. package/dist/cjs/react/components/AccountMenuButton/TransactionSettingsSection.js +3 -3
  27. package/dist/cjs/react/components/AccountMenuButton/TransactionSettingsSection.js.map +1 -1
  28. package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-CRlobBrN.css → PasskeyAuthMenu-D2eRb2-S.css} +3 -1
  29. package/dist/cjs/react/components/PasskeyAuthMenu/PasskeyAuthMenu-D2eRb2-S.css.map +1 -0
  30. package/dist/cjs/react/components/PasskeyAuthMenu/preload.js +1 -1
  31. package/dist/cjs/react/components/PasskeyAuthMenu/preload.js.map +1 -1
  32. package/dist/cjs/react/components/PasskeyAuthMenu/shell.js +52 -13
  33. package/dist/cjs/react/components/PasskeyAuthMenu/shell.js.map +1 -1
  34. package/dist/cjs/react/components/PasskeyAuthMenu/skeleton.js +4 -2
  35. package/dist/cjs/react/components/PasskeyAuthMenu/skeleton.js.map +1 -1
  36. package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +5 -1
  37. package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
  38. package/dist/cjs/react/context/useTatchiWithSdkFlow.js +1 -1
  39. package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -1
  40. package/dist/cjs/react/index.js +1 -1
  41. package/dist/cjs/react/src/core/IndexedDBManager/passkeyClientDB.js +2 -2
  42. package/dist/cjs/react/src/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
  43. package/dist/cjs/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
  44. package/dist/cjs/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
  45. package/dist/cjs/react/src/core/TatchiPasskey/login.js +1 -1
  46. package/dist/cjs/react/src/core/TatchiPasskey/login.js.map +1 -1
  47. package/dist/cjs/react/src/core/TatchiPasskey/registration.js +107 -63
  48. package/dist/cjs/react/src/core/TatchiPasskey/registration.js.map +1 -1
  49. package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
  50. package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
  51. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
  52. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
  53. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
  54. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
  55. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
  56. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
  57. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
  58. package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
  59. package/dist/cjs/react/src/core/WebAuthnManager/index.js +3 -3
  60. package/dist/cjs/react/src/core/WebAuthnManager/index.js.map +1 -1
  61. package/dist/cjs/react/src/core/defaultConfigs.js +3 -1
  62. package/dist/cjs/react/src/core/defaultConfigs.js.map +1 -1
  63. package/dist/cjs/react/src/core/types/sdkSentEvents.js +3 -2
  64. package/dist/cjs/react/src/core/types/sdkSentEvents.js.map +1 -1
  65. package/dist/cjs/server/core/AuthService.js +49 -6
  66. package/dist/cjs/server/core/AuthService.js.map +1 -1
  67. package/dist/cjs/server/sdk/src/core/defaultConfigs.js.map +1 -1
  68. package/dist/esm/core/IndexedDBManager/passkeyClientDB.js +2 -2
  69. package/dist/esm/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
  70. package/dist/esm/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
  71. package/dist/esm/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
  72. package/dist/esm/core/TatchiPasskey/login.js +1 -1
  73. package/dist/esm/core/TatchiPasskey/login.js.map +1 -1
  74. package/dist/esm/core/TatchiPasskey/registration.js +107 -63
  75. package/dist/esm/core/TatchiPasskey/registration.js.map +1 -1
  76. package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js +1 -1
  77. package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
  78. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
  79. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
  80. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
  81. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
  82. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
  83. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
  84. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
  85. package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
  86. package/dist/esm/core/WebAuthnManager/index.js +3 -3
  87. package/dist/esm/core/WebAuthnManager/index.js.map +1 -1
  88. package/dist/esm/core/defaultConfigs.js +3 -1
  89. package/dist/esm/core/defaultConfigs.js.map +1 -1
  90. package/dist/esm/core/types/sdkSentEvents.js +3 -2
  91. package/dist/esm/core/types/sdkSentEvents.js.map +1 -1
  92. package/dist/esm/react/components/AccountMenuButton/TransactionSettingsSection.js +3 -3
  93. package/dist/esm/react/components/AccountMenuButton/TransactionSettingsSection.js.map +1 -1
  94. package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-D2VHZ04W.css → PasskeyAuthMenu-qTHAv58Z.css} +3 -1
  95. package/dist/esm/react/components/PasskeyAuthMenu/PasskeyAuthMenu-qTHAv58Z.css.map +1 -0
  96. package/dist/esm/react/components/PasskeyAuthMenu/preload.js +1 -1
  97. package/dist/esm/react/components/PasskeyAuthMenu/preload.js.map +1 -1
  98. package/dist/esm/react/components/PasskeyAuthMenu/shell.js +52 -13
  99. package/dist/esm/react/components/PasskeyAuthMenu/shell.js.map +1 -1
  100. package/dist/esm/react/components/PasskeyAuthMenu/skeleton.js +4 -2
  101. package/dist/esm/react/components/PasskeyAuthMenu/skeleton.js.map +1 -1
  102. package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +5 -1
  103. package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
  104. package/dist/esm/react/context/useTatchiWithSdkFlow.js +1 -1
  105. package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -1
  106. package/dist/esm/react/index.js +1 -1
  107. package/dist/esm/react/src/core/IndexedDBManager/passkeyClientDB.js +2 -2
  108. package/dist/esm/react/src/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
  109. package/dist/esm/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
  110. package/dist/esm/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
  111. package/dist/esm/react/src/core/TatchiPasskey/login.js +1 -1
  112. package/dist/esm/react/src/core/TatchiPasskey/login.js.map +1 -1
  113. package/dist/esm/react/src/core/TatchiPasskey/registration.js +107 -63
  114. package/dist/esm/react/src/core/TatchiPasskey/registration.js.map +1 -1
  115. package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
  116. package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
  117. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
  118. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
  119. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
  120. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
  121. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
  122. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
  123. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
  124. package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
  125. package/dist/esm/react/src/core/WebAuthnManager/index.js +3 -3
  126. package/dist/esm/react/src/core/WebAuthnManager/index.js.map +1 -1
  127. package/dist/esm/react/src/core/defaultConfigs.js +3 -1
  128. package/dist/esm/react/src/core/defaultConfigs.js.map +1 -1
  129. package/dist/esm/react/src/core/types/sdkSentEvents.js +3 -2
  130. package/dist/esm/react/src/core/types/sdkSentEvents.js.map +1 -1
  131. package/dist/esm/react/styles/styles.css +2 -0
  132. package/dist/esm/sdk/{EmailRecovery-Dl8b4ONg.js → EmailRecovery-Y7rurd4B.js} +3 -3
  133. package/dist/esm/sdk/{EmailRecovery-v9oNO2Tc.js → EmailRecovery-lsjLWApQ.js} +1 -1
  134. package/dist/esm/sdk/{IndexedDBManager-B1cUvdyY.js → IndexedDBManager-CmdN7smS.js} +3 -3
  135. package/dist/esm/sdk/{createAdapters-Dv7ZJPf1.js → createAdapters-4c8mBiD5.js} +2 -11
  136. package/dist/esm/sdk/{createAdapters-Dv7ZJPf1.js.map → createAdapters-4c8mBiD5.js.map} +1 -1
  137. package/dist/esm/sdk/{createAdapters-1Hmc1vVC.js → createAdapters-DF32SIZa.js} +1 -10
  138. package/dist/esm/sdk/{defaultConfigs-BmCU1_qI.js → defaultConfigs-BQqiXif-.js} +3 -1
  139. package/dist/esm/sdk/{delegateAction-DdkvFFKA.js → delegateAction-Bq5zkOvn.js} +1 -1
  140. package/dist/esm/sdk/{emailRecovery-4J-g9tlY.js → emailRecovery-B1hbE_sM.js} +6 -6
  141. package/dist/esm/sdk/{getDeviceNumber-f8bfPB9U.js → getDeviceNumber-WiNzKx1x.js} +4 -2
  142. package/dist/esm/sdk/{getDeviceNumber-f8bfPB9U.js.map → getDeviceNumber-WiNzKx1x.js.map} +1 -1
  143. package/dist/esm/sdk/{linkDevice-C98klpcE.js → linkDevice-CRPf5aW2.js} +5 -5
  144. package/dist/esm/sdk/{localOnly-40zxrBMm.js → localOnly-COpDBMkm.js} +2 -2
  145. package/dist/esm/sdk/{localOnly-40zxrBMm.js.map → localOnly-COpDBMkm.js.map} +1 -1
  146. package/dist/esm/sdk/{localOnly-BZPBj14l.js → localOnly-DQQuqgjJ.js} +1 -1
  147. package/dist/esm/sdk/{login-DnROv3eA.js → login-DUIWZHp_.js} +4 -4
  148. package/dist/esm/sdk/offline-export-app.js +32 -21
  149. package/dist/esm/sdk/offline-export-app.js.map +1 -1
  150. package/dist/esm/sdk/{registration-BP9M3tE1.js → registration-BR2G9tz_.js} +59 -68
  151. package/dist/esm/sdk/{registration-MrAOC8Ub.js → registration-R70lvG_o.js} +60 -69
  152. package/dist/esm/sdk/registration-R70lvG_o.js.map +1 -0
  153. package/dist/esm/sdk/{relay-Dq9D7fhG.js → relay-BCEyWFew.js} +1 -1
  154. package/dist/esm/sdk/{router-BEGGuWaB.js → router-Cj2WexK-.js} +3 -3
  155. package/dist/esm/sdk/{rpcCalls-CMzj_Va_.js → rpcCalls-C1sp-Epo.js} +3 -3
  156. package/dist/esm/sdk/{rpcCalls-B44MZora.js → rpcCalls-VL4loDKP.js} +2 -2
  157. package/dist/esm/sdk/{scanDevice-Cp-r-Z2T.js → scanDevice-C0HcnZym.js} +5 -5
  158. package/dist/esm/sdk/{sdkSentEvents-CzAZBFjP.js → sdkSentEvents-BfkcI7EN.js} +3 -2
  159. package/dist/esm/sdk/{signNEP413-DsyWH_Jo.js → signNEP413-lj0swHsD.js} +1 -1
  160. package/dist/esm/sdk/{syncAccount-CqWCmBVb.js → syncAccount-DnQ9AstS.js} +5 -5
  161. package/dist/esm/sdk/{syncAccount-Dt5jJbEB.js → syncAccount-xh81Vppo.js} +3 -3
  162. package/dist/esm/sdk/{transactions-DAZrPW-6.js → transactions-Cg1TIUyK.js} +76 -77
  163. package/dist/esm/sdk/{transactions-CrjP8yPD.js → transactions-CxsklyCK.js} +77 -78
  164. package/dist/esm/sdk/transactions-CxsklyCK.js.map +1 -0
  165. package/dist/esm/sdk/wallet-iframe-host.js +160 -105
  166. package/dist/esm/server/core/AuthService.js +49 -6
  167. package/dist/esm/server/core/AuthService.js.map +1 -1
  168. package/dist/esm/server/sdk/src/core/defaultConfigs.js.map +1 -1
  169. package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
  170. package/dist/types/src/__tests__/setup/bootstrap.d.ts.map +1 -1
  171. package/dist/types/src/core/IndexedDBManager/passkeyClientDB.d.ts +1 -1
  172. package/dist/types/src/core/IndexedDBManager/passkeyClientDB.d.ts.map +1 -1
  173. package/dist/types/src/core/TatchiPasskey/faucets/createAccountRelayServer.d.ts +6 -6
  174. package/dist/types/src/core/TatchiPasskey/faucets/createAccountRelayServer.d.ts.map +1 -1
  175. package/dist/types/src/core/TatchiPasskey/registration.d.ts.map +1 -1
  176. package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.d.ts +0 -5
  177. package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.d.ts.map +1 -1
  178. package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.d.ts.map +1 -1
  179. package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.d.ts.map +1 -1
  180. package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.d.ts.map +1 -1
  181. package/dist/types/src/core/WebAuthnManager/index.d.ts +1 -1
  182. package/dist/types/src/core/WebAuthnManager/index.d.ts.map +1 -1
  183. package/dist/types/src/core/defaultConfigs.d.ts.map +1 -1
  184. package/dist/types/src/core/types/sdkSentEvents.d.ts +18 -7
  185. package/dist/types/src/core/types/sdkSentEvents.d.ts.map +1 -1
  186. package/dist/types/src/react/components/PasskeyAuthMenu/preload.d.ts.map +1 -1
  187. package/dist/types/src/react/components/PasskeyAuthMenu/shell.d.ts.map +1 -1
  188. package/dist/types/src/react/components/PasskeyAuthMenu/skeleton.d.ts +1 -1
  189. package/dist/types/src/react/components/PasskeyAuthMenu/skeleton.d.ts.map +1 -1
  190. package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
  191. package/dist/types/src/server/core/AuthService.d.ts.map +1 -1
  192. package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
  193. package/package.json +4 -4
  194. package/dist/cjs/react/components/PasskeyAuthMenu/PasskeyAuthMenu-CRlobBrN.css.map +0 -1
  195. package/dist/esm/react/components/PasskeyAuthMenu/PasskeyAuthMenu-D2VHZ04W.css.map +0 -1
  196. package/dist/esm/sdk/registration-MrAOC8Ub.js.map +0 -1
  197. package/dist/esm/sdk/transactions-CrjP8yPD.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"EmailRecoverySlide.js","names":["EmailRecoverySlide: React.FC<EmailRecoverySlideProps>","info: EmailRecoveryAccountInfo | null","err: unknown","summaryLine: React.ReactNode"],"sources":["../../../../../../src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.tsx"],"sourcesContent":["import React from 'react';\n\nimport {\n EmailRecoveryPhase,\n EmailRecoveryStatus,\n type EmailRecoverySSEEvent,\n} from '@/core/types/sdkSentEvents';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport { EmailRecoveryErrorCode } from '@/core/types/emailRecovery';\nimport type { EmailRecoveryFlowOptions } 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 EmailRecoveryAccountInfo = {\n emailsCount: number;\n};\n\ntype MailtoUiState = 'ready' | 'opening';\n\ntype RecoveryEmailRecord = Awaited<ReturnType<TatchiPasskey['getRecoveryEmails']>>[number];\n\ntype ExplorerToast = { url: string; accountId?: string; transactionHash?: string };\n\nconst DEFAULT_NEAR_EXPLORER_URL = 'https://testnet.nearblocks.io';\nconst ACCOUNT_INFO_DEBOUNCE_MS = 350;\nconst MAILTO_REENABLE_MS = 2_000;\n\nfunction getExplorerBaseUrl(tatchiPasskey: TatchiPasskey): string {\n return String(tatchiPasskey.configs?.nearExplorerUrl || DEFAULT_NEAR_EXPLORER_URL).replace(/\\/$/, '');\n}\n\nfunction getExplorerAccountUrl(args: { base: string; accountId: string }): string {\n const { base, accountId } = args;\n return base.includes('nearblocks.io') ? `${base}/address/${accountId}` : `${base}/accounts/${accountId}`;\n}\n\nfunction getExplorerTxUrl(args: { base: string; txHash: string }): string {\n const { base, txHash } = args;\n return base.includes('nearblocks.io') ? `${base}/txns/${txHash}` : `${base}/transactions/${txHash}`;\n}\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 normalizedFallback = fallback.trim().toLowerCase();\n if (normalizedFallback.includes('recovery email is required')) {\n return {\n message:\n fallback ||\n 'Recovery email is required for email-based account recovery. Make sure you send the email from your configured recovery email address.',\n canRestart: true,\n };\n }\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\nfunction getEmailRecoveryErrorTxHash(err: unknown): string | null {\n const carrier = (err as { context?: unknown; details?: unknown } | null);\n const ctx = carrier?.context && typeof carrier.context === 'object' ? carrier.context : null;\n const details = carrier?.details && typeof carrier.details === 'object' ? carrier.details : null;\n const source = ctx ?? details;\n if (!source) return null;\n const txHash = (source as { transactionHash?: unknown }).transactionHash;\n return typeof txHash === 'string' && txHash.trim().length > 0 ? txHash.trim() : null;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n if (!value || typeof value !== 'object') return null;\n return value as Record<string, unknown>;\n}\n\nfunction extractTxHashFromEmailRecoveryEvent(ev: EmailRecoverySSEEvent): string | null {\n const data = 'data' in ev ? asRecord(ev.data) : null;\n const rawTxHash = data?.['transactionHash'] ?? data?.['transaction_hash'];\n const txHash = typeof rawTxHash === 'string' ? rawTxHash.trim() : '';\n return txHash || null;\n}\n\nfunction extractElapsedMsFromEmailRecoveryEvent(ev: EmailRecoverySSEEvent): number | null | undefined {\n const data = 'data' in ev ? asRecord(ev.data) : null;\n const elapsedRaw = data?.['elapsedMs'] ?? data?.['elapsed_ms'];\n if (elapsedRaw == null) return null;\n const elapsed = Number(elapsedRaw);\n return Number.isNaN(elapsed) ? undefined : elapsed;\n}\n\nfunction deriveEmailsFromRecoveryRecords(records: RecoveryEmailRecord[]): string[] {\n if (records.length === 0) return [];\n const emails = records\n .map((r) => r.email.trim().toLowerCase())\n .filter((e) => e.length > 0 && e.includes('@'));\n return Array.from(new Set(emails));\n}\n\nfunction EmailRecoveryHeader() {\n return (\n <>\n <div className=\"w3a-email-recovery-title\">Recover Account with Email</div>\n <div className=\"w3a-email-recovery-help\">\n Send a special email to recover your account.\n This email must be sent from the designated email recovery address.\n </div>\n </>\n );\n}\n\nfunction AccountIdInputRow(props: {\n value: string;\n onChange: (value: string) => void;\n disabled: boolean;\n}) {\n const { value, onChange, disabled } = props;\n return (\n <div>\n <div className=\"w3a-input-pill w3a-email-recovery-input-pill\">\n <div className=\"w3a-input-wrap\">\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(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={disabled}\n />\n </div>\n </div>\n </div>\n );\n}\n\nfunction RecoveryEmailsSummary(props: {\n summaryLine: React.ReactNode;\n accountInfoError: string | null;\n localRecoveryEmails: string[];\n showFromWarning: boolean;\n}) {\n const { summaryLine, accountInfoError, localRecoveryEmails, showFromWarning } = props;\n return (\n <div className=\"w3a-email-recovery-summary\" aria-live=\"polite\">\n <div>{summaryLine}</div>\n {!!accountInfoError && (\n <div className=\"w3a-email-recovery-warning\">{accountInfoError}</div>\n )}\n {localRecoveryEmails.length > 0 && (\n <div className=\"w3a-email-recovery-saved-emails\" role=\"list\" aria-label=\"Recovery emails\">\n {localRecoveryEmails.map((email) => (\n <span key={email} className=\"w3a-email-recovery-email-chip w3a-email-recovery-email-chip-static\" role=\"listitem\">\n {email}\n </span>\n ))}\n </div>\n )}\n {showFromWarning && (\n <div className=\"w3a-email-recovery-from-warning\">\n {localRecoveryEmails.length === 1\n ? `Check that you are sending the recovery email from ${localRecoveryEmails[0]}.`\n : 'Check that you are sending the recovery email from your designated recovery email.'}\n </div>\n )}\n </div>\n );\n}\n\nfunction EmailRecoveryActions(props: {\n isBusy: boolean;\n pendingMailtoUrl: string | null;\n mailtoUiState: MailtoUiState;\n startDisabled: boolean;\n accountInfoLoading: boolean;\n noRecoveryEmailsConfigured: boolean;\n showRestart: boolean;\n onStart: () => void;\n onOpenDraft: (mailtoUrl: string) => void;\n onRestart: () => void;\n}) {\n const {\n isBusy,\n pendingMailtoUrl,\n mailtoUiState,\n startDisabled,\n accountInfoLoading,\n noRecoveryEmailsConfigured,\n showRestart,\n onStart,\n onOpenDraft,\n onRestart,\n } = props;\n\n return (\n <div className=\"w3a-email-recovery-actions\">\n {(!pendingMailtoUrl || !isBusy) && (\n <button\n onClick={onStart}\n className=\"w3a-link-device-btn w3a-link-device-btn-primary\"\n disabled={startDisabled}\n >\n {accountInfoLoading\n ? 'Checking recovery emails…'\n : noRecoveryEmailsConfigured\n ? 'No recovery emails configured'\n : (isBusy ? 'Working…' : 'Start Email Recovery')}\n </button>\n )}\n\n {pendingMailtoUrl && (\n <button\n type=\"button\"\n onClick={() => onOpenDraft(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 {showRestart && (\n <button\n type=\"button\"\n onClick={onRestart}\n className=\"w3a-link-device-btn\"\n disabled={isBusy}\n >\n Restart email recovery\n </button>\n )}\n </div>\n );\n}\n\nfunction EmailRecoveryStatusPanel(props: {\n errorText: string | null;\n statusText: string | null;\n pollingElapsedMs: number | null;\n explorerToast: ExplorerToast | null;\n}) {\n const { errorText, statusText, pollingElapsedMs, explorerToast } = props;\n if (!errorText && !statusText && !explorerToast) return null;\n\n return (\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}\n\nexport const EmailRecoverySlide: React.FC<EmailRecoverySlideProps> = ({\n tatchiPasskey,\n accountId,\n refreshLoginState,\n emailRecoveryOptions\n}) => {\n\n const mountedRef = React.useRef(true);\n const mailtoAttemptTimerRef = React.useRef<number | null>(null);\n const cancelRequestedRef = React.useRef(false);\n\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 type NoInferType<T> = [T][T extends any ? 0 : never];\n const safeSet = React.useCallback(<T,>(\n setter: React.Dispatch<React.SetStateAction<T>>,\n value: React.SetStateAction<NoInferType<T>>,\n ) => {\n if (!mountedRef.current) return;\n setter(value);\n }, []);\n\n const [isBusy, setIsBusy] = React.useState(false);\n const [accountIdInput, setAccountIdInput] = 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<ExplorerToast | null>(null);\n\n const lastPrefilledAccountIdRef = 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 onEvent = React.useCallback(\n (ev: EmailRecoverySSEEvent) => {\n if (cancelRequestedRef.current) return;\n safeSet(setStatusText, ev?.message || null);\n emailRecoveryOptions?.onEvent?.(ev);\n\n const txHash = extractTxHashFromEmailRecoveryEvent(ev);\n if (txHash) {\n const base = getExplorerBaseUrl(tatchiPasskey);\n safeSet(setExplorerToast, { url: getExplorerTxUrl({ base, txHash }), transactionHash: txHash });\n }\n const elapsed = extractElapsedMsFromEmailRecoveryEvent(ev);\n if (elapsed === null) safeSet(setPollingElapsedMs, null);\n else if (elapsed != null) safeSet(setPollingElapsedMs, elapsed);\n\n if (ev.phase === EmailRecoveryPhase.ERROR || ev.status === EmailRecoveryStatus.ERROR) {\n const raw = 'error' in ev ? ev.error : ev.message;\n safeSet(setErrorText, raw || 'Email recovery failed');\n safeSet(setCanRestart, false);\n }\n },\n [emailRecoveryOptions, safeSet, tatchiPasskey],\n );\n\n const showExplorerToast = React.useCallback(\n (rawAccountId: string) => {\n const normalized = (rawAccountId || '').trim();\n if (!normalized) return;\n const base = getExplorerBaseUrl(tatchiPasskey);\n safeSet(setExplorerToast, { url: getExplorerAccountUrl({ base, accountId: normalized }), accountId: normalized });\n },\n [safeSet, tatchiPasskey],\n );\n\n const showExplorerTxToast = React.useCallback(\n (txHash: string) => {\n const normalized = (txHash || '').trim();\n if (!normalized) return;\n const base = getExplorerBaseUrl(tatchiPasskey);\n safeSet(setExplorerToast, { url: getExplorerTxUrl({ base, txHash: normalized }), transactionHash: normalized });\n },\n [safeSet, 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 safeSet(setMailtoUiState, '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 safeSet(setMailtoUiState, prev => (prev === 'opening' ? 'ready' : prev));\n mailtoAttemptTimerRef.current = null;\n }, MAILTO_REENABLE_MS);\n\n launchMailto(url);\n },\n [launchMailto, safeSet],\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 safeSet(setMailtoUiState, '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, safeSet]);\n\n React.useEffect(() => {\n const normalized = (accountIdInput || '').trim();\n if (!normalized) {\n setAccountInfo(null);\n setAccountInfoError(null);\n setAccountInfoLoading(false);\n safeSet(setLocalRecoveryEmails, []);\n return;\n }\n\n let cancelled = false;\n // Show loading state immediately (don't wait for debounce).\n safeSet(setAccountInfoLoading, true);\n safeSet(setAccountInfoError, null);\n const handle = window.setTimeout(() => {\n void (async () => {\n try {\n const records = await tatchiPasskey.getRecoveryEmails(normalized);\n const resolvedEmails = deriveEmailsFromRecoveryRecords(records);\n\n if (!cancelled) {\n safeSet(setLocalRecoveryEmails, resolvedEmails);\n }\n\n const info: EmailRecoveryAccountInfo | null = records\n ? { emailsCount: Array.isArray(records) ? records.length : 0 }\n : null;\n if (cancelled) return;\n safeSet(setAccountInfo, info);\n } catch (err: unknown) {\n if (cancelled) return;\n safeSet(setAccountInfo, null);\n const msg = err instanceof Error ? err.message : '';\n safeSet(setAccountInfoError, msg || 'Failed to load email recovery settings for this account');\n safeSet(setLocalRecoveryEmails, []);\n } finally {\n if (!cancelled) safeSet(setAccountInfoLoading, false);\n }\n })();\n }, ACCOUNT_INFO_DEBOUNCE_MS);\n\n return () => {\n cancelled = true;\n window.clearTimeout(handle);\n };\n }, [accountIdInput, safeSet, tatchiPasskey]);\n\n const handleStart = React.useCallback(async () => {\n const normalizedAccountId = (accountIdInput || '').trim();\n if (!normalizedAccountId) {\n safeSet(setErrorText, 'Enter an account ID.');\n return;\n }\n\n if (accountInfoLoading) {\n safeSet(setErrorText, 'Checking recovery email settings…');\n return;\n }\n\n if (accountInfoError) {\n safeSet(setErrorText, accountInfoError);\n return;\n }\n\n if (!accountInfo) {\n safeSet(setErrorText, 'Failed to load email recovery settings for this account.');\n return;\n }\n\n if (accountInfo.emailsCount === 0) {\n safeSet(setErrorText, 'No recovery emails are configured for this account.');\n return;\n }\n\n safeSet(setIsBusy, true);\n cancelRequestedRef.current = false;\n safeSet(setErrorText, null);\n safeSet(setCanRestart, false);\n safeSet(setStatusText, null);\n safeSet(setPollingElapsedMs, null);\n safeSet(setPendingMailtoUrl, null);\n safeSet(setPendingNearPublicKey, null);\n safeSet(setMailtoUiState, 'ready');\n\n let didForwardError = false;\n try {\n const result = await tatchiPasskey.startEmailRecovery({\n accountId: normalizedAccountId,\n options: {\n onEvent,\n onError: (err: Error) => {\n if (cancelRequestedRef.current) return;\n safeSet(setErrorText, err?.message || 'Failed to start email recovery');\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n } satisfies EmailRecoveryFlowOptions,\n });\n\n safeSet(setPendingMailtoUrl, result.mailtoUrl);\n safeSet(setPendingNearPublicKey, result.nearPublicKey);\n safeSet(\n setStatusText,\n 'Recovery email draft ready. If it didn’t open automatically, click “Open recovery email draft”. Waiting for verification…'\n );\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 safeSet(setErrorText, uiError.message || 'Failed to finalize email recovery');\n safeSet(setCanRestart, uiError.canRestart);\n const txHash = getEmailRecoveryErrorTxHash(err);\n if (txHash) showExplorerTxToast(txHash);\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n } satisfies EmailRecoveryFlowOptions,\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 safeSet(setStatusText, 'Email recovery completed. Logging you in…');\n loginOk = await tatchiPasskey\n .loginAndCreateSession(normalizedAccountId)\n .then(() => true)\n .catch(() => false);\n }\n\n if (refreshLoginState) {\n await refreshLoginState(normalizedAccountId).catch(() => {});\n }\n\n safeSet(\n setStatusText,\n loginOk ? 'Email recovery completed on this device.' : 'Email recovery completed. Please log in on this device.'\n );\n safeSet(setPendingMailtoUrl, null);\n safeSet(setMailtoUiState, 'ready');\n safeSet(setPollingElapsedMs, null);\n } catch (err: unknown) {\n if (cancelRequestedRef.current) {\n safeSet(setErrorText, 'Email recovery cancelled. Please try again.');\n safeSet(setStatusText, null);\n safeSet(setPollingElapsedMs, null);\n safeSet(setPendingMailtoUrl, null);\n safeSet(setPendingNearPublicKey, null);\n safeSet(setMailtoUiState, 'ready');\n safeSet(setCanRestart, false);\n return;\n }\n const uiError = getEmailRecoveryUiError(err);\n safeSet(setErrorText, uiError.message || 'Failed to start email recovery');\n safeSet(setCanRestart, uiError.canRestart);\n const txHash = getEmailRecoveryErrorTxHash(err);\n if (txHash) showExplorerTxToast(txHash);\n if (!didForwardError && err instanceof Error) {\n emailRecoveryOptions?.onError?.(err);\n }\n } finally {\n safeSet(setIsBusy, false);\n }\n }, [\n accountIdInput,\n accountInfo,\n accountInfoError,\n accountInfoLoading,\n attemptOpenMailtoAuto,\n emailRecoveryOptions,\n onEvent,\n refreshLoginState,\n safeSet,\n showExplorerToast,\n showExplorerTxToast,\n tatchiPasskey,\n ]);\n\n const handleRestart = React.useCallback(async () => {\n const normalizedAccountId = (accountIdInput || '').trim();\n if (!normalizedAccountId) return;\n\n safeSet(setIsBusy, true);\n try {\n cancelRequestedRef.current = true;\n await tatchiPasskey\n .cancelEmailRecovery({\n accountId: normalizedAccountId,\n nearPublicKey: pendingNearPublicKey || undefined,\n })\n .catch(() => {});\n safeSet(setErrorText, null);\n safeSet(setStatusText, null);\n safeSet(setPollingElapsedMs, null);\n safeSet(setPendingMailtoUrl, null);\n safeSet(setPendingNearPublicKey, null);\n safeSet(setMailtoUiState, 'ready');\n safeSet(setCanRestart, false);\n } finally {\n cancelRequestedRef.current = false;\n safeSet(setIsBusy, false);\n }\n }, [accountIdInput, pendingNearPublicKey, safeSet, tatchiPasskey]);\n\n const summaryLine: React.ReactNode = accountInfoLoading\n ? (\n <>\n Checking if account has recovery emails configured\n <span className=\"w3a-ellipsis\" aria-hidden=\"true\">\n <span className=\"w3a-ellipsis-dot\">.</span>\n <span className=\"w3a-ellipsis-dot\">.</span>\n <span className=\"w3a-ellipsis-dot\">.</span>\n </span>\n </>\n )\n : accountInfo && !accountInfoError\n ? `Recovery emails configured: ${accountInfo.emailsCount}`\n : '\\u00A0';\n\n const noRecoveryEmailsConfigured =\n !accountInfoLoading && !accountInfoError && !!accountInfo && accountInfo.emailsCount === 0;\n const startDisabled = isBusy || accountInfoLoading || !!accountInfoError || !accountInfo || noRecoveryEmailsConfigured;\n const showFromWarning = !!accountIdInput.trim() && !noRecoveryEmailsConfigured;\n const showRestart = !!errorText && canRestart;\n\n return (\n <div className=\"w3a-email-recovery-slide\">\n <EmailRecoveryHeader />\n <AccountIdInputRow value={accountIdInput} onChange={setAccountIdInput} disabled={isBusy} />\n <RecoveryEmailsSummary\n summaryLine={summaryLine}\n accountInfoError={accountInfoError}\n localRecoveryEmails={localRecoveryEmails}\n showFromWarning={showFromWarning}\n />\n <EmailRecoveryActions\n isBusy={isBusy}\n pendingMailtoUrl={pendingMailtoUrl}\n mailtoUiState={mailtoUiState}\n startDisabled={startDisabled}\n accountInfoLoading={accountInfoLoading}\n noRecoveryEmailsConfigured={noRecoveryEmailsConfigured}\n showRestart={showRestart}\n onStart={handleStart}\n onOpenDraft={attemptOpenMailtoFromUserGesture}\n onRestart={handleRestart}\n />\n <EmailRecoveryStatusPanel\n errorText={errorText}\n statusText={statusText}\n pollingElapsedMs={pollingElapsedMs}\n explorerToast={explorerToast}\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>","info: EmailRecoveryAccountInfo | null","err: unknown","summaryLine: React.ReactNode"],"sources":["../../../../../../src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.tsx"],"sourcesContent":["import React from 'react';\n\nimport {\n EmailRecoveryPhase,\n EmailRecoveryStatus,\n type EmailRecoverySSEEvent,\n} from '@/core/types/sdkSentEvents';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport { EmailRecoveryErrorCode } from '@/core/types/emailRecovery';\nimport type { EmailRecoveryFlowOptions } 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 EmailRecoveryAccountInfo = {\n emailsCount: number;\n};\n\ntype MailtoUiState = 'ready' | 'opening';\n\ntype RecoveryEmailRecord = Awaited<ReturnType<TatchiPasskey['getRecoveryEmails']>>[number];\n\ntype ExplorerToast = { url: string; accountId?: string; transactionHash?: string };\n\nconst DEFAULT_NEAR_EXPLORER_URL = 'https://testnet.nearblocks.io';\nconst ACCOUNT_INFO_DEBOUNCE_MS = 350;\nconst MAILTO_REENABLE_MS = 2_000;\n\nfunction getExplorerBaseUrl(tatchiPasskey: TatchiPasskey): string {\n return String(tatchiPasskey.configs?.nearExplorerUrl || DEFAULT_NEAR_EXPLORER_URL).replace(/\\/$/, '');\n}\n\nfunction getExplorerAccountUrl(args: { base: string; accountId: string }): string {\n const { base, accountId } = args;\n return base.includes('nearblocks.io') ? `${base}/address/${accountId}` : `${base}/accounts/${accountId}`;\n}\n\nfunction getExplorerTxUrl(args: { base: string; txHash: string }): string {\n const { base, txHash } = args;\n return base.includes('nearblocks.io') ? `${base}/txns/${txHash}` : `${base}/transactions/${txHash}`;\n}\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 normalizedFallback = fallback.trim().toLowerCase();\n if (normalizedFallback.includes('recovery email is required')) {\n return {\n message:\n fallback ||\n 'Recovery email is required for email-based account recovery. Make sure you send the email from your configured recovery email address.',\n canRestart: true,\n };\n }\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\nfunction getEmailRecoveryErrorTxHash(err: unknown): string | null {\n const carrier = (err as { context?: unknown; details?: unknown } | null);\n const ctx = carrier?.context && typeof carrier.context === 'object' ? carrier.context : null;\n const details = carrier?.details && typeof carrier.details === 'object' ? carrier.details : null;\n const source = ctx ?? details;\n if (!source) return null;\n const txHash = (source as { transactionHash?: unknown }).transactionHash;\n return typeof txHash === 'string' && txHash.trim().length > 0 ? txHash.trim() : null;\n}\n\nfunction asRecord(value: unknown): Record<string, unknown> | null {\n if (!value || typeof value !== 'object') return null;\n return value as Record<string, unknown>;\n}\n\nfunction extractTxHashFromEmailRecoveryEvent(ev: EmailRecoverySSEEvent): string | null {\n const data = 'data' in ev ? asRecord(ev.data) : null;\n const rawTxHash = data?.['transactionHash'] ?? data?.['transaction_hash'];\n const txHash = typeof rawTxHash === 'string' ? rawTxHash.trim() : '';\n return txHash || null;\n}\n\nfunction extractElapsedMsFromEmailRecoveryEvent(ev: EmailRecoverySSEEvent): number | null | undefined {\n const data = 'data' in ev ? asRecord(ev.data) : null;\n const elapsedRaw = data?.['elapsedMs'] ?? data?.['elapsed_ms'];\n if (elapsedRaw == null) return null;\n const elapsed = Number(elapsedRaw);\n return Number.isNaN(elapsed) ? undefined : elapsed;\n}\n\nfunction deriveEmailsFromRecoveryRecords(records: RecoveryEmailRecord[]): string[] {\n if (records.length === 0) return [];\n const emails = records\n .map((r) => r.email.trim().toLowerCase())\n .filter((e) => e.length > 0 && e.includes('@'));\n return Array.from(new Set(emails));\n}\n\nfunction EmailRecoveryHeader() {\n return (\n <>\n <div className=\"w3a-email-recovery-title\">Recover Account with Email</div>\n <div className=\"w3a-email-recovery-help\">\n Send a special email to recover your account.\n This email must be sent from the designated email recovery address.\n </div>\n </>\n );\n}\n\nfunction AccountIdInputRow(props: {\n value: string;\n onChange: (value: string) => void;\n disabled: boolean;\n}) {\n const { value, onChange, disabled } = props;\n return (\n <div>\n <div className=\"w3a-input-pill w3a-email-recovery-input-pill\">\n <div className=\"w3a-input-wrap\">\n <input\n type=\"text\"\n value={value}\n onChange={(e) => onChange(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={disabled}\n />\n </div>\n </div>\n </div>\n );\n}\n\nfunction RecoveryEmailsSummary(props: {\n summaryLine: React.ReactNode;\n accountInfoError: string | null;\n localRecoveryEmails: string[];\n showFromWarning: boolean;\n}) {\n const { summaryLine, accountInfoError, localRecoveryEmails, showFromWarning } = props;\n return (\n <div className=\"w3a-email-recovery-summary\" aria-live=\"polite\">\n <div>{summaryLine}</div>\n {!!accountInfoError && (\n <div className=\"w3a-email-recovery-warning\">{accountInfoError}</div>\n )}\n {localRecoveryEmails.length > 0 && (\n <div className=\"w3a-email-recovery-saved-emails\" role=\"list\" aria-label=\"Recovery emails\">\n {localRecoveryEmails.map((email) => (\n <span key={email} className=\"w3a-email-recovery-email-chip w3a-email-recovery-email-chip-static\" role=\"listitem\">\n {email}\n </span>\n ))}\n </div>\n )}\n {showFromWarning && (\n <div className=\"w3a-email-recovery-from-warning\">\n {localRecoveryEmails.length === 1\n ? `Check that you are sending the recovery email from ${localRecoveryEmails[0]}.`\n : 'Check that you are sending the recovery email from your designated recovery email.'}\n </div>\n )}\n </div>\n );\n}\n\nfunction EmailRecoveryActions(props: {\n isBusy: boolean;\n pendingMailtoUrl: string | null;\n mailtoUiState: MailtoUiState;\n startDisabled: boolean;\n accountInfoLoading: boolean;\n noRecoveryEmailsConfigured: boolean;\n showRestart: boolean;\n onStart: () => void;\n onOpenDraft: (mailtoUrl: string) => void;\n onRestart: () => void;\n}) {\n const {\n isBusy,\n pendingMailtoUrl,\n mailtoUiState,\n startDisabled,\n accountInfoLoading,\n noRecoveryEmailsConfigured,\n showRestart,\n onStart,\n onOpenDraft,\n onRestart,\n } = props;\n\n return (\n <div className=\"w3a-email-recovery-actions\">\n {(!pendingMailtoUrl || !isBusy) && (\n <button\n onClick={onStart}\n className=\"w3a-link-device-btn w3a-link-device-btn-primary\"\n disabled={startDisabled}\n >\n {accountInfoLoading\n ? 'Checking recovery emails…'\n : noRecoveryEmailsConfigured\n ? 'No recovery emails configured'\n : (isBusy ? 'Working…' : 'Start Email Recovery')}\n </button>\n )}\n\n {pendingMailtoUrl && (\n <button\n type=\"button\"\n onClick={() => onOpenDraft(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 {showRestart && (\n <button\n type=\"button\"\n onClick={onRestart}\n className=\"w3a-link-device-btn\"\n disabled={isBusy}\n >\n Restart email recovery\n </button>\n )}\n </div>\n );\n}\n\nfunction EmailRecoveryStatusPanel(props: {\n errorText: string | null;\n statusText: string | null;\n pollingElapsedMs: number | null;\n explorerToast: ExplorerToast | null;\n}) {\n const { errorText, statusText, pollingElapsedMs, explorerToast } = props;\n if (!errorText && !statusText && !explorerToast) return null;\n\n return (\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}\n\nexport const EmailRecoverySlide: React.FC<EmailRecoverySlideProps> = ({\n tatchiPasskey,\n accountId,\n refreshLoginState,\n emailRecoveryOptions\n}) => {\n\n const mountedRef = React.useRef(true);\n const mailtoAttemptTimerRef = React.useRef<number | null>(null);\n const cancelRequestedRef = React.useRef(false);\n\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 type NoInferType<T> = [T][T extends any ? 0 : never];\n const safeSet = React.useCallback(<T,>(\n setter: React.Dispatch<React.SetStateAction<T>>,\n value: React.SetStateAction<NoInferType<T>>,\n ) => {\n if (!mountedRef.current) return;\n setter(value);\n }, []);\n\n const [isBusy, setIsBusy] = React.useState(false);\n const [accountIdInput, setAccountIdInput] = 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<ExplorerToast | null>(null);\n\n const lastPrefilledAccountIdRef = 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 onEvent = React.useCallback(\n (ev: EmailRecoverySSEEvent) => {\n if (cancelRequestedRef.current) return;\n safeSet(setStatusText, ev?.message || null);\n emailRecoveryOptions?.onEvent?.(ev);\n\n const txHash = extractTxHashFromEmailRecoveryEvent(ev);\n if (txHash) {\n const base = getExplorerBaseUrl(tatchiPasskey);\n safeSet(setExplorerToast, { url: getExplorerTxUrl({ base, txHash }), transactionHash: txHash });\n }\n const elapsed = extractElapsedMsFromEmailRecoveryEvent(ev);\n if (elapsed === null) safeSet(setPollingElapsedMs, null);\n else if (elapsed != null) safeSet(setPollingElapsedMs, elapsed);\n\n if (ev.phase === EmailRecoveryPhase.ERROR || ev.status === EmailRecoveryStatus.ERROR) {\n const raw = 'error' in ev ? ev.error : ev.message;\n safeSet(setErrorText, raw || 'Email recovery failed');\n safeSet(setCanRestart, false);\n }\n },\n [emailRecoveryOptions, safeSet, tatchiPasskey],\n );\n\n const showExplorerToast = React.useCallback(\n (rawAccountId: string) => {\n const normalized = (rawAccountId || '').trim();\n if (!normalized) return;\n const base = getExplorerBaseUrl(tatchiPasskey);\n safeSet(setExplorerToast, { url: getExplorerAccountUrl({ base, accountId: normalized }), accountId: normalized });\n },\n [safeSet, tatchiPasskey],\n );\n\n const showExplorerTxToast = React.useCallback(\n (txHash: string) => {\n const normalized = (txHash || '').trim();\n if (!normalized) return;\n const base = getExplorerBaseUrl(tatchiPasskey);\n safeSet(setExplorerToast, { url: getExplorerTxUrl({ base, txHash: normalized }), transactionHash: normalized });\n },\n [safeSet, 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 safeSet(setMailtoUiState, '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 safeSet(setMailtoUiState, prev => (prev === 'opening' ? 'ready' : prev));\n mailtoAttemptTimerRef.current = null;\n }, MAILTO_REENABLE_MS);\n\n launchMailto(url);\n },\n [launchMailto, safeSet],\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 safeSet(setMailtoUiState, '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, safeSet]);\n\n React.useEffect(() => {\n const normalized = (accountIdInput || '').trim();\n if (!normalized) {\n setAccountInfo(null);\n setAccountInfoError(null);\n setAccountInfoLoading(false);\n safeSet(setLocalRecoveryEmails, []);\n return;\n }\n\n let cancelled = false;\n // Show loading state immediately (don't wait for debounce).\n safeSet(setAccountInfoLoading, true);\n safeSet(setAccountInfoError, null);\n const handle = window.setTimeout(() => {\n void (async () => {\n try {\n const records = await tatchiPasskey.getRecoveryEmails(normalized);\n const resolvedEmails = deriveEmailsFromRecoveryRecords(records);\n\n if (!cancelled) {\n safeSet(setLocalRecoveryEmails, resolvedEmails);\n }\n\n const info: EmailRecoveryAccountInfo | null = records\n ? { emailsCount: Array.isArray(records) ? records.length : 0 }\n : null;\n if (cancelled) return;\n safeSet(setAccountInfo, info);\n } catch (err: unknown) {\n if (cancelled) return;\n safeSet(setAccountInfo, null);\n const msg = err instanceof Error ? err.message : '';\n safeSet(setAccountInfoError, msg || 'Failed to load email recovery settings for this account');\n safeSet(setLocalRecoveryEmails, []);\n } finally {\n if (!cancelled) safeSet(setAccountInfoLoading, false);\n }\n })();\n }, ACCOUNT_INFO_DEBOUNCE_MS);\n\n return () => {\n cancelled = true;\n window.clearTimeout(handle);\n };\n }, [accountIdInput, safeSet, tatchiPasskey]);\n\n const handleStart = React.useCallback(async () => {\n const normalizedAccountId = (accountIdInput || '').trim();\n if (!normalizedAccountId) {\n safeSet(setErrorText, 'Enter an account ID.');\n return;\n }\n\n if (accountInfoLoading) {\n safeSet(setErrorText, 'Checking recovery email settings…');\n return;\n }\n\n if (accountInfoError) {\n safeSet(setErrorText, accountInfoError);\n return;\n }\n\n if (!accountInfo) {\n safeSet(setErrorText, 'Failed to load email recovery settings for this account.');\n return;\n }\n\n if (accountInfo.emailsCount === 0) {\n safeSet(setErrorText, 'No recovery emails are configured for this account.');\n return;\n }\n\n safeSet(setIsBusy, true);\n cancelRequestedRef.current = false;\n safeSet(setErrorText, null);\n safeSet(setCanRestart, false);\n safeSet(setStatusText, null);\n safeSet(setPollingElapsedMs, null);\n safeSet(setPendingMailtoUrl, null);\n safeSet(setPendingNearPublicKey, null);\n safeSet(setMailtoUiState, 'ready');\n\n let didForwardError = false;\n try {\n const result = await tatchiPasskey.startEmailRecovery({\n accountId: normalizedAccountId,\n options: {\n onEvent,\n onError: (err: Error) => {\n if (cancelRequestedRef.current) return;\n safeSet(setErrorText, err?.message || 'Failed to start email recovery');\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n } satisfies EmailRecoveryFlowOptions,\n });\n\n safeSet(setPendingMailtoUrl, result.mailtoUrl);\n safeSet(setPendingNearPublicKey, result.nearPublicKey);\n safeSet(\n setStatusText,\n 'Recovery email draft ready. If it didn’t open automatically, click “Open recovery email draft”. Waiting for verification…'\n );\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 safeSet(setErrorText, uiError.message || 'Failed to finalize email recovery');\n safeSet(setCanRestart, uiError.canRestart);\n const txHash = getEmailRecoveryErrorTxHash(err);\n if (txHash) showExplorerTxToast(txHash);\n didForwardError = true;\n emailRecoveryOptions?.onError?.(err);\n },\n } satisfies EmailRecoveryFlowOptions,\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 safeSet(setStatusText, 'Email recovery completed. Logging you in…');\n const loginResult = await tatchiPasskey\n .loginAndCreateSession(normalizedAccountId)\n .catch(() => null);\n if (loginResult?.success) {\n const updatedSession = await tatchiPasskey.getLoginSession(normalizedAccountId).catch(() => null);\n loginOk = !!updatedSession?.login?.isLoggedIn;\n } else {\n loginOk = false;\n }\n }\n\n if (refreshLoginState) {\n await refreshLoginState(normalizedAccountId).catch(() => {});\n }\n\n safeSet(\n setStatusText,\n loginOk ? 'Email recovery completed on this device.' : 'Email recovery completed. Please log in on this device.'\n );\n safeSet(setPendingMailtoUrl, null);\n safeSet(setMailtoUiState, 'ready');\n safeSet(setPollingElapsedMs, null);\n } catch (err: unknown) {\n if (cancelRequestedRef.current) {\n safeSet(setErrorText, 'Email recovery cancelled. Please try again.');\n safeSet(setStatusText, null);\n safeSet(setPollingElapsedMs, null);\n safeSet(setPendingMailtoUrl, null);\n safeSet(setPendingNearPublicKey, null);\n safeSet(setMailtoUiState, 'ready');\n safeSet(setCanRestart, false);\n return;\n }\n const uiError = getEmailRecoveryUiError(err);\n safeSet(setErrorText, uiError.message || 'Failed to start email recovery');\n safeSet(setCanRestart, uiError.canRestart);\n const txHash = getEmailRecoveryErrorTxHash(err);\n if (txHash) showExplorerTxToast(txHash);\n if (!didForwardError && err instanceof Error) {\n emailRecoveryOptions?.onError?.(err);\n }\n } finally {\n safeSet(setIsBusy, false);\n }\n }, [\n accountIdInput,\n accountInfo,\n accountInfoError,\n accountInfoLoading,\n attemptOpenMailtoAuto,\n emailRecoveryOptions,\n onEvent,\n refreshLoginState,\n safeSet,\n showExplorerToast,\n showExplorerTxToast,\n tatchiPasskey,\n ]);\n\n const handleRestart = React.useCallback(async () => {\n const normalizedAccountId = (accountIdInput || '').trim();\n if (!normalizedAccountId) return;\n\n safeSet(setIsBusy, true);\n try {\n cancelRequestedRef.current = true;\n await tatchiPasskey\n .cancelEmailRecovery({\n accountId: normalizedAccountId,\n nearPublicKey: pendingNearPublicKey || undefined,\n })\n .catch(() => {});\n safeSet(setErrorText, null);\n safeSet(setStatusText, null);\n safeSet(setPollingElapsedMs, null);\n safeSet(setPendingMailtoUrl, null);\n safeSet(setPendingNearPublicKey, null);\n safeSet(setMailtoUiState, 'ready');\n safeSet(setCanRestart, false);\n } finally {\n cancelRequestedRef.current = false;\n safeSet(setIsBusy, false);\n }\n }, [accountIdInput, pendingNearPublicKey, safeSet, tatchiPasskey]);\n\n const summaryLine: React.ReactNode = accountInfoLoading\n ? (\n <>\n Checking if account has recovery emails configured\n <span className=\"w3a-ellipsis\" aria-hidden=\"true\">\n <span className=\"w3a-ellipsis-dot\">.</span>\n <span className=\"w3a-ellipsis-dot\">.</span>\n <span className=\"w3a-ellipsis-dot\">.</span>\n </span>\n </>\n )\n : accountInfo && !accountInfoError\n ? `Recovery emails configured: ${accountInfo.emailsCount}`\n : '\\u00A0';\n\n const noRecoveryEmailsConfigured =\n !accountInfoLoading && !accountInfoError && !!accountInfo && accountInfo.emailsCount === 0;\n const startDisabled = isBusy || accountInfoLoading || !!accountInfoError || !accountInfo || noRecoveryEmailsConfigured;\n const showFromWarning = !!accountIdInput.trim() && !noRecoveryEmailsConfigured;\n const showRestart = !!errorText && canRestart;\n\n return (\n <div className=\"w3a-email-recovery-slide\">\n <EmailRecoveryHeader />\n <AccountIdInputRow value={accountIdInput} onChange={setAccountIdInput} disabled={isBusy} />\n <RecoveryEmailsSummary\n summaryLine={summaryLine}\n accountInfoError={accountInfoError}\n localRecoveryEmails={localRecoveryEmails}\n showFromWarning={showFromWarning}\n />\n <EmailRecoveryActions\n isBusy={isBusy}\n pendingMailtoUrl={pendingMailtoUrl}\n mailtoUiState={mailtoUiState}\n startDisabled={startDisabled}\n accountInfoLoading={accountInfoLoading}\n noRecoveryEmailsConfigured={noRecoveryEmailsConfigured}\n showRestart={showRestart}\n onStart={handleStart}\n onOpenDraft={attemptOpenMailtoFromUserGesture}\n onRestart={handleRestart}\n />\n <EmailRecoveryStatusPanel\n errorText={errorText}\n statusText={statusText}\n pollingElapsedMs={pollingElapsedMs}\n explorerToast={explorerToast}\n />\n </div>\n );\n};\n\nexport default EmailRecoverySlide;\n"],"mappings":"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"}
@@ -32,7 +32,7 @@ function useTatchiWithSdkFlow(args) {
32
32
  ...options,
33
33
  onEvent: (event) => {
34
34
  appendSdkEventMessage(seq, event.message);
35
- if (event.phase === RegistrationPhase.STEP_8_REGISTRATION_COMPLETE && event.status === RegistrationStatus.SUCCESS) endSdkFlow("register", seq, "success");
35
+ if (event.phase === RegistrationPhase.STEP_9_REGISTRATION_COMPLETE && event.status === RegistrationStatus.SUCCESS) endSdkFlow("register", seq, "success");
36
36
  else if (event.phase === RegistrationPhase.REGISTRATION_ERROR || event.status === RegistrationStatus.ERROR) {
37
37
  const error = "error" in event ? event.error : event.message;
38
38
  endSdkFlow("register", seq, "error", error || event.message);
@@ -1 +1 @@
1
- {"version":3,"file":"useTatchiWithSdkFlow.js","names":["loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn","wrappedOptions: LoginHooksOptions","registerPasskeyWithSdkFlow: RegisterPasskeyFn","wrappedOptions: RegistrationHooksOptions","syncAccountWithSdkFlow: SyncAccountFn","args","options: SyncAccountHooksOptions | undefined","wrappedOptions: SyncAccountHooksOptions","value: unknown"],"sources":["../../../../src/react/context/useTatchiWithSdkFlow.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport {\n SyncAccountPhase,\n SyncAccountStatus,\n type SyncAccountHooksOptions,\n type SyncAccountSSEEvent,\n LoginPhase,\n LoginStatus,\n type LoginHooksOptions,\n type LoginSSEvent,\n RegistrationPhase,\n RegistrationStatus,\n type RegistrationHooksOptions,\n type RegistrationSSEEvent,\n} from '@/core/types/sdkSentEvents';\n\nexport function useTatchiWithSdkFlow(args: {\n tatchi: TatchiPasskey;\n beginSdkFlow: (kind: 'login' | 'register' | 'sync', accountId?: string) => number;\n appendSdkEventMessage: (seq: number, message: string) => void;\n endSdkFlow: (kind: 'login' | 'register' | 'sync', seq: number, status: 'success' | 'error', error?: string) => void;\n}): TatchiPasskey {\n const { tatchi, beginSdkFlow, appendSdkEventMessage, endSdkFlow } = args;\n\n return useMemo(() => {\n /**\n * We use a `Proxy` to instrument a few core flow entrypoints (login/register/sync)\n * while preserving the full `TatchiPasskey` API surface.\n *\n * This lets *all* callers (not just PasskeyAuthMenu) use `ctx.tatchi.*` directly and\n * still have `sdkFlow` update as events stream in.\n */\n type LoginAndCreateSessionFn = TatchiPasskey['loginAndCreateSession'];\n type RegisterPasskeyFn = TatchiPasskey['registerPasskey'];\n type SyncAccountFn = TatchiPasskey['syncAccount'];\n\n const loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('login', nearAccountId);\n const wrappedOptions: LoginHooksOptions = {\n ...options,\n onEvent: (event: LoginSSEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (event.phase === LoginPhase.STEP_4_LOGIN_COMPLETE && event.status === LoginStatus.SUCCESS) {\n endSdkFlow('login', seq, 'success');\n } else if (event.phase === LoginPhase.LOGIN_ERROR || event.status === LoginStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('login', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('login', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.loginAndCreateSession(nearAccountId, wrappedOptions);\n };\n\n const registerPasskeyWithSdkFlow: RegisterPasskeyFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('register', nearAccountId);\n const wrappedOptions: RegistrationHooksOptions = {\n ...options,\n onEvent: (event: RegistrationSSEEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (\n event.phase === RegistrationPhase.STEP_8_REGISTRATION_COMPLETE &&\n event.status === RegistrationStatus.SUCCESS\n ) {\n endSdkFlow('register', seq, 'success');\n } else if (event.phase === RegistrationPhase.REGISTRATION_ERROR || event.status === RegistrationStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('register', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('register', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.registerPasskey(nearAccountId, wrappedOptions);\n };\n\n const syncAccountWithSdkFlow: SyncAccountFn = async (args) => {\n const seq = beginSdkFlow('sync', args?.accountId);\n const options: SyncAccountHooksOptions | undefined = args?.options;\n\n const wrappedOptions: SyncAccountHooksOptions = {\n ...options,\n onEvent: (event: SyncAccountSSEEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (\n event.phase === SyncAccountPhase.STEP_5_SYNC_ACCOUNT_COMPLETE &&\n event.status === SyncAccountStatus.SUCCESS\n ) {\n endSdkFlow('sync', seq, 'success');\n } else if (event.phase === SyncAccountPhase.ERROR || event.status === SyncAccountStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('sync', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('sync', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.syncAccount({\n ...args,\n options: wrappedOptions,\n });\n };\n\n return new Proxy(tatchi, {\n get(target, prop, receiver) {\n if (prop === 'loginAndCreateSession') {\n return loginAndCreateSessionWithSdkFlow;\n }\n\n if (prop === 'registerPasskey') {\n return registerPasskeyWithSdkFlow;\n }\n\n if (prop === 'syncAccount') {\n return syncAccountWithSdkFlow;\n }\n\n const value: unknown = Reflect.get(target as object, prop, receiver);\n // For non-wrapped methods, bind to preserve `this` on the class instance.\n if (typeof value === 'function') return (value as (...args: unknown[]) => unknown).bind(target);\n return value;\n },\n });\n }, [appendSdkEventMessage, beginSdkFlow, endSdkFlow, tatchi]);\n}\n\nexport default useTatchiWithSdkFlow;\n"],"mappings":";;;;AAiBA,SAAgB,qBAAqB,MAKnB;CAChB,MAAM,EAAE,QAAQ,cAAc,uBAAuB,eAAe;AAEpE,QAAO,cAAc;EAYnB,MAAMA,mCAA4D,OAChE,eACA,YACG;GACH,MAAM,MAAM,aAAa,SAAS;GAClC,MAAMC,iBAAoC;IACxC,GAAG;IACH,UAAU,UAAwB;AAChC,2BAAsB,KAAK,MAAM;AACjC,SAAI,MAAM,UAAU,WAAW,yBAAyB,MAAM,WAAW,YAAY,QACnF,YAAW,SAAS,KAAK;cAChB,MAAM,UAAU,WAAW,eAAe,MAAM,WAAW,YAAY,OAAO;MACvF,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,SAAS,KAAK,SAAS,SAAS,MAAM;;AAEnD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,SAAS,KAAK,SAAS,MAAM;AACxC,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,sBAAsB,eAAe;;EAG3D,MAAMC,6BAAgD,OACpD,eACA,YACG;GACH,MAAM,MAAM,aAAa,YAAY;GACrC,MAAMC,iBAA2C;IAC/C,GAAG;IACH,UAAU,UAAgC;AACxC,2BAAsB,KAAK,MAAM;AACjC,SACE,MAAM,UAAU,kBAAkB,gCAClC,MAAM,WAAW,mBAAmB,QAEpC,YAAW,YAAY,KAAK;cACnB,MAAM,UAAU,kBAAkB,sBAAsB,MAAM,WAAW,mBAAmB,OAAO;MAC5G,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,YAAY,KAAK,SAAS,SAAS,MAAM;;AAEtD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,YAAY,KAAK,SAAS,MAAM;AAC3C,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,gBAAgB,eAAe;;EAGrD,MAAMC,yBAAwC,OAAO,WAAS;GAC5D,MAAM,MAAM,aAAa,QAAQC,QAAM;GACvC,MAAMC,UAA+CD,QAAM;GAE3D,MAAME,iBAA0C;IAC9C,GAAG;IACH,UAAU,UAA+B;AACvC,2BAAsB,KAAK,MAAM;AACjC,SACE,MAAM,UAAU,iBAAiB,gCACjC,MAAM,WAAW,kBAAkB,QAEnC,YAAW,QAAQ,KAAK;cACf,MAAM,UAAU,iBAAiB,SAAS,MAAM,WAAW,kBAAkB,OAAO;MAC7F,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,QAAQ,KAAK,SAAS,SAAS,MAAM;;AAElD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,QAAQ,KAAK,SAAS,MAAM;AACvC,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,YAAY;IAC9B,GAAGF;IACH,SAAS;;;AAIb,SAAO,IAAI,MAAM,QAAQ,EACvB,IAAI,QAAQ,MAAM,UAAU;AAC1B,OAAI,SAAS,wBACX,QAAO;AAGT,OAAI,SAAS,kBACX,QAAO;AAGT,OAAI,SAAS,cACX,QAAO;GAGT,MAAMG,QAAiB,QAAQ,IAAI,QAAkB,MAAM;AAE3D,OAAI,OAAO,UAAU,WAAY,QAAQ,MAA0C,KAAK;AACxF,UAAO;;IAGV;EAAC;EAAuB;EAAc;EAAY"}
1
+ {"version":3,"file":"useTatchiWithSdkFlow.js","names":["loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn","wrappedOptions: LoginHooksOptions","registerPasskeyWithSdkFlow: RegisterPasskeyFn","wrappedOptions: RegistrationHooksOptions","syncAccountWithSdkFlow: SyncAccountFn","args","options: SyncAccountHooksOptions | undefined","wrappedOptions: SyncAccountHooksOptions","value: unknown"],"sources":["../../../../src/react/context/useTatchiWithSdkFlow.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport {\n SyncAccountPhase,\n SyncAccountStatus,\n type SyncAccountHooksOptions,\n type SyncAccountSSEEvent,\n LoginPhase,\n LoginStatus,\n type LoginHooksOptions,\n type LoginSSEvent,\n RegistrationPhase,\n RegistrationStatus,\n type RegistrationHooksOptions,\n type RegistrationSSEEvent,\n} from '@/core/types/sdkSentEvents';\n\nexport function useTatchiWithSdkFlow(args: {\n tatchi: TatchiPasskey;\n beginSdkFlow: (kind: 'login' | 'register' | 'sync', accountId?: string) => number;\n appendSdkEventMessage: (seq: number, message: string) => void;\n endSdkFlow: (kind: 'login' | 'register' | 'sync', seq: number, status: 'success' | 'error', error?: string) => void;\n}): TatchiPasskey {\n const { tatchi, beginSdkFlow, appendSdkEventMessage, endSdkFlow } = args;\n\n return useMemo(() => {\n /**\n * We use a `Proxy` to instrument a few core flow entrypoints (login/register/sync)\n * while preserving the full `TatchiPasskey` API surface.\n *\n * This lets *all* callers (not just PasskeyAuthMenu) use `ctx.tatchi.*` directly and\n * still have `sdkFlow` update as events stream in.\n */\n type LoginAndCreateSessionFn = TatchiPasskey['loginAndCreateSession'];\n type RegisterPasskeyFn = TatchiPasskey['registerPasskey'];\n type SyncAccountFn = TatchiPasskey['syncAccount'];\n\n const loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('login', nearAccountId);\n const wrappedOptions: LoginHooksOptions = {\n ...options,\n onEvent: (event: LoginSSEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (event.phase === LoginPhase.STEP_4_LOGIN_COMPLETE && event.status === LoginStatus.SUCCESS) {\n endSdkFlow('login', seq, 'success');\n } else if (event.phase === LoginPhase.LOGIN_ERROR || event.status === LoginStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('login', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('login', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.loginAndCreateSession(nearAccountId, wrappedOptions);\n };\n\n const registerPasskeyWithSdkFlow: RegisterPasskeyFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('register', nearAccountId);\n const wrappedOptions: RegistrationHooksOptions = {\n ...options,\n onEvent: (event: RegistrationSSEEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (\n event.phase === RegistrationPhase.STEP_9_REGISTRATION_COMPLETE &&\n event.status === RegistrationStatus.SUCCESS\n ) {\n endSdkFlow('register', seq, 'success');\n } else if (event.phase === RegistrationPhase.REGISTRATION_ERROR || event.status === RegistrationStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('register', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('register', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.registerPasskey(nearAccountId, wrappedOptions);\n };\n\n const syncAccountWithSdkFlow: SyncAccountFn = async (args) => {\n const seq = beginSdkFlow('sync', args?.accountId);\n const options: SyncAccountHooksOptions | undefined = args?.options;\n\n const wrappedOptions: SyncAccountHooksOptions = {\n ...options,\n onEvent: (event: SyncAccountSSEEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (\n event.phase === SyncAccountPhase.STEP_5_SYNC_ACCOUNT_COMPLETE &&\n event.status === SyncAccountStatus.SUCCESS\n ) {\n endSdkFlow('sync', seq, 'success');\n } else if (event.phase === SyncAccountPhase.ERROR || event.status === SyncAccountStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('sync', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('sync', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.syncAccount({\n ...args,\n options: wrappedOptions,\n });\n };\n\n return new Proxy(tatchi, {\n get(target, prop, receiver) {\n if (prop === 'loginAndCreateSession') {\n return loginAndCreateSessionWithSdkFlow;\n }\n\n if (prop === 'registerPasskey') {\n return registerPasskeyWithSdkFlow;\n }\n\n if (prop === 'syncAccount') {\n return syncAccountWithSdkFlow;\n }\n\n const value: unknown = Reflect.get(target as object, prop, receiver);\n // For non-wrapped methods, bind to preserve `this` on the class instance.\n if (typeof value === 'function') return (value as (...args: unknown[]) => unknown).bind(target);\n return value;\n },\n });\n }, [appendSdkEventMessage, beginSdkFlow, endSdkFlow, tatchi]);\n}\n\nexport default useTatchiWithSdkFlow;\n"],"mappings":";;;;AAiBA,SAAgB,qBAAqB,MAKnB;CAChB,MAAM,EAAE,QAAQ,cAAc,uBAAuB,eAAe;AAEpE,QAAO,cAAc;EAYnB,MAAMA,mCAA4D,OAChE,eACA,YACG;GACH,MAAM,MAAM,aAAa,SAAS;GAClC,MAAMC,iBAAoC;IACxC,GAAG;IACH,UAAU,UAAwB;AAChC,2BAAsB,KAAK,MAAM;AACjC,SAAI,MAAM,UAAU,WAAW,yBAAyB,MAAM,WAAW,YAAY,QACnF,YAAW,SAAS,KAAK;cAChB,MAAM,UAAU,WAAW,eAAe,MAAM,WAAW,YAAY,OAAO;MACvF,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,SAAS,KAAK,SAAS,SAAS,MAAM;;AAEnD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,SAAS,KAAK,SAAS,MAAM;AACxC,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,sBAAsB,eAAe;;EAG3D,MAAMC,6BAAgD,OACpD,eACA,YACG;GACH,MAAM,MAAM,aAAa,YAAY;GACrC,MAAMC,iBAA2C;IAC/C,GAAG;IACH,UAAU,UAAgC;AACxC,2BAAsB,KAAK,MAAM;AACjC,SACE,MAAM,UAAU,kBAAkB,gCAClC,MAAM,WAAW,mBAAmB,QAEpC,YAAW,YAAY,KAAK;cACnB,MAAM,UAAU,kBAAkB,sBAAsB,MAAM,WAAW,mBAAmB,OAAO;MAC5G,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,YAAY,KAAK,SAAS,SAAS,MAAM;;AAEtD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,YAAY,KAAK,SAAS,MAAM;AAC3C,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,gBAAgB,eAAe;;EAGrD,MAAMC,yBAAwC,OAAO,WAAS;GAC5D,MAAM,MAAM,aAAa,QAAQC,QAAM;GACvC,MAAMC,UAA+CD,QAAM;GAE3D,MAAME,iBAA0C;IAC9C,GAAG;IACH,UAAU,UAA+B;AACvC,2BAAsB,KAAK,MAAM;AACjC,SACE,MAAM,UAAU,iBAAiB,gCACjC,MAAM,WAAW,kBAAkB,QAEnC,YAAW,QAAQ,KAAK;cACf,MAAM,UAAU,iBAAiB,SAAS,MAAM,WAAW,kBAAkB,OAAO;MAC7F,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,QAAQ,KAAK,SAAS,SAAS,MAAM;;AAElD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,QAAQ,KAAK,SAAS,MAAM;AACvC,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,YAAY;IAC9B,GAAGF;IACH,SAAS;;;AAIb,SAAO,IAAI,MAAM,QAAQ,EACvB,IAAI,QAAQ,MAAM,UAAU;AAC1B,OAAI,SAAS,wBACX,QAAO;AAGT,OAAI,SAAS,kBACX,QAAO;AAGT,OAAI,SAAS,cACX,QAAO;GAGT,MAAMG,QAAiB,QAAQ,IAAI,QAAkB,MAAM;AAE3D,OAAI,OAAO,UAAU,WAAY,QAAQ,MAA0C,KAAK;AACxF,UAAO;;IAGV;EAAC;EAAuB;EAAc;EAAY"}
@@ -20,10 +20,10 @@ import { QRCodeScanner } from "./components/QRCodeScanner.js";
20
20
  import { AccountMenuButton, ProfileSettingsButton } from "./components/AccountMenuButton/index.js";
21
21
  import { ShowQRCode } from "./components/ShowQRCode2.js";
22
22
  import { PasskeyAuthMenuSkeleton, PasskeyAuthMenuSkeletonInner } from "./components/PasskeyAuthMenu/skeleton.js";
23
+ import { preloadPasskeyAuthMenu } from "./components/PasskeyAuthMenu/preload.js";
23
24
  import { PasskeyAuthMenu } from "./components/PasskeyAuthMenu/shell.js";
24
25
  import { AuthMenuMode, AuthMenuModeMap } from "./components/PasskeyAuthMenu/authMenuTypes.js";
25
26
  import QRCodeIcon_default from "./components/QRCodeIcon.js";
26
27
  import { PasskeyAuthMenuClient } from "./components/PasskeyAuthMenu/client.js";
27
- import { preloadPasskeyAuthMenu } from "./components/PasskeyAuthMenu/preload.js";
28
28
 
29
29
  export { AccountMenuButton, ActionPhase, ActionStatus, ActionType, AuthMenuMode, AuthMenuModeMap, DARK_TOKENS, ActionPhase as DelegateActionPhase, DeviceLinkingPhase, DeviceLinkingStatus, EmailRecoveryPhase, EmailRecoveryStatus, LIGHT_TOKENS, LoginPhase, LoginStatus, MoonIcon_default as MoonIcon, PASSKEY_MANAGER_DEFAULT_CONFIGS, PROFILE_MENU_ITEM_IDS, PasskeyAuthMenu, PasskeyAuthMenuClient, PasskeyAuthMenuSkeleton, PasskeyAuthMenuSkeletonInner, ProfileSettingsButton, QRCodeIcon_default as QRCodeIcon, QRCodeScanner, QRScanMode, RegistrationPhase, RegistrationStatus, ShowQRCode, SunIcon_default as SunIcon, SyncAccountPhase, SyncAccountStatus, TatchiContextProvider, TatchiPasskey, TatchiPasskeyProvider, Theme, TouchIcon_default as TouchIcon, TxExecutionStatus, preloadPasskeyAuthMenu, useAccountInput, useDeviceLinking, useNearClient, usePostfixPosition, useQRCamera, useQRFileUpload, useTatchi, useTheme };
@@ -298,8 +298,8 @@ var PasskeyClientDBManager = class {
298
298
  await this.storeUser(userData);
299
299
  return userData;
300
300
  }
301
- async updateUser(nearAccountId, updates) {
302
- const user = await this.getUser(nearAccountId);
301
+ async updateUser(nearAccountId, updates, deviceNumber) {
302
+ const user = await this.getUser(nearAccountId, deviceNumber);
303
303
  if (user) {
304
304
  const updatedUser = {
305
305
  ...user,
@@ -1 +1 @@
1
- {"version":3,"file":"passkeyClientDB.js","names":["DB_CONFIG: PasskeyClientDBConfig","err: any","entry: AppStateEntry<T>","userData: ClientUserData","lastUserState: LastUserAccountIdState","fixed: ClientUserData","updatedUser: ClientUserData","clientAuth: ClientAuthenticatorData","rec: DerivedAddressRecord","rec: RecoveryEmailRecord"],"sources":["../../../../../../src/core/IndexedDBManager/passkeyClientDB.ts"],"sourcesContent":["import { openDB, type IDBPDatabase } from 'idb';\nimport { type ValidationResult, validateNearAccountId } from '../../utils/validation';\nimport type { AccountId } from '../types/accountIds';\nimport { toAccountId } from '../types/accountIds';\nimport {\n ConfirmationConfig,\n DEFAULT_CONFIRMATION_CONFIG,\n type SignerMode,\n DEFAULT_SIGNING_MODE,\n coerceSignerMode,\n} from '../types/signer-worker'\n\n\nexport interface ClientUserData {\n // Primary key - now uses AccountId + deviceNumber for unique identification\n nearAccountId: AccountId;\n deviceNumber: number; // Device number for multi-device support (1-indexed)\n version?: number;\n\n // User metadata\n registeredAt?: number;\n lastLogin?: number;\n lastUpdated?: number;\n\n // WebAuthn/Passkey data (merged from WebAuthnManager)\n clientNearPublicKey: string;\n passkeyCredential: {\n id: string;\n rawId: string;\n };\n\n // VRF credentials for stateless authentication\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: string;\n chacha20NonceB64u: string;\n };\n // Server-assisted auto-login (VRF key session): Shamir 3-pass fields\n // Stores relayer-blinded KEK and the VRF ciphertext; server never sees plaintext VRF or KEK\n serverEncryptedVrfKeypair?: {\n ciphertextVrfB64u: string;\n kek_s_b64u: string;\n // Metadata for proactive refresh\n serverKeyId: string;\n updatedAt?: number;\n };\n\n // User preferences\n preferences?: UserPreferences;\n}\n\nexport type StoreUserDataInput = Omit<ClientUserData, 'deviceNumber' | 'lastLogin' | 'registeredAt'>\n & {\n deviceNumber?: number;\n serverEncryptedVrfKeypair?: ClientUserData['serverEncryptedVrfKeypair'];\n version?: number;\n };\n\nexport type StoreWebAuthnUserDataInput = {\n nearAccountId: AccountId;\n deviceNumber: number;\n clientNearPublicKey: string;\n lastUpdated?: number;\n version?: number;\n passkeyCredential: ClientUserData['passkeyCredential'];\n encryptedVrfKeypair: ClientUserData['encryptedVrfKeypair'];\n serverEncryptedVrfKeypair?: ClientUserData['serverEncryptedVrfKeypair'];\n};\n\nexport interface UserPreferences {\n useRelayer: boolean;\n useNetwork: 'testnet' | 'mainnet';\n confirmationConfig: ConfirmationConfig;\n signerMode?: SignerMode;\n // User preferences can be extended here as needed\n}\n\n// Authenticator cache\nexport interface ClientAuthenticatorData {\n credentialId: string;\n credentialPublicKey: Uint8Array;\n transports?: string[]; // AuthenticatorTransport[]\n name?: string;\n nearAccountId: AccountId; // FK reference using AccountId\n deviceNumber: number; // Device number for this authenticator (1-indexed)\n registered: string; // ISO date string\n syncedAt: string; // When this cache entry was last synced with contract\n vrfPublicKey: string; // Base64-encoded VRF public key (1:1 relationship on client)\n}\n\ninterface AppStateEntry<T = unknown> {\n key: string;\n value: T;\n}\n\n// Internal helper: legacy user records may be missing deviceNumber.\ntype ClientUserDataWithOptionalDevice =\n | ClientUserData\n | (Omit<ClientUserData, 'deviceNumber'> & { deviceNumber?: number });\n\n// Special type for lastUserAccountId app state entry\nexport interface LastUserAccountIdState {\n accountId: AccountId;\n deviceNumber: number;\n}\n\ninterface PasskeyClientDBConfig {\n dbName: string;\n dbVersion: number;\n userStore: string;\n appStateStore: string;\n authenticatorStore: string;\n derivedAddressStore: string;\n recoveryEmailStore: string;\n}\n\n// === CONSTANTS ===\nconst DB_CONFIG: PasskeyClientDBConfig = {\n dbName: 'PasskeyClientDB',\n dbVersion: 15, // v15: add recoveryEmails store\n userStore: 'users',\n appStateStore: 'appState',\n authenticatorStore: 'authenticators',\n derivedAddressStore: 'derivedAddresses',\n recoveryEmailStore: 'recoveryEmails'\n} as const;\n\nexport interface IndexedDBEvent {\n type: 'user-updated' | 'preferences-updated' | 'user-deleted';\n accountId: AccountId;\n data?: Record<string, unknown>;\n}\n\n// Persisted mapping of derived (e.g., EVM) addresses tied to an account\n/**\n * Persisted mapping of derived (e.g., EVM/Solana/Zcash) addresses tied to an account.\n *\n * Notes on multi-chain support:\n * - The composite primary key is [nearAccountId, contractId, path]. To support\n * different chains and chain IDs, encode them in the `path` string, e.g.:\n * - EVM: `evm:<chainId>:<derivationPath>` → `evm:84532:ethereum-1`\n * - Solana: `solana:<derivationPath>`\n * - Zcash: `zcash:<derivationPath>`\n * - Additional descriptive fields like `namespace` and `chainRef` are optional metadata\n * and are not part of the key.\n */\nexport interface DerivedAddressRecord {\n nearAccountId: AccountId;\n contractId: string; // MPC/Derivation contract on NEAR\n path: string; // Composite path (may include namespace/chainId); see docs above\n address: string; // Derived address (e.g., 0x...)\n updatedAt: number;\n // Optional metadata (not used in the key)\n namespace?: string; // e.g., 'evm', 'solana', 'zcash'\n chainRef?: string; // e.g., chainId '84532' or a named network slug\n}\n\n/**\n * Persisted mapping of recovery email hashes to canonical email addresses for an account.\n *\n * Notes:\n * - Composite primary key is [nearAccountId, hashHex].\n * - `hashHex` is the 0x-prefixed hex encoding of the 32-byte hash:\n * SHA256(canonical_email || \"|\" || account_id)\n * - `email` is the canonical form: \"local@domain\", lowercased.\n */\nexport interface RecoveryEmailRecord {\n nearAccountId: AccountId;\n hashHex: string;\n email: string;\n addedAt: number;\n}\n\nexport class PasskeyClientDBManager {\n private config: PasskeyClientDBConfig;\n private db: IDBPDatabase | null = null;\n private disabled = false;\n private eventListeners: Set<(event: IndexedDBEvent) => void> = new Set();\n\n constructor(config: PasskeyClientDBConfig = DB_CONFIG) {\n this.config = config;\n }\n\n getDbName(): string {\n return this.config.dbName;\n }\n\n setDbName(dbName: string): void {\n const next = String(dbName || '').trim();\n if (!next || next === this.config.dbName) return;\n try { (this.db as any)?.close?.(); } catch {}\n this.db = null;\n this.config = { ...this.config, dbName: next };\n }\n\n isDisabled(): boolean {\n return this.disabled;\n }\n\n setDisabled(disabled: boolean): void {\n const next = !!disabled;\n if (next === this.disabled) return;\n this.disabled = next;\n if (next) {\n try { (this.db as any)?.close?.(); } catch {}\n this.db = null;\n }\n }\n\n // === EVENT SYSTEM ===\n\n onChange(listener: (event: IndexedDBEvent) => void): () => void {\n this.eventListeners.add(listener);\n return () => {\n this.eventListeners.delete(listener);\n };\n }\n\n private emitEvent(event: IndexedDBEvent): void {\n this.eventListeners.forEach(listener => {\n try {\n listener(event);\n } catch (error) {\n console.warn('[IndexedDBManager]: Error in event listener:', error);\n }\n });\n }\n\n private async getDB(): Promise<IDBPDatabase> {\n if (this.disabled) {\n throw new Error('[PasskeyClientDBManager] IndexedDB is disabled in this environment.');\n }\n if (this.db) {\n return this.db;\n }\n\n try {\n this.db = await openDB(this.config.dbName, this.config.dbVersion, {\n upgrade: (db, oldVersion, _newVersion, _transaction): void => {\n // Create stores if they don't exist\n if (!db.objectStoreNames.contains(DB_CONFIG.userStore)) {\n // Users table: composite key of [nearAccountId, deviceNumber]\n const userStore = db.createObjectStore(DB_CONFIG.userStore, { keyPath: ['nearAccountId', 'deviceNumber'] });\n userStore.createIndex('nearAccountId', 'nearAccountId', { unique: false });\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.appStateStore)) {\n db.createObjectStore(DB_CONFIG.appStateStore, { keyPath: 'key' });\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.authenticatorStore)) {\n // Authenticators table: composite key of [nearAccountId, deviceNumber, credentialId]\n const authStore = db.createObjectStore(DB_CONFIG.authenticatorStore, { keyPath: ['nearAccountId', 'deviceNumber', 'credentialId'] });\n authStore.createIndex('nearAccountId', 'nearAccountId', { unique: false });\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.derivedAddressStore)) {\n // Derived addresses: composite key of [nearAccountId, contractId, path]\n const dStore = db.createObjectStore(DB_CONFIG.derivedAddressStore, { keyPath: ['nearAccountId', 'contractId', 'path'] });\n try { dStore.createIndex('nearAccountId', 'nearAccountId', { unique: false }); } catch {}\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.recoveryEmailStore)) {\n // Recovery emails: composite key of [nearAccountId, hashHex]\n const rStore = db.createObjectStore(DB_CONFIG.recoveryEmailStore, { keyPath: ['nearAccountId', 'hashHex'] });\n try { rStore.createIndex('nearAccountId', 'nearAccountId', { unique: false }); } catch {}\n }\n },\n blocked() {\n console.warn('PasskeyClientDB connection is blocked.');\n },\n blocking() {\n console.warn('PasskeyClientDB connection is blocking another connection.');\n },\n terminated: () => {\n console.warn('PasskeyClientDB connection has been terminated.');\n this.db = null;\n },\n });\n\n // Post-open migrations (non-blocking)\n try { await this.runMigrationsIfNeeded(this.db); } catch {}\n\n } catch (err: any) {\n const msg = String(err?.message || '');\n if (err?.name === 'VersionError' || /less than the existing version/i.test(msg)) {\n // Mixed-version contexts (host/app) — open without version to adopt existing DB\n try {\n console.warn('PasskeyClientDB: opening existing DB without version due to VersionError');\n this.db = await openDB(this.config.dbName);\n } catch (e) {\n throw err;\n }\n } else {\n throw err;\n }\n }\n\n return this.db;\n }\n\n private async runMigrationsIfNeeded(_db: IDBPDatabase): Promise<void> {\n return;\n }\n\n // === APP STATE METHODS ===\n\n async getAppState<T = unknown>(key: string): Promise<T | undefined> {\n const db = await this.getDB();\n const result = await db.get(DB_CONFIG.appStateStore, key);\n return result?.value as T | undefined;\n }\n\n async setAppState<T = unknown>(key: string, value: T): Promise<void> {\n const db = await this.getDB();\n const entry: AppStateEntry<T> = { key, value };\n await db.put(DB_CONFIG.appStateStore, entry);\n }\n\n // === ACCOUNT ID VALIDATION AND UTILITIES ===\n\n /**\n * Validate that a NEAR account ID is in the expected format\n * Supports both <username>.<relayerAccountId> and <username>.testnet formats\n */\n validateNearAccountId(nearAccountId: AccountId): ValidationResult {\n return validateNearAccountId(nearAccountId);\n }\n\n /**\n * Extract username from NEAR account ID\n */\n extractUsername(nearAccountId: AccountId): string {\n const validation = validateNearAccountId(nearAccountId);\n if (!validation.valid) {\n throw new Error(`Invalid NEAR account ID: ${validation.error}`);\n }\n return nearAccountId.split('.')[0];\n }\n\n /**\n * Generate a NEAR account ID from a username and domain\n * @param username - The username to use for the account ID\n * @param domain - The domain to use for the account ID\n * @returns The generated NEAR account ID\n */\n generateNearAccountId(username: string, domain: string): string {\n const sanitizedName = username\n .toLowerCase()\n .replace(/[^a-z0-9_\\\\-]/g, '')\n .substring(0, 32);\n return `${sanitizedName}.${domain}`;\n }\n\n // === USER MANAGEMENT METHODS ===\n\n async getUser(nearAccountId: AccountId, deviceNumber?: number): Promise<ClientUserData | null> {\n if (!nearAccountId) return null;\n\n const validation = this.validateNearAccountId(nearAccountId);\n if (!validation.valid) {\n console.warn(`Invalid account ID format: ${nearAccountId}`);\n return null;\n }\n\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n\n if (typeof deviceNumber === 'number') {\n const rec = await db.get(DB_CONFIG.userStore, [accountId, deviceNumber]);\n if (!rec) return null;\n return await this.normalizeUserDeviceNumber(rec as ClientUserDataWithOptionalDevice, deviceNumber);\n }\n\n const index = db.transaction(DB_CONFIG.userStore).store.index('nearAccountId');\n const results = await index.getAll(accountId);\n if (results.length === 0) {\n return null;\n }\n\n if (results.length > 1) {\n console.warn(\n `Multiple passkeys found for account ${accountId}, deviceNumber not provided; ` +\n 'defaulting to last logged-in user.'\n );\n console.log('defaulting to last used user deviceNumber');\n const lastUserState = await this.getAppState<LastUserAccountIdState>('lastUserAccountId').catch(() => null);\n if (lastUserState && toAccountId(lastUserState.accountId) === accountId) {\n const keyed = await db.get(DB_CONFIG.userStore, [accountId, lastUserState.deviceNumber]);\n if (keyed) {\n return await this.normalizeUserDeviceNumber(\n keyed as ClientUserDataWithOptionalDevice,\n lastUserState.deviceNumber\n );\n }\n }\n }\n\n const first = results[0] as ClientUserDataWithOptionalDevice;\n if (!first) return null;\n return await this.normalizeUserDeviceNumber(first, 1);\n }\n\n /**\n * Get the current/last user\n * This is maintained via app state and updated whenever a user is stored or updated\n */\n async getLastUser(): Promise<ClientUserData | null> {\n const lastUserState = await this.getAppState<LastUserAccountIdState>('lastUserAccountId');\n if (!lastUserState) return null;\n const db = await this.getDB();\n const accountId = toAccountId(lastUserState.accountId);\n // Prefer exact device match using composite primary key\n const record = await db.get(DB_CONFIG.userStore, [accountId, lastUserState.deviceNumber]);\n if (record) return record as ClientUserData;\n // Fallback: return any user for account\n return this.getUser(accountId);\n }\n\n /** Get user record by composite key (nearAccountId, deviceNumber) */\n async getUserByDevice(nearAccountId: AccountId, deviceNumber: number): Promise<ClientUserData | null> {\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n const rec = await db.get(DB_CONFIG.userStore, [accountId, deviceNumber]);\n return rec as ClientUserData || null;\n }\n\n /**\n * Get the most recently updated user record for a given account.\n * Useful when deviceNumber is unknown but we need the freshest key for the account.\n */\n /**\n * Get the most recently updated user record for a given account.\n * Useful when deviceNumber is unknown but we need the freshest key for the account.\n */\n async getLastDBUpdatedUser(nearAccountId: AccountId): Promise<ClientUserData | null> {\n const db = await this.getDB();\n try {\n const idx = db.transaction(DB_CONFIG.userStore).store.index('nearAccountId');\n const all = await idx.getAll(toAccountId(nearAccountId));\n if (Array.isArray(all) && all.length > 0) {\n const latest = (all as ClientUserData[]).reduce((a, b) =>\n (a.lastUpdated ?? 0) >= (b.lastUpdated ?? 0) ? a : b\n );\n return latest;\n }\n } catch {\n // fall through\n }\n return null;\n }\n\n async hasPasskeyCredential(nearAccountId: AccountId): Promise<boolean> {\n const authenticators = await this.getAuthenticatorsByUser(nearAccountId);\n return !!authenticators[0]?.credentialId;\n }\n\n /**\n * Ensure the current passkey selection is aligned with the last logged-in device.\n *\n * - When multiple authenticators exist for an account and no deviceNumber is specified,\n * this helper prefers authenticators whose deviceNumber matches the last logged-in user.\n * - Optionally validates that a selected credential (by rawId) also matches the last-user device.\n *\n * @param nearAccountId - Account ID for which the operation is being performed\n * @param authenticators - All authenticators stored for the account\n * @param selectedCredentialRawId - Optional rawId of the credential chosen by WebAuthn\n * @returns filtered authenticators for allowCredentials, plus optional wrongPasskeyError\n */\n async ensureCurrentPasskey(\n nearAccountId: AccountId,\n authenticators: ClientAuthenticatorData[],\n selectedCredentialRawId?: string,\n ): Promise<{\n authenticatorsForPrompt: ClientAuthenticatorData[];\n wrongPasskeyError?: string;\n }> {\n if (authenticators.length <= 1) {\n return { authenticatorsForPrompt: authenticators };\n }\n\n const accountIdNormalized = toAccountId(nearAccountId);\n const lastUser = await this.getLastUser().catch(() => null);\n if (!lastUser || lastUser.nearAccountId !== accountIdNormalized) {\n return { authenticatorsForPrompt: authenticators };\n }\n\n const expectedDeviceNumber = lastUser.deviceNumber;\n const byDeviceNumber = authenticators.filter(a => a.deviceNumber === expectedDeviceNumber);\n\n // Prefer the credentialId for the last-user deviceNumber; use the stored last-user rawId\n // only when it matches an authenticator for that device (or when we have no device match).\n let expectedCredentialId = lastUser.passkeyCredential.rawId;\n if (byDeviceNumber.length > 0 && !byDeviceNumber.some(a => a.credentialId === expectedCredentialId)) {\n expectedCredentialId = byDeviceNumber[0].credentialId;\n }\n\n // Preference: restrict allowCredentials to the last-user credentialId.\n // Fallback: if the local authenticator cache is missing that entry, prefer the last-user deviceNumber.\n const byCredentialId = authenticators.filter(a => a.credentialId === expectedCredentialId);\n const authenticatorsForPrompt =\n byCredentialId.length > 0\n ? byCredentialId\n : (byDeviceNumber.length > 0 ? byDeviceNumber : authenticators);\n\n const wrongPasskeyError =\n selectedCredentialRawId && selectedCredentialRawId !== expectedCredentialId\n ? (\n `You have multiple passkeys (deviceNumbers) for account ${accountIdNormalized}, ` +\n 'but used a different passkey than the most recently logged-in one. Please use the passkey for the most recently logged-in device.'\n )\n : undefined;\n\n return { authenticatorsForPrompt, wrongPasskeyError };\n }\n\n /**\n * Register a new user with the given NEAR account ID\n * @param nearAccountId - Full NEAR account ID (e.g., \"username.testnet\" or \"username.relayer.testnet\")\n * @param additionalData - Additional user data to store\n */\n async registerUser(storeUserData: StoreUserDataInput): Promise<ClientUserData> {\n\n const validation = this.validateNearAccountId(storeUserData.nearAccountId);\n if (!validation.valid) {\n throw new Error(`Cannot register user with invalid account ID: ${validation.error}`);\n }\n\n const now = Date.now();\n\n const userData: ClientUserData = {\n nearAccountId: toAccountId(storeUserData.nearAccountId),\n deviceNumber: storeUserData.deviceNumber || 1, // Default to device 1 (1-indexed)\n version: storeUserData.version || 2,\n registeredAt: now,\n lastLogin: now,\n lastUpdated: now,\n clientNearPublicKey: storeUserData.clientNearPublicKey,\n passkeyCredential: storeUserData.passkeyCredential,\n preferences: {\n useRelayer: false,\n useNetwork: 'testnet',\n confirmationConfig: DEFAULT_CONFIRMATION_CONFIG,\n // Default preferences can be set here\n },\n encryptedVrfKeypair: storeUserData.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: storeUserData.serverEncryptedVrfKeypair,\n };\n\n await this.storeUser(userData);\n return userData;\n }\n\n async updateUser(nearAccountId: AccountId, updates: Partial<ClientUserData>): Promise<void> {\n const user = await this.getUser(nearAccountId);\n if (user) {\n const updatedUser = {\n ...user,\n ...updates,\n lastUpdated: Date.now()\n };\n await this.storeUser(updatedUser); // This will update the app state lastUserAccountId\n\n // Emit event for user updates\n this.emitEvent({\n type: 'user-updated',\n accountId: nearAccountId,\n data: { updates, updatedUser }\n });\n }\n }\n\n async updateLastLogin(nearAccountId: AccountId): Promise<void> {\n await this.updateUser(nearAccountId, { lastLogin: Date.now() });\n }\n\n /**\n * Set the last logged-in user\n * @param nearAccountId - The account ID of the user\n * @param deviceNumber - The device number (defaults to 1)\n */\n async setLastUser(nearAccountId: AccountId, deviceNumber: number = 1): Promise<void> {\n const lastUserState: LastUserAccountIdState = {\n accountId: nearAccountId,\n deviceNumber,\n };\n await this.setAppState('lastUserAccountId', lastUserState);\n }\n\n async updatePreferences(\n nearAccountId: AccountId,\n preferences: Partial<UserPreferences>\n ): Promise<void> {\n const user = await this.getUser(nearAccountId);\n if (user) {\n const updatedPreferences = {\n ...user.preferences,\n ...preferences\n } as UserPreferences;\n await this.updateUser(nearAccountId, { preferences: updatedPreferences });\n\n // Emit event for preference changes\n this.emitEvent({\n type: 'preferences-updated',\n accountId: nearAccountId,\n data: { preferences: updatedPreferences }\n });\n }\n }\n\n private async normalizeUserDeviceNumber(\n user: ClientUserDataWithOptionalDevice,\n defaultDeviceNumber: number\n ): Promise<ClientUserData> {\n const hasValidDevice =\n typeof user.deviceNumber === 'number' && Number.isFinite(user.deviceNumber);\n if (hasValidDevice) {\n return user as ClientUserData;\n }\n\n const deviceNumber = defaultDeviceNumber;\n const fixed: ClientUserData = {\n ...(user as Omit<ClientUserData, 'deviceNumber'>),\n deviceNumber,\n };\n await this.storeUser(fixed);\n return fixed;\n }\n\n private async storeUser(userData: ClientUserData): Promise<void> {\n const validation = this.validateNearAccountId(userData.nearAccountId);\n if (!validation.valid) {\n throw new Error(`Cannot store user with invalid account ID: ${validation.error}`);\n }\n\n const db = await this.getDB();\n await db.put(DB_CONFIG.userStore, userData);\n\n // Update lastUserAccountId with new format including device info\n const lastUserState: LastUserAccountIdState = {\n accountId: userData.nearAccountId,\n deviceNumber: userData.deviceNumber,\n };\n\n await this.setAppState('lastUserAccountId', lastUserState);\n }\n\n /**\n * Store WebAuthn user data (compatibility with WebAuthnManager)\n * @param userData - User data with nearAccountId as primary identifier\n */\n async storeWebAuthnUserData(userData: StoreWebAuthnUserDataInput): Promise<void> {\n const validation = this.validateNearAccountId(userData.nearAccountId);\n if (!validation.valid) {\n throw new Error(`Cannot store WebAuthn data for invalid account ID: ${validation.error}`);\n }\n\n const accountId = toAccountId(userData.nearAccountId);\n const deviceNumber = userData.deviceNumber;\n let user = await this.getUser(accountId, deviceNumber);\n\n if (!user) {\n user = await this.registerUser({\n nearAccountId: accountId,\n deviceNumber,\n clientNearPublicKey: userData.clientNearPublicKey,\n passkeyCredential: userData.passkeyCredential,\n encryptedVrfKeypair: userData.encryptedVrfKeypair,\n version: userData.version || 2,\n serverEncryptedVrfKeypair: userData.serverEncryptedVrfKeypair,\n });\n }\n\n const updatedUser: ClientUserData = {\n ...user,\n clientNearPublicKey: userData.clientNearPublicKey,\n passkeyCredential: userData.passkeyCredential,\n encryptedVrfKeypair: userData.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: userData.serverEncryptedVrfKeypair ?? user.serverEncryptedVrfKeypair,\n version: userData.version ?? user.version,\n lastUpdated: userData.lastUpdated ?? Date.now(),\n };\n\n await this.storeUser(updatedUser);\n this.emitEvent({\n type: 'user-updated',\n accountId,\n data: { updatedUser }\n });\n }\n\n async getAllUsers(): Promise<ClientUserData[]> {\n const db = await this.getDB();\n return db.getAll(DB_CONFIG.userStore);\n }\n\n async deleteUser(nearAccountId: AccountId): Promise<void> {\n const db = await this.getDB();\n await db.delete(DB_CONFIG.userStore, nearAccountId);\n // Also clean up related authenticators\n await this.clearAuthenticatorsForUser(nearAccountId);\n }\n\n async clearAllUsers(): Promise<void> {\n const db = await this.getDB();\n await db.clear(DB_CONFIG.userStore);\n }\n\n async clearAllAppState(): Promise<void> {\n const db = await this.getDB();\n await db.clear(DB_CONFIG.appStateStore);\n }\n\n /**\n * Store authenticator data for a user\n */\n async storeAuthenticator(authenticatorData: ClientAuthenticatorData): Promise<void> {\n const db = await this.getDB();\n await db.put(DB_CONFIG.authenticatorStore, authenticatorData);\n }\n\n /**\n * Get all authenticators for a user (optionally for a specific device)\n */\n async getAuthenticatorsByUser(nearAccountId: AccountId): Promise<ClientAuthenticatorData[]> {\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readonly');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n const accountId = toAccountId(nearAccountId);\n\n // Get all authenticators for this account across all devices\n const index = store.index('nearAccountId');\n return await index.getAll(accountId);\n }\n\n /**\n * Get a specific authenticator by credential ID\n */\n async getAuthenticatorByCredentialId(\n nearAccountId: AccountId,\n credentialId: string\n ): Promise<ClientAuthenticatorData | null> {\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readonly');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n const accountId = toAccountId(nearAccountId);\n\n // Primary key is [nearAccountId, deviceNumber, credentialId], so we cannot\n // look up by [nearAccountId, credentialId] directly. Use the nearAccountId\n // index and filter by credentialId.\n const index = store.index('nearAccountId');\n const all = await index.getAll(accountId);\n const match = all.find((auth: any) => auth.credentialId === credentialId) || null;\n return match;\n }\n\n /**\n * Clear all authenticators for a user\n */\n async clearAuthenticatorsForUser(nearAccountId: AccountId): Promise<void> {\n const authenticators = await this.getAuthenticatorsByUser(nearAccountId);\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readwrite');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n\n for (const auth of authenticators) {\n // Composite PK is [nearAccountId, deviceNumber, credentialId]\n await store.delete([nearAccountId, auth.deviceNumber, auth.credentialId]);\n }\n }\n\n /**\n * Sync authenticators from contract data\n */\n async syncAuthenticatorsFromContract(\n nearAccountId: AccountId,\n contractAuthenticators: Array<{\n credentialId: string;\n credentialPublicKey: Uint8Array;\n transports?: string[];\n name?: string;\n registered: string;\n vrfPublicKey: string;\n deviceNumber?: number; // Device number from contract\n }>\n ): Promise<void> {\n // Clear existing cache for this user\n await this.clearAuthenticatorsForUser(nearAccountId);\n\n // Add all contract authenticators to cache\n const syncedAt = new Date().toISOString();\n for (const auth of contractAuthenticators) {\n // Fix transport processing: filter out undefined values and provide fallback\n const rawTransports = auth.transports || [];\n const validTransports = rawTransports.filter((transport: any) =>\n transport !== undefined && transport !== null && typeof transport === 'string'\n );\n\n // If no valid transports, default to 'internal' for platform authenticators\n const transports = validTransports.length > 0 ? validTransports : ['internal'];\n\n const clientAuth: ClientAuthenticatorData = {\n credentialId: auth.credentialId,\n credentialPublicKey: auth.credentialPublicKey,\n transports,\n name: auth.name,\n nearAccountId: toAccountId(nearAccountId),\n deviceNumber: auth.deviceNumber || 1, // Default to device 1 (1-indexed)\n registered: auth.registered,\n syncedAt: syncedAt,\n vrfPublicKey: auth.vrfPublicKey,\n };\n await this.storeAuthenticator(clientAuth);\n }\n }\n\n // === ATOMIC OPERATIONS AND ROLLBACK METHODS ===\n\n /**\n * Delete all authenticators for a user\n */\n async deleteAllAuthenticatorsForUser(nearAccountId: AccountId): Promise<void> {\n const authenticators = await this.getAuthenticatorsByUser(nearAccountId);\n\n if (authenticators.length === 0) {\n console.warn(`No authenticators found for user ${nearAccountId}`);\n return;\n }\n\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readwrite');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n\n for (const auth of authenticators) {\n // Composite PK is [nearAccountId, deviceNumber, credentialId]\n await store.delete([nearAccountId, auth.deviceNumber, auth.credentialId]);\n }\n\n console.debug(`Deleted ${authenticators.length} authenticators for user ${nearAccountId}`);\n }\n\n /**\n * Get user's confirmation config from IndexedDB\n * @param nearAccountId - The user's account ID\n * @returns ConfirmationConfig or undefined\n */\n async getConfirmationConfig(nearAccountId: AccountId): Promise<ConfirmationConfig> {\n const user = await this.getUser(nearAccountId);\n return user?.preferences?.confirmationConfig || DEFAULT_CONFIRMATION_CONFIG;\n }\n\n /**\n * Get user's theme preference from IndexedDB\n * @param nearAccountId - The user's account ID\n * @returns 'dark' | 'light' | null\n */\n async getTheme(nearAccountId: AccountId): Promise<'dark' | 'light' | null> {\n const user = await this.getUser(nearAccountId);\n return user?.preferences?.confirmationConfig.theme || null;\n }\n\n /**\n * Set user's theme preference in IndexedDB\n * @param nearAccountId - The user's account ID\n * @param theme - The theme to set ('dark' | 'light')\n */\n async setTheme(nearAccountId: AccountId, theme: 'dark' | 'light'): Promise<void> {\n const existingConfig = await this.getConfirmationConfig(nearAccountId);\n const confirmationConfig = { ...existingConfig, theme };\n await this.updatePreferences(nearAccountId, { confirmationConfig });\n }\n\n /**\n * Get user's theme with fallback to 'dark'\n * @param nearAccountId - The user's account ID\n * @returns 'dark' | 'light'\n */\n async getThemeOrDefault(nearAccountId: AccountId): Promise<'dark' | 'light'> {\n const theme = await this.getTheme(nearAccountId);\n return theme || 'dark';\n }\n\n /**\n * Get user's signer mode preference from IndexedDB\n */\n async getSignerMode(nearAccountId: AccountId): Promise<SignerMode> {\n const user = await this.getUser(nearAccountId);\n const raw = user?.preferences?.signerMode as SignerMode | SignerMode['mode'] | null | undefined;\n return coerceSignerMode(raw, DEFAULT_SIGNING_MODE);\n }\n\n /**\n * Set user's signer mode preference in IndexedDB\n */\n async setSignerMode(nearAccountId: AccountId, signerMode: SignerMode | SignerMode['mode']): Promise<void> {\n const next = coerceSignerMode(signerMode, DEFAULT_SIGNING_MODE);\n await this.updatePreferences(nearAccountId, { signerMode: next });\n }\n\n /**\n * Toggle between dark and light theme for a user\n * @param nearAccountId - The user's account ID\n * @returns The new theme that was set\n */\n async toggleTheme(nearAccountId: AccountId): Promise<'dark' | 'light'> {\n const currentTheme = await this.getThemeOrDefault(nearAccountId);\n const newTheme = currentTheme === 'dark' ? 'light' : 'dark';\n await this.setTheme(nearAccountId, newTheme);\n return newTheme;\n }\n\n // === DERIVED ADDRESS METHODS ===\n\n /**\n * Store a derived address for a given NEAR account + contract + path\n */\n async setDerivedAddress(nearAccountId: AccountId, args: { contractId: string; path: string; address: string }): Promise<void> {\n if (!nearAccountId || !args?.contractId || !args?.path || !args?.address) return;\n const validation = this.validateNearAccountId(nearAccountId);\n if (!validation.valid) return;\n const rec: DerivedAddressRecord = {\n nearAccountId: toAccountId(nearAccountId),\n contractId: String(args.contractId),\n path: String(args.path),\n address: String(args.address),\n updatedAt: Date.now(),\n };\n const db = await this.getDB();\n await db.put(DB_CONFIG.derivedAddressStore, rec);\n }\n\n /**\n * Fetch a derived address record; returns null if not found\n */\n async getDerivedAddressRecord(nearAccountId: AccountId, args: { contractId: string; path: string }): Promise<DerivedAddressRecord | null> {\n if (!nearAccountId || !args?.contractId || !args?.path) return null;\n const db = await this.getDB();\n const rec = await db.get(DB_CONFIG.derivedAddressStore, [toAccountId(nearAccountId), String(args.contractId), String(args.path)]);\n return (rec as DerivedAddressRecord) || null;\n }\n\n /**\n * Get only the derived address string; returns null if not set\n */\n async getDerivedAddress(nearAccountId: AccountId, args: { contractId: string; path: string }): Promise<string | null> {\n const rec = await this.getDerivedAddressRecord(nearAccountId, args);\n return rec?.address || null;\n }\n\n // === RECOVERY EMAIL METHODS ===\n\n /**\n * Upsert recovery email records for an account.\n * Merges by hashHex, preferring the most recent email.\n */\n async upsertRecoveryEmails(\n nearAccountId: AccountId,\n entries: Array<{ hashHex: string; email: string }>\n ): Promise<void> {\n if (!nearAccountId || !entries?.length) return;\n const validation = this.validateNearAccountId(nearAccountId);\n if (!validation.valid) return;\n\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n const now = Date.now();\n\n for (const entry of entries) {\n const hashHex = String(entry?.hashHex || '').trim();\n const email = String(entry?.email || '').trim();\n if (!hashHex || !email) continue;\n\n const rec: RecoveryEmailRecord = {\n nearAccountId: accountId,\n hashHex,\n email,\n addedAt: now,\n };\n await db.put(DB_CONFIG.recoveryEmailStore, rec);\n }\n }\n\n /**\n * Fetch all recovery email records for an account.\n */\n async getRecoveryEmails(nearAccountId: AccountId): Promise<RecoveryEmailRecord[]> {\n if (!nearAccountId) return [];\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n const tx = db.transaction(DB_CONFIG.recoveryEmailStore, 'readonly');\n const store = tx.objectStore(DB_CONFIG.recoveryEmailStore);\n const index = store.index('nearAccountId');\n const result = await index.getAll(accountId);\n return (result as RecoveryEmailRecord[]) || [];\n }\n\n /**\n * Atomic operation wrapper for multiple IndexedDB operations\n * Either all operations succeed or all are rolled back\n */\n async atomicOperation<T>(operation: (db: IDBPDatabase) => Promise<T>): Promise<T> {\n const db = await this.getDB();\n try {\n const result = await operation(db);\n return result;\n } catch (error) {\n console.error('Atomic operation failed:', error);\n throw error;\n }\n }\n\n /**\n * Complete rollback of user registration data\n * Deletes user, authenticators, and WebAuthn data atomically\n */\n async rollbackUserRegistration(nearAccountId: AccountId): Promise<void> {\n console.debug(`Rolling back registration data for ${nearAccountId}`);\n\n await this.atomicOperation(async (db) => {\n // Delete all authenticators for this user\n await this.deleteAllAuthenticatorsForUser(nearAccountId);\n\n // Delete user record\n await db.delete(DB_CONFIG.userStore, nearAccountId);\n\n // Clear from app state if this was the last user\n const lastUserAccount = await this.getAppState<string>('lastUserAccountId');\n if (lastUserAccount === nearAccountId) {\n await this.setAppState('lastUserAccountId', null);\n }\n\n console.debug(`Rolled back all registration data for ${nearAccountId}`);\n return true;\n });\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"passkeyClientDB.js","names":["DB_CONFIG: PasskeyClientDBConfig","err: any","entry: AppStateEntry<T>","userData: ClientUserData","lastUserState: LastUserAccountIdState","fixed: ClientUserData","updatedUser: ClientUserData","clientAuth: ClientAuthenticatorData","rec: DerivedAddressRecord","rec: RecoveryEmailRecord"],"sources":["../../../../../../src/core/IndexedDBManager/passkeyClientDB.ts"],"sourcesContent":["import { openDB, type IDBPDatabase } from 'idb';\nimport { type ValidationResult, validateNearAccountId } from '../../utils/validation';\nimport type { AccountId } from '../types/accountIds';\nimport { toAccountId } from '../types/accountIds';\nimport {\n ConfirmationConfig,\n DEFAULT_CONFIRMATION_CONFIG,\n type SignerMode,\n DEFAULT_SIGNING_MODE,\n coerceSignerMode,\n} from '../types/signer-worker'\n\n\nexport interface ClientUserData {\n // Primary key - now uses AccountId + deviceNumber for unique identification\n nearAccountId: AccountId;\n deviceNumber: number; // Device number for multi-device support (1-indexed)\n version?: number;\n\n // User metadata\n registeredAt?: number;\n lastLogin?: number;\n lastUpdated?: number;\n\n // WebAuthn/Passkey data (merged from WebAuthnManager)\n clientNearPublicKey: string;\n passkeyCredential: {\n id: string;\n rawId: string;\n };\n\n // VRF credentials for stateless authentication\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: string;\n chacha20NonceB64u: string;\n };\n // Server-assisted auto-login (VRF key session): Shamir 3-pass fields\n // Stores relayer-blinded KEK and the VRF ciphertext; server never sees plaintext VRF or KEK\n serverEncryptedVrfKeypair?: {\n ciphertextVrfB64u: string;\n kek_s_b64u: string;\n // Metadata for proactive refresh\n serverKeyId: string;\n updatedAt?: number;\n };\n\n // User preferences\n preferences?: UserPreferences;\n}\n\nexport type StoreUserDataInput = Omit<ClientUserData, 'deviceNumber' | 'lastLogin' | 'registeredAt'>\n & {\n deviceNumber?: number;\n serverEncryptedVrfKeypair?: ClientUserData['serverEncryptedVrfKeypair'];\n version?: number;\n };\n\nexport type StoreWebAuthnUserDataInput = {\n nearAccountId: AccountId;\n deviceNumber: number;\n clientNearPublicKey: string;\n lastUpdated?: number;\n version?: number;\n passkeyCredential: ClientUserData['passkeyCredential'];\n encryptedVrfKeypair: ClientUserData['encryptedVrfKeypair'];\n serverEncryptedVrfKeypair?: ClientUserData['serverEncryptedVrfKeypair'];\n};\n\nexport interface UserPreferences {\n useRelayer: boolean;\n useNetwork: 'testnet' | 'mainnet';\n confirmationConfig: ConfirmationConfig;\n signerMode?: SignerMode;\n // User preferences can be extended here as needed\n}\n\n// Authenticator cache\nexport interface ClientAuthenticatorData {\n credentialId: string;\n credentialPublicKey: Uint8Array;\n transports?: string[]; // AuthenticatorTransport[]\n name?: string;\n nearAccountId: AccountId; // FK reference using AccountId\n deviceNumber: number; // Device number for this authenticator (1-indexed)\n registered: string; // ISO date string\n syncedAt: string; // When this cache entry was last synced with contract\n vrfPublicKey: string; // Base64-encoded VRF public key (1:1 relationship on client)\n}\n\ninterface AppStateEntry<T = unknown> {\n key: string;\n value: T;\n}\n\n// Internal helper: legacy user records may be missing deviceNumber.\ntype ClientUserDataWithOptionalDevice =\n | ClientUserData\n | (Omit<ClientUserData, 'deviceNumber'> & { deviceNumber?: number });\n\n// Special type for lastUserAccountId app state entry\nexport interface LastUserAccountIdState {\n accountId: AccountId;\n deviceNumber: number;\n}\n\ninterface PasskeyClientDBConfig {\n dbName: string;\n dbVersion: number;\n userStore: string;\n appStateStore: string;\n authenticatorStore: string;\n derivedAddressStore: string;\n recoveryEmailStore: string;\n}\n\n// === CONSTANTS ===\nconst DB_CONFIG: PasskeyClientDBConfig = {\n dbName: 'PasskeyClientDB',\n dbVersion: 15, // v15: add recoveryEmails store\n userStore: 'users',\n appStateStore: 'appState',\n authenticatorStore: 'authenticators',\n derivedAddressStore: 'derivedAddresses',\n recoveryEmailStore: 'recoveryEmails'\n} as const;\n\nexport interface IndexedDBEvent {\n type: 'user-updated' | 'preferences-updated' | 'user-deleted';\n accountId: AccountId;\n data?: Record<string, unknown>;\n}\n\n// Persisted mapping of derived (e.g., EVM) addresses tied to an account\n/**\n * Persisted mapping of derived (e.g., EVM/Solana/Zcash) addresses tied to an account.\n *\n * Notes on multi-chain support:\n * - The composite primary key is [nearAccountId, contractId, path]. To support\n * different chains and chain IDs, encode them in the `path` string, e.g.:\n * - EVM: `evm:<chainId>:<derivationPath>` → `evm:84532:ethereum-1`\n * - Solana: `solana:<derivationPath>`\n * - Zcash: `zcash:<derivationPath>`\n * - Additional descriptive fields like `namespace` and `chainRef` are optional metadata\n * and are not part of the key.\n */\nexport interface DerivedAddressRecord {\n nearAccountId: AccountId;\n contractId: string; // MPC/Derivation contract on NEAR\n path: string; // Composite path (may include namespace/chainId); see docs above\n address: string; // Derived address (e.g., 0x...)\n updatedAt: number;\n // Optional metadata (not used in the key)\n namespace?: string; // e.g., 'evm', 'solana', 'zcash'\n chainRef?: string; // e.g., chainId '84532' or a named network slug\n}\n\n/**\n * Persisted mapping of recovery email hashes to canonical email addresses for an account.\n *\n * Notes:\n * - Composite primary key is [nearAccountId, hashHex].\n * - `hashHex` is the 0x-prefixed hex encoding of the 32-byte hash:\n * SHA256(canonical_email || \"|\" || account_id)\n * - `email` is the canonical form: \"local@domain\", lowercased.\n */\nexport interface RecoveryEmailRecord {\n nearAccountId: AccountId;\n hashHex: string;\n email: string;\n addedAt: number;\n}\n\nexport class PasskeyClientDBManager {\n private config: PasskeyClientDBConfig;\n private db: IDBPDatabase | null = null;\n private disabled = false;\n private eventListeners: Set<(event: IndexedDBEvent) => void> = new Set();\n\n constructor(config: PasskeyClientDBConfig = DB_CONFIG) {\n this.config = config;\n }\n\n getDbName(): string {\n return this.config.dbName;\n }\n\n setDbName(dbName: string): void {\n const next = String(dbName || '').trim();\n if (!next || next === this.config.dbName) return;\n try { (this.db as any)?.close?.(); } catch {}\n this.db = null;\n this.config = { ...this.config, dbName: next };\n }\n\n isDisabled(): boolean {\n return this.disabled;\n }\n\n setDisabled(disabled: boolean): void {\n const next = !!disabled;\n if (next === this.disabled) return;\n this.disabled = next;\n if (next) {\n try { (this.db as any)?.close?.(); } catch {}\n this.db = null;\n }\n }\n\n // === EVENT SYSTEM ===\n\n onChange(listener: (event: IndexedDBEvent) => void): () => void {\n this.eventListeners.add(listener);\n return () => {\n this.eventListeners.delete(listener);\n };\n }\n\n private emitEvent(event: IndexedDBEvent): void {\n this.eventListeners.forEach(listener => {\n try {\n listener(event);\n } catch (error) {\n console.warn('[IndexedDBManager]: Error in event listener:', error);\n }\n });\n }\n\n private async getDB(): Promise<IDBPDatabase> {\n if (this.disabled) {\n throw new Error('[PasskeyClientDBManager] IndexedDB is disabled in this environment.');\n }\n if (this.db) {\n return this.db;\n }\n\n try {\n this.db = await openDB(this.config.dbName, this.config.dbVersion, {\n upgrade: (db, oldVersion, _newVersion, _transaction): void => {\n // Create stores if they don't exist\n if (!db.objectStoreNames.contains(DB_CONFIG.userStore)) {\n // Users table: composite key of [nearAccountId, deviceNumber]\n const userStore = db.createObjectStore(DB_CONFIG.userStore, { keyPath: ['nearAccountId', 'deviceNumber'] });\n userStore.createIndex('nearAccountId', 'nearAccountId', { unique: false });\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.appStateStore)) {\n db.createObjectStore(DB_CONFIG.appStateStore, { keyPath: 'key' });\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.authenticatorStore)) {\n // Authenticators table: composite key of [nearAccountId, deviceNumber, credentialId]\n const authStore = db.createObjectStore(DB_CONFIG.authenticatorStore, { keyPath: ['nearAccountId', 'deviceNumber', 'credentialId'] });\n authStore.createIndex('nearAccountId', 'nearAccountId', { unique: false });\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.derivedAddressStore)) {\n // Derived addresses: composite key of [nearAccountId, contractId, path]\n const dStore = db.createObjectStore(DB_CONFIG.derivedAddressStore, { keyPath: ['nearAccountId', 'contractId', 'path'] });\n try { dStore.createIndex('nearAccountId', 'nearAccountId', { unique: false }); } catch {}\n }\n if (!db.objectStoreNames.contains(DB_CONFIG.recoveryEmailStore)) {\n // Recovery emails: composite key of [nearAccountId, hashHex]\n const rStore = db.createObjectStore(DB_CONFIG.recoveryEmailStore, { keyPath: ['nearAccountId', 'hashHex'] });\n try { rStore.createIndex('nearAccountId', 'nearAccountId', { unique: false }); } catch {}\n }\n },\n blocked() {\n console.warn('PasskeyClientDB connection is blocked.');\n },\n blocking() {\n console.warn('PasskeyClientDB connection is blocking another connection.');\n },\n terminated: () => {\n console.warn('PasskeyClientDB connection has been terminated.');\n this.db = null;\n },\n });\n\n // Post-open migrations (non-blocking)\n try { await this.runMigrationsIfNeeded(this.db); } catch {}\n\n } catch (err: any) {\n const msg = String(err?.message || '');\n if (err?.name === 'VersionError' || /less than the existing version/i.test(msg)) {\n // Mixed-version contexts (host/app) — open without version to adopt existing DB\n try {\n console.warn('PasskeyClientDB: opening existing DB without version due to VersionError');\n this.db = await openDB(this.config.dbName);\n } catch (e) {\n throw err;\n }\n } else {\n throw err;\n }\n }\n\n return this.db;\n }\n\n private async runMigrationsIfNeeded(_db: IDBPDatabase): Promise<void> {\n return;\n }\n\n // === APP STATE METHODS ===\n\n async getAppState<T = unknown>(key: string): Promise<T | undefined> {\n const db = await this.getDB();\n const result = await db.get(DB_CONFIG.appStateStore, key);\n return result?.value as T | undefined;\n }\n\n async setAppState<T = unknown>(key: string, value: T): Promise<void> {\n const db = await this.getDB();\n const entry: AppStateEntry<T> = { key, value };\n await db.put(DB_CONFIG.appStateStore, entry);\n }\n\n // === ACCOUNT ID VALIDATION AND UTILITIES ===\n\n /**\n * Validate that a NEAR account ID is in the expected format\n * Supports both <username>.<relayerAccountId> and <username>.testnet formats\n */\n validateNearAccountId(nearAccountId: AccountId): ValidationResult {\n return validateNearAccountId(nearAccountId);\n }\n\n /**\n * Extract username from NEAR account ID\n */\n extractUsername(nearAccountId: AccountId): string {\n const validation = validateNearAccountId(nearAccountId);\n if (!validation.valid) {\n throw new Error(`Invalid NEAR account ID: ${validation.error}`);\n }\n return nearAccountId.split('.')[0];\n }\n\n /**\n * Generate a NEAR account ID from a username and domain\n * @param username - The username to use for the account ID\n * @param domain - The domain to use for the account ID\n * @returns The generated NEAR account ID\n */\n generateNearAccountId(username: string, domain: string): string {\n const sanitizedName = username\n .toLowerCase()\n .replace(/[^a-z0-9_\\\\-]/g, '')\n .substring(0, 32);\n return `${sanitizedName}.${domain}`;\n }\n\n // === USER MANAGEMENT METHODS ===\n\n async getUser(nearAccountId: AccountId, deviceNumber?: number): Promise<ClientUserData | null> {\n if (!nearAccountId) return null;\n\n const validation = this.validateNearAccountId(nearAccountId);\n if (!validation.valid) {\n console.warn(`Invalid account ID format: ${nearAccountId}`);\n return null;\n }\n\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n\n if (typeof deviceNumber === 'number') {\n const rec = await db.get(DB_CONFIG.userStore, [accountId, deviceNumber]);\n if (!rec) return null;\n return await this.normalizeUserDeviceNumber(rec as ClientUserDataWithOptionalDevice, deviceNumber);\n }\n\n const index = db.transaction(DB_CONFIG.userStore).store.index('nearAccountId');\n const results = await index.getAll(accountId);\n if (results.length === 0) {\n return null;\n }\n\n if (results.length > 1) {\n console.warn(\n `Multiple passkeys found for account ${accountId}, deviceNumber not provided; ` +\n 'defaulting to last logged-in user.'\n );\n console.log('defaulting to last used user deviceNumber');\n const lastUserState = await this.getAppState<LastUserAccountIdState>('lastUserAccountId').catch(() => null);\n if (lastUserState && toAccountId(lastUserState.accountId) === accountId) {\n const keyed = await db.get(DB_CONFIG.userStore, [accountId, lastUserState.deviceNumber]);\n if (keyed) {\n return await this.normalizeUserDeviceNumber(\n keyed as ClientUserDataWithOptionalDevice,\n lastUserState.deviceNumber\n );\n }\n }\n }\n\n const first = results[0] as ClientUserDataWithOptionalDevice;\n if (!first) return null;\n return await this.normalizeUserDeviceNumber(first, 1);\n }\n\n /**\n * Get the current/last user\n * This is maintained via app state and updated whenever a user is stored or updated\n */\n async getLastUser(): Promise<ClientUserData | null> {\n const lastUserState = await this.getAppState<LastUserAccountIdState>('lastUserAccountId');\n if (!lastUserState) return null;\n const db = await this.getDB();\n const accountId = toAccountId(lastUserState.accountId);\n // Prefer exact device match using composite primary key\n const record = await db.get(DB_CONFIG.userStore, [accountId, lastUserState.deviceNumber]);\n if (record) return record as ClientUserData;\n // Fallback: return any user for account\n return this.getUser(accountId);\n }\n\n /** Get user record by composite key (nearAccountId, deviceNumber) */\n async getUserByDevice(nearAccountId: AccountId, deviceNumber: number): Promise<ClientUserData | null> {\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n const rec = await db.get(DB_CONFIG.userStore, [accountId, deviceNumber]);\n return rec as ClientUserData || null;\n }\n\n /**\n * Get the most recently updated user record for a given account.\n * Useful when deviceNumber is unknown but we need the freshest key for the account.\n */\n /**\n * Get the most recently updated user record for a given account.\n * Useful when deviceNumber is unknown but we need the freshest key for the account.\n */\n async getLastDBUpdatedUser(nearAccountId: AccountId): Promise<ClientUserData | null> {\n const db = await this.getDB();\n try {\n const idx = db.transaction(DB_CONFIG.userStore).store.index('nearAccountId');\n const all = await idx.getAll(toAccountId(nearAccountId));\n if (Array.isArray(all) && all.length > 0) {\n const latest = (all as ClientUserData[]).reduce((a, b) =>\n (a.lastUpdated ?? 0) >= (b.lastUpdated ?? 0) ? a : b\n );\n return latest;\n }\n } catch {\n // fall through\n }\n return null;\n }\n\n async hasPasskeyCredential(nearAccountId: AccountId): Promise<boolean> {\n const authenticators = await this.getAuthenticatorsByUser(nearAccountId);\n return !!authenticators[0]?.credentialId;\n }\n\n /**\n * Ensure the current passkey selection is aligned with the last logged-in device.\n *\n * - When multiple authenticators exist for an account and no deviceNumber is specified,\n * this helper prefers authenticators whose deviceNumber matches the last logged-in user.\n * - Optionally validates that a selected credential (by rawId) also matches the last-user device.\n *\n * @param nearAccountId - Account ID for which the operation is being performed\n * @param authenticators - All authenticators stored for the account\n * @param selectedCredentialRawId - Optional rawId of the credential chosen by WebAuthn\n * @returns filtered authenticators for allowCredentials, plus optional wrongPasskeyError\n */\n async ensureCurrentPasskey(\n nearAccountId: AccountId,\n authenticators: ClientAuthenticatorData[],\n selectedCredentialRawId?: string,\n ): Promise<{\n authenticatorsForPrompt: ClientAuthenticatorData[];\n wrongPasskeyError?: string;\n }> {\n if (authenticators.length <= 1) {\n return { authenticatorsForPrompt: authenticators };\n }\n\n const accountIdNormalized = toAccountId(nearAccountId);\n const lastUser = await this.getLastUser().catch(() => null);\n if (!lastUser || lastUser.nearAccountId !== accountIdNormalized) {\n return { authenticatorsForPrompt: authenticators };\n }\n\n const expectedDeviceNumber = lastUser.deviceNumber;\n const byDeviceNumber = authenticators.filter(a => a.deviceNumber === expectedDeviceNumber);\n\n // Prefer the credentialId for the last-user deviceNumber; use the stored last-user rawId\n // only when it matches an authenticator for that device (or when we have no device match).\n let expectedCredentialId = lastUser.passkeyCredential.rawId;\n if (byDeviceNumber.length > 0 && !byDeviceNumber.some(a => a.credentialId === expectedCredentialId)) {\n expectedCredentialId = byDeviceNumber[0].credentialId;\n }\n\n // Preference: restrict allowCredentials to the last-user credentialId.\n // Fallback: if the local authenticator cache is missing that entry, prefer the last-user deviceNumber.\n const byCredentialId = authenticators.filter(a => a.credentialId === expectedCredentialId);\n const authenticatorsForPrompt =\n byCredentialId.length > 0\n ? byCredentialId\n : (byDeviceNumber.length > 0 ? byDeviceNumber : authenticators);\n\n const wrongPasskeyError =\n selectedCredentialRawId && selectedCredentialRawId !== expectedCredentialId\n ? (\n `You have multiple passkeys (deviceNumbers) for account ${accountIdNormalized}, ` +\n 'but used a different passkey than the most recently logged-in one. Please use the passkey for the most recently logged-in device.'\n )\n : undefined;\n\n return { authenticatorsForPrompt, wrongPasskeyError };\n }\n\n /**\n * Register a new user with the given NEAR account ID\n * @param nearAccountId - Full NEAR account ID (e.g., \"username.testnet\" or \"username.relayer.testnet\")\n * @param additionalData - Additional user data to store\n */\n async registerUser(storeUserData: StoreUserDataInput): Promise<ClientUserData> {\n\n const validation = this.validateNearAccountId(storeUserData.nearAccountId);\n if (!validation.valid) {\n throw new Error(`Cannot register user with invalid account ID: ${validation.error}`);\n }\n\n const now = Date.now();\n\n const userData: ClientUserData = {\n nearAccountId: toAccountId(storeUserData.nearAccountId),\n deviceNumber: storeUserData.deviceNumber || 1, // Default to device 1 (1-indexed)\n version: storeUserData.version || 2,\n registeredAt: now,\n lastLogin: now,\n lastUpdated: now,\n clientNearPublicKey: storeUserData.clientNearPublicKey,\n passkeyCredential: storeUserData.passkeyCredential,\n preferences: {\n useRelayer: false,\n useNetwork: 'testnet',\n confirmationConfig: DEFAULT_CONFIRMATION_CONFIG,\n // Default preferences can be set here\n },\n encryptedVrfKeypair: storeUserData.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: storeUserData.serverEncryptedVrfKeypair,\n };\n\n await this.storeUser(userData);\n return userData;\n }\n\n async updateUser(nearAccountId: AccountId, updates: Partial<ClientUserData>, deviceNumber?: number): Promise<void> {\n const user = await this.getUser(nearAccountId, deviceNumber);\n if (user) {\n const updatedUser = {\n ...user,\n ...updates,\n lastUpdated: Date.now()\n };\n await this.storeUser(updatedUser); // This will update the app state lastUserAccountId\n\n // Emit event for user updates\n this.emitEvent({\n type: 'user-updated',\n accountId: nearAccountId,\n data: { updates, updatedUser }\n });\n }\n }\n\n async updateLastLogin(nearAccountId: AccountId): Promise<void> {\n await this.updateUser(nearAccountId, { lastLogin: Date.now() });\n }\n\n /**\n * Set the last logged-in user\n * @param nearAccountId - The account ID of the user\n * @param deviceNumber - The device number (defaults to 1)\n */\n async setLastUser(nearAccountId: AccountId, deviceNumber: number = 1): Promise<void> {\n const lastUserState: LastUserAccountIdState = {\n accountId: nearAccountId,\n deviceNumber,\n };\n await this.setAppState('lastUserAccountId', lastUserState);\n }\n\n async updatePreferences(\n nearAccountId: AccountId,\n preferences: Partial<UserPreferences>\n ): Promise<void> {\n const user = await this.getUser(nearAccountId);\n if (user) {\n const updatedPreferences = {\n ...user.preferences,\n ...preferences\n } as UserPreferences;\n await this.updateUser(nearAccountId, { preferences: updatedPreferences });\n\n // Emit event for preference changes\n this.emitEvent({\n type: 'preferences-updated',\n accountId: nearAccountId,\n data: { preferences: updatedPreferences }\n });\n }\n }\n\n private async normalizeUserDeviceNumber(\n user: ClientUserDataWithOptionalDevice,\n defaultDeviceNumber: number\n ): Promise<ClientUserData> {\n const hasValidDevice =\n typeof user.deviceNumber === 'number' && Number.isFinite(user.deviceNumber);\n if (hasValidDevice) {\n return user as ClientUserData;\n }\n\n const deviceNumber = defaultDeviceNumber;\n const fixed: ClientUserData = {\n ...(user as Omit<ClientUserData, 'deviceNumber'>),\n deviceNumber,\n };\n await this.storeUser(fixed);\n return fixed;\n }\n\n private async storeUser(userData: ClientUserData): Promise<void> {\n const validation = this.validateNearAccountId(userData.nearAccountId);\n if (!validation.valid) {\n throw new Error(`Cannot store user with invalid account ID: ${validation.error}`);\n }\n\n const db = await this.getDB();\n await db.put(DB_CONFIG.userStore, userData);\n\n // Update lastUserAccountId with new format including device info\n const lastUserState: LastUserAccountIdState = {\n accountId: userData.nearAccountId,\n deviceNumber: userData.deviceNumber,\n };\n\n await this.setAppState('lastUserAccountId', lastUserState);\n }\n\n /**\n * Store WebAuthn user data (compatibility with WebAuthnManager)\n * @param userData - User data with nearAccountId as primary identifier\n */\n async storeWebAuthnUserData(userData: StoreWebAuthnUserDataInput): Promise<void> {\n const validation = this.validateNearAccountId(userData.nearAccountId);\n if (!validation.valid) {\n throw new Error(`Cannot store WebAuthn data for invalid account ID: ${validation.error}`);\n }\n\n const accountId = toAccountId(userData.nearAccountId);\n const deviceNumber = userData.deviceNumber;\n let user = await this.getUser(accountId, deviceNumber);\n\n if (!user) {\n user = await this.registerUser({\n nearAccountId: accountId,\n deviceNumber,\n clientNearPublicKey: userData.clientNearPublicKey,\n passkeyCredential: userData.passkeyCredential,\n encryptedVrfKeypair: userData.encryptedVrfKeypair,\n version: userData.version || 2,\n serverEncryptedVrfKeypair: userData.serverEncryptedVrfKeypair,\n });\n }\n\n const updatedUser: ClientUserData = {\n ...user,\n clientNearPublicKey: userData.clientNearPublicKey,\n passkeyCredential: userData.passkeyCredential,\n encryptedVrfKeypair: userData.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: userData.serverEncryptedVrfKeypair ?? user.serverEncryptedVrfKeypair,\n version: userData.version ?? user.version,\n lastUpdated: userData.lastUpdated ?? Date.now(),\n };\n\n await this.storeUser(updatedUser);\n this.emitEvent({\n type: 'user-updated',\n accountId,\n data: { updatedUser }\n });\n }\n\n async getAllUsers(): Promise<ClientUserData[]> {\n const db = await this.getDB();\n return db.getAll(DB_CONFIG.userStore);\n }\n\n async deleteUser(nearAccountId: AccountId): Promise<void> {\n const db = await this.getDB();\n await db.delete(DB_CONFIG.userStore, nearAccountId);\n // Also clean up related authenticators\n await this.clearAuthenticatorsForUser(nearAccountId);\n }\n\n async clearAllUsers(): Promise<void> {\n const db = await this.getDB();\n await db.clear(DB_CONFIG.userStore);\n }\n\n async clearAllAppState(): Promise<void> {\n const db = await this.getDB();\n await db.clear(DB_CONFIG.appStateStore);\n }\n\n /**\n * Store authenticator data for a user\n */\n async storeAuthenticator(authenticatorData: ClientAuthenticatorData): Promise<void> {\n const db = await this.getDB();\n await db.put(DB_CONFIG.authenticatorStore, authenticatorData);\n }\n\n /**\n * Get all authenticators for a user (optionally for a specific device)\n */\n async getAuthenticatorsByUser(nearAccountId: AccountId): Promise<ClientAuthenticatorData[]> {\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readonly');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n const accountId = toAccountId(nearAccountId);\n\n // Get all authenticators for this account across all devices\n const index = store.index('nearAccountId');\n return await index.getAll(accountId);\n }\n\n /**\n * Get a specific authenticator by credential ID\n */\n async getAuthenticatorByCredentialId(\n nearAccountId: AccountId,\n credentialId: string\n ): Promise<ClientAuthenticatorData | null> {\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readonly');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n const accountId = toAccountId(nearAccountId);\n\n // Primary key is [nearAccountId, deviceNumber, credentialId], so we cannot\n // look up by [nearAccountId, credentialId] directly. Use the nearAccountId\n // index and filter by credentialId.\n const index = store.index('nearAccountId');\n const all = await index.getAll(accountId);\n const match = all.find((auth: any) => auth.credentialId === credentialId) || null;\n return match;\n }\n\n /**\n * Clear all authenticators for a user\n */\n async clearAuthenticatorsForUser(nearAccountId: AccountId): Promise<void> {\n const authenticators = await this.getAuthenticatorsByUser(nearAccountId);\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readwrite');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n\n for (const auth of authenticators) {\n // Composite PK is [nearAccountId, deviceNumber, credentialId]\n await store.delete([nearAccountId, auth.deviceNumber, auth.credentialId]);\n }\n }\n\n /**\n * Sync authenticators from contract data\n */\n async syncAuthenticatorsFromContract(\n nearAccountId: AccountId,\n contractAuthenticators: Array<{\n credentialId: string;\n credentialPublicKey: Uint8Array;\n transports?: string[];\n name?: string;\n registered: string;\n vrfPublicKey: string;\n deviceNumber?: number; // Device number from contract\n }>\n ): Promise<void> {\n // Clear existing cache for this user\n await this.clearAuthenticatorsForUser(nearAccountId);\n\n // Add all contract authenticators to cache\n const syncedAt = new Date().toISOString();\n for (const auth of contractAuthenticators) {\n // Fix transport processing: filter out undefined values and provide fallback\n const rawTransports = auth.transports || [];\n const validTransports = rawTransports.filter((transport: any) =>\n transport !== undefined && transport !== null && typeof transport === 'string'\n );\n\n // If no valid transports, default to 'internal' for platform authenticators\n const transports = validTransports.length > 0 ? validTransports : ['internal'];\n\n const clientAuth: ClientAuthenticatorData = {\n credentialId: auth.credentialId,\n credentialPublicKey: auth.credentialPublicKey,\n transports,\n name: auth.name,\n nearAccountId: toAccountId(nearAccountId),\n deviceNumber: auth.deviceNumber || 1, // Default to device 1 (1-indexed)\n registered: auth.registered,\n syncedAt: syncedAt,\n vrfPublicKey: auth.vrfPublicKey,\n };\n await this.storeAuthenticator(clientAuth);\n }\n }\n\n // === ATOMIC OPERATIONS AND ROLLBACK METHODS ===\n\n /**\n * Delete all authenticators for a user\n */\n async deleteAllAuthenticatorsForUser(nearAccountId: AccountId): Promise<void> {\n const authenticators = await this.getAuthenticatorsByUser(nearAccountId);\n\n if (authenticators.length === 0) {\n console.warn(`No authenticators found for user ${nearAccountId}`);\n return;\n }\n\n const db = await this.getDB();\n const tx = db.transaction(DB_CONFIG.authenticatorStore, 'readwrite');\n const store = tx.objectStore(DB_CONFIG.authenticatorStore);\n\n for (const auth of authenticators) {\n // Composite PK is [nearAccountId, deviceNumber, credentialId]\n await store.delete([nearAccountId, auth.deviceNumber, auth.credentialId]);\n }\n\n console.debug(`Deleted ${authenticators.length} authenticators for user ${nearAccountId}`);\n }\n\n /**\n * Get user's confirmation config from IndexedDB\n * @param nearAccountId - The user's account ID\n * @returns ConfirmationConfig or undefined\n */\n async getConfirmationConfig(nearAccountId: AccountId): Promise<ConfirmationConfig> {\n const user = await this.getUser(nearAccountId);\n return user?.preferences?.confirmationConfig || DEFAULT_CONFIRMATION_CONFIG;\n }\n\n /**\n * Get user's theme preference from IndexedDB\n * @param nearAccountId - The user's account ID\n * @returns 'dark' | 'light' | null\n */\n async getTheme(nearAccountId: AccountId): Promise<'dark' | 'light' | null> {\n const user = await this.getUser(nearAccountId);\n return user?.preferences?.confirmationConfig.theme || null;\n }\n\n /**\n * Set user's theme preference in IndexedDB\n * @param nearAccountId - The user's account ID\n * @param theme - The theme to set ('dark' | 'light')\n */\n async setTheme(nearAccountId: AccountId, theme: 'dark' | 'light'): Promise<void> {\n const existingConfig = await this.getConfirmationConfig(nearAccountId);\n const confirmationConfig = { ...existingConfig, theme };\n await this.updatePreferences(nearAccountId, { confirmationConfig });\n }\n\n /**\n * Get user's theme with fallback to 'dark'\n * @param nearAccountId - The user's account ID\n * @returns 'dark' | 'light'\n */\n async getThemeOrDefault(nearAccountId: AccountId): Promise<'dark' | 'light'> {\n const theme = await this.getTheme(nearAccountId);\n return theme || 'dark';\n }\n\n /**\n * Get user's signer mode preference from IndexedDB\n */\n async getSignerMode(nearAccountId: AccountId): Promise<SignerMode> {\n const user = await this.getUser(nearAccountId);\n const raw = user?.preferences?.signerMode as SignerMode | SignerMode['mode'] | null | undefined;\n return coerceSignerMode(raw, DEFAULT_SIGNING_MODE);\n }\n\n /**\n * Set user's signer mode preference in IndexedDB\n */\n async setSignerMode(nearAccountId: AccountId, signerMode: SignerMode | SignerMode['mode']): Promise<void> {\n const next = coerceSignerMode(signerMode, DEFAULT_SIGNING_MODE);\n await this.updatePreferences(nearAccountId, { signerMode: next });\n }\n\n /**\n * Toggle between dark and light theme for a user\n * @param nearAccountId - The user's account ID\n * @returns The new theme that was set\n */\n async toggleTheme(nearAccountId: AccountId): Promise<'dark' | 'light'> {\n const currentTheme = await this.getThemeOrDefault(nearAccountId);\n const newTheme = currentTheme === 'dark' ? 'light' : 'dark';\n await this.setTheme(nearAccountId, newTheme);\n return newTheme;\n }\n\n // === DERIVED ADDRESS METHODS ===\n\n /**\n * Store a derived address for a given NEAR account + contract + path\n */\n async setDerivedAddress(nearAccountId: AccountId, args: { contractId: string; path: string; address: string }): Promise<void> {\n if (!nearAccountId || !args?.contractId || !args?.path || !args?.address) return;\n const validation = this.validateNearAccountId(nearAccountId);\n if (!validation.valid) return;\n const rec: DerivedAddressRecord = {\n nearAccountId: toAccountId(nearAccountId),\n contractId: String(args.contractId),\n path: String(args.path),\n address: String(args.address),\n updatedAt: Date.now(),\n };\n const db = await this.getDB();\n await db.put(DB_CONFIG.derivedAddressStore, rec);\n }\n\n /**\n * Fetch a derived address record; returns null if not found\n */\n async getDerivedAddressRecord(nearAccountId: AccountId, args: { contractId: string; path: string }): Promise<DerivedAddressRecord | null> {\n if (!nearAccountId || !args?.contractId || !args?.path) return null;\n const db = await this.getDB();\n const rec = await db.get(DB_CONFIG.derivedAddressStore, [toAccountId(nearAccountId), String(args.contractId), String(args.path)]);\n return (rec as DerivedAddressRecord) || null;\n }\n\n /**\n * Get only the derived address string; returns null if not set\n */\n async getDerivedAddress(nearAccountId: AccountId, args: { contractId: string; path: string }): Promise<string | null> {\n const rec = await this.getDerivedAddressRecord(nearAccountId, args);\n return rec?.address || null;\n }\n\n // === RECOVERY EMAIL METHODS ===\n\n /**\n * Upsert recovery email records for an account.\n * Merges by hashHex, preferring the most recent email.\n */\n async upsertRecoveryEmails(\n nearAccountId: AccountId,\n entries: Array<{ hashHex: string; email: string }>\n ): Promise<void> {\n if (!nearAccountId || !entries?.length) return;\n const validation = this.validateNearAccountId(nearAccountId);\n if (!validation.valid) return;\n\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n const now = Date.now();\n\n for (const entry of entries) {\n const hashHex = String(entry?.hashHex || '').trim();\n const email = String(entry?.email || '').trim();\n if (!hashHex || !email) continue;\n\n const rec: RecoveryEmailRecord = {\n nearAccountId: accountId,\n hashHex,\n email,\n addedAt: now,\n };\n await db.put(DB_CONFIG.recoveryEmailStore, rec);\n }\n }\n\n /**\n * Fetch all recovery email records for an account.\n */\n async getRecoveryEmails(nearAccountId: AccountId): Promise<RecoveryEmailRecord[]> {\n if (!nearAccountId) return [];\n const db = await this.getDB();\n const accountId = toAccountId(nearAccountId);\n const tx = db.transaction(DB_CONFIG.recoveryEmailStore, 'readonly');\n const store = tx.objectStore(DB_CONFIG.recoveryEmailStore);\n const index = store.index('nearAccountId');\n const result = await index.getAll(accountId);\n return (result as RecoveryEmailRecord[]) || [];\n }\n\n /**\n * Atomic operation wrapper for multiple IndexedDB operations\n * Either all operations succeed or all are rolled back\n */\n async atomicOperation<T>(operation: (db: IDBPDatabase) => Promise<T>): Promise<T> {\n const db = await this.getDB();\n try {\n const result = await operation(db);\n return result;\n } catch (error) {\n console.error('Atomic operation failed:', error);\n throw error;\n }\n }\n\n /**\n * Complete rollback of user registration data\n * Deletes user, authenticators, and WebAuthn data atomically\n */\n async rollbackUserRegistration(nearAccountId: AccountId): Promise<void> {\n console.debug(`Rolling back registration data for ${nearAccountId}`);\n\n await this.atomicOperation(async (db) => {\n // Delete all authenticators for this user\n await this.deleteAllAuthenticatorsForUser(nearAccountId);\n\n // Delete user record\n await db.delete(DB_CONFIG.userStore, nearAccountId);\n\n // Clear from app state if this was the last user\n const lastUserAccount = await this.getAppState<string>('lastUserAccountId');\n if (lastUserAccount === nearAccountId) {\n await this.setAppState('lastUserAccountId', null);\n }\n\n console.debug(`Rolled back all registration data for ${nearAccountId}`);\n return true;\n });\n }\n}\n"],"mappings":"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"}
@@ -30,23 +30,24 @@ async function createAccountAndRegisterWithRelayServer(context, nearAccountId, p
30
30
  const serialized = isSerialized ? normalizeRegistrationCredential(credential) : serializeRegistrationCredential(credential);
31
31
  const serializedCredential = removePrfOutputGuard(serialized);
32
32
  if (!Array.isArray(serializedCredential?.response?.transports)) serializedCredential.response.transports = [];
33
- const intent_digest_32 = Array.from(base64UrlDecode(vrfChallenge.intentDigest || ""));
34
- if (intent_digest_32.length !== 32) throw new Error("Missing or invalid vrfChallenge.intentDigest (expected base64url-encoded 32 bytes)");
33
+ const intentDigestB64u = String(vrfChallenge.intentDigest || "").trim();
34
+ const intentDigestBytes = base64UrlDecode(intentDigestB64u);
35
+ if (intentDigestBytes.length !== 32) throw new Error("Missing or invalid vrfChallenge.intentDigest (expected base64url-encoded 32 bytes)");
35
36
  const requestData = {
36
37
  new_account_id: nearAccountId,
37
38
  new_public_key: publicKey,
38
39
  device_number: 1,
39
40
  ...opts?.thresholdEd25519?.clientVerifyingShareB64u ? { threshold_ed25519: { client_verifying_share_b64u: opts.thresholdEd25519.clientVerifyingShareB64u } } : {},
40
41
  vrf_data: {
41
- vrf_input_data: Array.from(base64UrlDecode(vrfChallenge.vrfInput)),
42
- vrf_output: Array.from(base64UrlDecode(vrfChallenge.vrfOutput)),
43
- vrf_proof: Array.from(base64UrlDecode(vrfChallenge.vrfProof)),
44
- public_key: Array.from(base64UrlDecode(vrfChallenge.vrfPublicKey)),
42
+ vrf_input_data: vrfChallenge.vrfInput,
43
+ vrf_output: vrfChallenge.vrfOutput,
44
+ vrf_proof: vrfChallenge.vrfProof,
45
+ public_key: vrfChallenge.vrfPublicKey,
45
46
  user_id: vrfChallenge.userId,
46
47
  rp_id: vrfChallenge.rpId,
47
48
  block_height: Number(vrfChallenge.blockHeight),
48
- block_hash: Array.from(base64UrlDecode(vrfChallenge.blockHash)),
49
- intent_digest_32
49
+ block_hash: vrfChallenge.blockHash,
50
+ intent_digest_32: intentDigestB64u
50
51
  },
51
52
  webauthn_registration: serializedCredential,
52
53
  deterministic_vrf_public_key: Array.from(base64UrlDecode(deterministicVrfPublicKey)),