@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.
- package/README.md +2 -0
- package/dist/cjs/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/cjs/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/cjs/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/login.js +1 -1
- package/dist/cjs/core/TatchiPasskey/login.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/registration.js +107 -63
- package/dist/cjs/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/cjs/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/index.js +3 -3
- package/dist/cjs/core/WebAuthnManager/index.js.map +1 -1
- package/dist/cjs/core/defaultConfigs.js +3 -1
- package/dist/cjs/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/core/types/sdkSentEvents.js +3 -2
- package/dist/cjs/core/types/sdkSentEvents.js.map +1 -1
- package/dist/cjs/react/components/AccountMenuButton/TransactionSettingsSection.js +3 -3
- package/dist/cjs/react/components/AccountMenuButton/TransactionSettingsSection.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-CRlobBrN.css → PasskeyAuthMenu-D2eRb2-S.css} +3 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/PasskeyAuthMenu-D2eRb2-S.css.map +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/preload.js +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/preload.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/shell.js +52 -13
- package/dist/cjs/react/components/PasskeyAuthMenu/shell.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/skeleton.js +4 -2
- package/dist/cjs/react/components/PasskeyAuthMenu/skeleton.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +5 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -1
- package/dist/cjs/react/index.js +1 -1
- package/dist/cjs/react/src/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/cjs/react/src/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/cjs/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/login.js +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/login.js.map +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/registration.js +107 -63
- package/dist/cjs/react/src/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/cjs/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/cjs/react/src/core/WebAuthnManager/index.js +3 -3
- package/dist/cjs/react/src/core/WebAuthnManager/index.js.map +1 -1
- package/dist/cjs/react/src/core/defaultConfigs.js +3 -1
- package/dist/cjs/react/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/react/src/core/types/sdkSentEvents.js +3 -2
- package/dist/cjs/react/src/core/types/sdkSentEvents.js.map +1 -1
- package/dist/cjs/server/core/AuthService.js +49 -6
- package/dist/cjs/server/core/AuthService.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/esm/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/esm/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/login.js +1 -1
- package/dist/esm/core/TatchiPasskey/login.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/registration.js +107 -63
- package/dist/esm/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/esm/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/index.js +3 -3
- package/dist/esm/core/WebAuthnManager/index.js.map +1 -1
- package/dist/esm/core/defaultConfigs.js +3 -1
- package/dist/esm/core/defaultConfigs.js.map +1 -1
- package/dist/esm/core/types/sdkSentEvents.js +3 -2
- package/dist/esm/core/types/sdkSentEvents.js.map +1 -1
- package/dist/esm/react/components/AccountMenuButton/TransactionSettingsSection.js +3 -3
- package/dist/esm/react/components/AccountMenuButton/TransactionSettingsSection.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-D2VHZ04W.css → PasskeyAuthMenu-qTHAv58Z.css} +3 -1
- package/dist/esm/react/components/PasskeyAuthMenu/PasskeyAuthMenu-qTHAv58Z.css.map +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/preload.js +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/preload.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/shell.js +52 -13
- package/dist/esm/react/components/PasskeyAuthMenu/shell.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/skeleton.js +4 -2
- package/dist/esm/react/components/PasskeyAuthMenu/skeleton.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +5 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -1
- package/dist/esm/react/index.js +1 -1
- package/dist/esm/react/src/core/IndexedDBManager/passkeyClientDB.js +2 -2
- package/dist/esm/react/src/core/IndexedDBManager/passkeyClientDB.js.map +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js +9 -8
- package/dist/esm/react/src/core/TatchiPasskey/faucets/createAccountRelayServer.js.map +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/login.js +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/login.js.map +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/registration.js +107 -63
- package/dist/esm/react/src/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js +1 -10
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js +58 -67
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js +74 -75
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js +17 -7
- package/dist/esm/react/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.js.map +1 -1
- package/dist/esm/react/src/core/WebAuthnManager/index.js +3 -3
- package/dist/esm/react/src/core/WebAuthnManager/index.js.map +1 -1
- package/dist/esm/react/src/core/defaultConfigs.js +3 -1
- package/dist/esm/react/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/react/src/core/types/sdkSentEvents.js +3 -2
- package/dist/esm/react/src/core/types/sdkSentEvents.js.map +1 -1
- package/dist/esm/react/styles/styles.css +2 -0
- package/dist/esm/sdk/{EmailRecovery-Dl8b4ONg.js → EmailRecovery-Y7rurd4B.js} +3 -3
- package/dist/esm/sdk/{EmailRecovery-v9oNO2Tc.js → EmailRecovery-lsjLWApQ.js} +1 -1
- package/dist/esm/sdk/{IndexedDBManager-B1cUvdyY.js → IndexedDBManager-CmdN7smS.js} +3 -3
- package/dist/esm/sdk/{createAdapters-Dv7ZJPf1.js → createAdapters-4c8mBiD5.js} +2 -11
- package/dist/esm/sdk/{createAdapters-Dv7ZJPf1.js.map → createAdapters-4c8mBiD5.js.map} +1 -1
- package/dist/esm/sdk/{createAdapters-1Hmc1vVC.js → createAdapters-DF32SIZa.js} +1 -10
- package/dist/esm/sdk/{defaultConfigs-BmCU1_qI.js → defaultConfigs-BQqiXif-.js} +3 -1
- package/dist/esm/sdk/{delegateAction-DdkvFFKA.js → delegateAction-Bq5zkOvn.js} +1 -1
- package/dist/esm/sdk/{emailRecovery-4J-g9tlY.js → emailRecovery-B1hbE_sM.js} +6 -6
- package/dist/esm/sdk/{getDeviceNumber-f8bfPB9U.js → getDeviceNumber-WiNzKx1x.js} +4 -2
- package/dist/esm/sdk/{getDeviceNumber-f8bfPB9U.js.map → getDeviceNumber-WiNzKx1x.js.map} +1 -1
- package/dist/esm/sdk/{linkDevice-C98klpcE.js → linkDevice-CRPf5aW2.js} +5 -5
- package/dist/esm/sdk/{localOnly-40zxrBMm.js → localOnly-COpDBMkm.js} +2 -2
- package/dist/esm/sdk/{localOnly-40zxrBMm.js.map → localOnly-COpDBMkm.js.map} +1 -1
- package/dist/esm/sdk/{localOnly-BZPBj14l.js → localOnly-DQQuqgjJ.js} +1 -1
- package/dist/esm/sdk/{login-DnROv3eA.js → login-DUIWZHp_.js} +4 -4
- package/dist/esm/sdk/offline-export-app.js +32 -21
- package/dist/esm/sdk/offline-export-app.js.map +1 -1
- package/dist/esm/sdk/{registration-BP9M3tE1.js → registration-BR2G9tz_.js} +59 -68
- package/dist/esm/sdk/{registration-MrAOC8Ub.js → registration-R70lvG_o.js} +60 -69
- package/dist/esm/sdk/registration-R70lvG_o.js.map +1 -0
- package/dist/esm/sdk/{relay-Dq9D7fhG.js → relay-BCEyWFew.js} +1 -1
- package/dist/esm/sdk/{router-BEGGuWaB.js → router-Cj2WexK-.js} +3 -3
- package/dist/esm/sdk/{rpcCalls-CMzj_Va_.js → rpcCalls-C1sp-Epo.js} +3 -3
- package/dist/esm/sdk/{rpcCalls-B44MZora.js → rpcCalls-VL4loDKP.js} +2 -2
- package/dist/esm/sdk/{scanDevice-Cp-r-Z2T.js → scanDevice-C0HcnZym.js} +5 -5
- package/dist/esm/sdk/{sdkSentEvents-CzAZBFjP.js → sdkSentEvents-BfkcI7EN.js} +3 -2
- package/dist/esm/sdk/{signNEP413-DsyWH_Jo.js → signNEP413-lj0swHsD.js} +1 -1
- package/dist/esm/sdk/{syncAccount-CqWCmBVb.js → syncAccount-DnQ9AstS.js} +5 -5
- package/dist/esm/sdk/{syncAccount-Dt5jJbEB.js → syncAccount-xh81Vppo.js} +3 -3
- package/dist/esm/sdk/{transactions-DAZrPW-6.js → transactions-Cg1TIUyK.js} +76 -77
- package/dist/esm/sdk/{transactions-CrjP8yPD.js → transactions-CxsklyCK.js} +77 -78
- package/dist/esm/sdk/transactions-CxsklyCK.js.map +1 -0
- package/dist/esm/sdk/wallet-iframe-host.js +160 -105
- package/dist/esm/server/core/AuthService.js +49 -6
- package/dist/esm/server/core/AuthService.js.map +1 -1
- package/dist/esm/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/types/src/__tests__/setup/bootstrap.d.ts.map +1 -1
- package/dist/types/src/core/IndexedDBManager/passkeyClientDB.d.ts +1 -1
- package/dist/types/src/core/IndexedDBManager/passkeyClientDB.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/faucets/createAccountRelayServer.d.ts +6 -6
- package/dist/types/src/core/TatchiPasskey/faucets/createAccountRelayServer.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/registration.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.d.ts +0 -5
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/adapters/session.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/registration.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/flows/transactions.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/VrfWorkerManager/confirmTxFlow/handleSecureConfirmRequest.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/index.d.ts +1 -1
- package/dist/types/src/core/WebAuthnManager/index.d.ts.map +1 -1
- package/dist/types/src/core/defaultConfigs.d.ts.map +1 -1
- package/dist/types/src/core/types/sdkSentEvents.d.ts +18 -7
- package/dist/types/src/core/types/sdkSentEvents.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/preload.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/shell.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/skeleton.d.ts +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/skeleton.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
- package/dist/types/src/server/core/AuthService.d.ts.map +1 -1
- package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
- package/package.json +4 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/PasskeyAuthMenu-CRlobBrN.css.map +0 -1
- package/dist/esm/react/components/PasskeyAuthMenu/PasskeyAuthMenu-D2VHZ04W.css.map +0 -1
- package/dist/esm/sdk/registration-MrAOC8Ub.js.map +0 -1
- package/dist/esm/sdk/transactions-CrjP8yPD.js.map +0 -1
|
@@ -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
|
|
34
|
-
|
|
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:
|
|
42
|
-
vrf_output:
|
|
43
|
-
vrf_proof:
|
|
44
|
-
public_key:
|
|
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:
|
|
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)),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createAccountRelayServer.js","names":["serialized: WebAuthnRegistrationCredential","requestData: CreateAccountAndRegisterUserRequest","result: CreateAccountAndRegisterResult","errorMessage","error: unknown"],"sources":["../../../../../src/core/TatchiPasskey/faucets/createAccountRelayServer.ts"],"sourcesContent":["import { VRFChallenge } from '../../types/vrf-worker';\nimport { RegistrationSSEEvent, RegistrationPhase, RegistrationStatus } from '../../types/sdkSentEvents';\nimport { PasskeyManagerContext } from '..';\nimport { base64UrlDecode } from '../../../utils/encoders';\nimport { removePrfOutputGuard, serializeRegistrationCredential, normalizeRegistrationCredential } from '../../WebAuthnManager/credentialsHelpers';\nimport type { WebAuthnRegistrationCredential } from '../../types/webauthn';\nimport type { AuthenticatorOptions } from '../../types/authenticatorOptions';\nimport type { CreateAccountAndRegisterResult } from '../../../server/core/types';\nimport { isObject } from '@/utils/validation';\nimport { errorMessage } from '../../../utils/errors';\n\nfunction isSerializedRegistrationCredential(\n credential: WebAuthnRegistrationCredential | PublicKeyCredential,\n): credential is WebAuthnRegistrationCredential {\n if (!isObject(credential)) return false;\n const resp = (credential as { response?: unknown }).response;\n if (!isObject(resp)) return false;\n return typeof (resp as { attestationObject?: unknown }).attestationObject === 'string';\n}\n\n/**\n * HTTP Request body for the relay server's /create_account_and_register_user endpoint\n */\nexport interface CreateAccountAndRegisterUserRequest {\n new_account_id: string;\n new_public_key: string;\n device_number: number;\n threshold_ed25519?: {\n client_verifying_share_b64u: string;\n };\n vrf_data: {\n vrf_input_data: number[];\n vrf_output: number[];\n vrf_proof: number[];\n public_key: number[];\n user_id: string;\n rp_id: string;\n block_height: number;\n block_hash: number[];\n intent_digest_32: number[];\n };\n webauthn_registration: WebAuthnRegistrationCredential;\n deterministic_vrf_public_key: number[];\n authenticator_options?: AuthenticatorOptions;\n}\n\n/**\n * Create account and register user using relay-server atomic endpoint\n * Makes a single call to the relay-server's /create_account_and_register_user endpoint\n * which calls the contract's atomic create_account_and_register_user function\n */\nexport async function createAccountAndRegisterWithRelayServer(\n context: PasskeyManagerContext,\n nearAccountId: string,\n publicKey: string,\n credential: WebAuthnRegistrationCredential | PublicKeyCredential,\n vrfChallenge: VRFChallenge,\n deterministicVrfPublicKey: string,\n authenticatorOptions?: AuthenticatorOptions,\n onEvent?: (event: RegistrationSSEEvent) => void,\n opts?: {\n thresholdEd25519?: {\n clientVerifyingShareB64u: string;\n };\n },\n): Promise<{\n success: boolean;\n transactionId?: string;\n thresholdEd25519?: {\n publicKey: string;\n relayerKeyId: string;\n relayerVerifyingShareB64u?: string;\n clientParticipantId?: number;\n relayerParticipantId?: number;\n participantIds?: number[];\n };\n error?: string;\n}> {\n const { configs } = context;\n\n if (!configs.relayer.url) {\n throw new Error('Relay server URL is required for atomic registration');\n }\n\n try {\n onEvent?.({\n step: 4,\n phase: RegistrationPhase.STEP_4_ACCESS_KEY_ADDITION,\n status: RegistrationStatus.PROGRESS,\n message: 'Creating account and adding access key...',\n });\n\n // Serialize the WebAuthn credential properly for the contract.\n // Accept both live PublicKeyCredential and already-serialized credentials from secureConfirm.\n const isSerialized = isSerializedRegistrationCredential(credential);\n\n // Ensure proper serialization + normalization regardless of source\n const serialized: WebAuthnRegistrationCredential = isSerialized\n ? normalizeRegistrationCredential(credential)\n : serializeRegistrationCredential(credential);\n\n // Strip PRF outputs before sending to relay/contract\n const serializedCredential = removePrfOutputGuard<WebAuthnRegistrationCredential>(serialized);\n // Normalize transports to an array (avoid null)\n if (!Array.isArray(serializedCredential?.response?.transports)) {\n serializedCredential.response.transports = [];\n }\n\n // Prepare data for atomic endpoint\n const intent_digest_32 = Array.from(base64UrlDecode(vrfChallenge.intentDigest || ''));\n if (intent_digest_32.length !== 32) {\n throw new Error('Missing or invalid vrfChallenge.intentDigest (expected base64url-encoded 32 bytes)');\n }\n const requestData: CreateAccountAndRegisterUserRequest = {\n new_account_id: nearAccountId,\n new_public_key: publicKey,\n device_number: 1, // First device gets device number 1 (1-indexed)\n ...(opts?.thresholdEd25519?.clientVerifyingShareB64u\n ? {\n threshold_ed25519: {\n client_verifying_share_b64u: opts.thresholdEd25519.clientVerifyingShareB64u,\n },\n }\n : {}),\n vrf_data: {\n vrf_input_data: Array.from(base64UrlDecode(vrfChallenge.vrfInput)),\n vrf_output: Array.from(base64UrlDecode(vrfChallenge.vrfOutput)),\n vrf_proof: Array.from(base64UrlDecode(vrfChallenge.vrfProof)),\n public_key: Array.from(base64UrlDecode(vrfChallenge.vrfPublicKey)),\n user_id: vrfChallenge.userId,\n rp_id: vrfChallenge.rpId,\n block_height: Number(vrfChallenge.blockHeight),\n block_hash: Array.from(base64UrlDecode(vrfChallenge.blockHash)),\n intent_digest_32,\n },\n webauthn_registration: serializedCredential,\n deterministic_vrf_public_key: Array.from(base64UrlDecode(deterministicVrfPublicKey)),\n authenticator_options: authenticatorOptions || context.configs.authenticatorOptions,\n };\n\n onEvent?.({\n step: 5,\n phase: RegistrationPhase.STEP_5_CONTRACT_REGISTRATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Registering user with Web3Authn contract...',\n });\n\n // Call the atomic endpoint\n const response = await fetch(`${configs.relayer.url}/create_account_and_register_user`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(requestData)\n });\n\n // Handle both successful and failed responses\n const result: CreateAccountAndRegisterResult = await response.json();\n\n if (!response.ok) {\n // Extract specific error message from relay server response\n const errorMessage = result.error || result.message || `HTTP ${response.status}: ${response.statusText}`;\n throw new Error(errorMessage);\n }\n\n if (!result.success) {\n throw new Error(result.error || 'Atomic registration failed');\n }\n\n onEvent?.({\n step: 5,\n phase: RegistrationPhase.STEP_5_CONTRACT_REGISTRATION,\n status: RegistrationStatus.SUCCESS,\n message: 'User registered with Web3Authn contract successfully',\n });\n\n return {\n success: true,\n transactionId: result.transactionHash,\n thresholdEd25519: result.thresholdEd25519\n ? {\n publicKey: result.thresholdEd25519.publicKey,\n relayerKeyId: result.thresholdEd25519.relayerKeyId,\n relayerVerifyingShareB64u: result.thresholdEd25519.relayerVerifyingShareB64u,\n clientParticipantId: result.thresholdEd25519.clientParticipantId,\n relayerParticipantId: result.thresholdEd25519.relayerParticipantId,\n participantIds: result.thresholdEd25519.participantIds,\n }\n : undefined,\n };\n\n } catch (error: unknown) {\n console.error('Atomic registration failed:', error);\n\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: 'Registration failed',\n error: errorMessage(error),\n });\n\n return {\n success: false,\n error: errorMessage(error),\n };\n }\n}\n"],"mappings":";;;;;;;AAWA,SAAS,mCACP,YAC8C;AAC9C,KAAI,CAAC,SAAS,YAAa,QAAO;CAClC,MAAM,OAAQ,WAAsC;AACpD,KAAI,CAAC,SAAS,MAAO,QAAO;AAC5B,QAAO,OAAQ,KAAyC,sBAAsB;;;;;;;AAkChF,eAAsB,wCACpB,SACA,eACA,WACA,YACA,cACA,2BACA,sBACA,SACA,MAiBC;CACD,MAAM,EAAE,YAAY;AAEpB,KAAI,CAAC,QAAQ,QAAQ,IACnB,OAAM,IAAI,MAAM;AAGlB,KAAI;AACF,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;;EAKX,MAAM,eAAe,mCAAmC;EAGxD,MAAMA,aAA6C,eAC/C,gCAAgC,cAChC,gCAAgC;EAGpC,MAAM,uBAAuB,qBAAqD;AAElF,MAAI,CAAC,MAAM,QAAQ,sBAAsB,UAAU,YACjD,sBAAqB,SAAS,aAAa;EAI7C,MAAM,mBAAmB,MAAM,KAAK,gBAAgB,aAAa,gBAAgB;AACjF,MAAI,iBAAiB,WAAW,GAC9B,OAAM,IAAI,MAAM;EAElB,MAAMC,cAAmD;GACvD,gBAAgB;GAChB,gBAAgB;GAChB,eAAe;GACf,GAAI,MAAM,kBAAkB,2BACxB,EACA,mBAAmB,EACjB,6BAA6B,KAAK,iBAAiB,+BAGrD;GACJ,UAAU;IACR,gBAAgB,MAAM,KAAK,gBAAgB,aAAa;IACxD,YAAY,MAAM,KAAK,gBAAgB,aAAa;IACpD,WAAW,MAAM,KAAK,gBAAgB,aAAa;IACnD,YAAY,MAAM,KAAK,gBAAgB,aAAa;IACpD,SAAS,aAAa;IACtB,OAAO,aAAa;IACpB,cAAc,OAAO,aAAa;IAClC,YAAY,MAAM,KAAK,gBAAgB,aAAa;IACpD;;GAEF,uBAAuB;GACvB,8BAA8B,MAAM,KAAK,gBAAgB;GACzD,uBAAuB,wBAAwB,QAAQ,QAAQ;;AAGjE,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;;EAIX,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,IAAI,oCAAoC;GACtF,QAAQ;GACR,SAAS,EAAE,gBAAgB;GAC3B,MAAM,KAAK,UAAU;;EAIvB,MAAMC,SAAyC,MAAM,SAAS;AAE9D,MAAI,CAAC,SAAS,IAAI;GAEhB,MAAMC,iBAAe,OAAO,SAAS,OAAO,WAAW,QAAQ,SAAS,OAAO,IAAI,SAAS;AAC5F,SAAM,IAAI,MAAMA;;AAGlB,MAAI,CAAC,OAAO,QACV,OAAM,IAAI,MAAM,OAAO,SAAS;AAGlC,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;;AAGX,SAAO;GACL,SAAS;GACT,eAAe,OAAO;GACtB,kBAAkB,OAAO,mBACrB;IACA,WAAW,OAAO,iBAAiB;IACnC,cAAc,OAAO,iBAAiB;IACtC,2BAA2B,OAAO,iBAAiB;IACnD,qBAAqB,OAAO,iBAAiB;IAC7C,sBAAsB,OAAO,iBAAiB;IAC9C,gBAAgB,OAAO,iBAAiB;OAExC;;UAGCC,OAAgB;AACvB,UAAQ,MAAM,+BAA+B;AAE7C,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;GACT,OAAO,aAAa;;AAGtB,SAAO;GACL,SAAS;GACT,OAAO,aAAa"}
|
|
1
|
+
{"version":3,"file":"createAccountRelayServer.js","names":["serialized: WebAuthnRegistrationCredential","requestData: CreateAccountAndRegisterUserRequest","result: CreateAccountAndRegisterResult","errorMessage","error: unknown"],"sources":["../../../../../src/core/TatchiPasskey/faucets/createAccountRelayServer.ts"],"sourcesContent":["import { VRFChallenge } from '../../types/vrf-worker';\nimport { RegistrationSSEEvent, RegistrationPhase, RegistrationStatus } from '../../types/sdkSentEvents';\nimport { PasskeyManagerContext } from '..';\nimport { base64UrlDecode } from '../../../utils/encoders';\nimport { removePrfOutputGuard, serializeRegistrationCredential, normalizeRegistrationCredential } from '../../WebAuthnManager/credentialsHelpers';\nimport type { WebAuthnRegistrationCredential } from '../../types/webauthn';\nimport type { AuthenticatorOptions } from '../../types/authenticatorOptions';\nimport type { CreateAccountAndRegisterResult } from '../../../server/core/types';\nimport { isObject } from '@/utils/validation';\nimport { errorMessage } from '../../../utils/errors';\n\nfunction isSerializedRegistrationCredential(\n credential: WebAuthnRegistrationCredential | PublicKeyCredential,\n): credential is WebAuthnRegistrationCredential {\n if (!isObject(credential)) return false;\n const resp = (credential as { response?: unknown }).response;\n if (!isObject(resp)) return false;\n return typeof (resp as { attestationObject?: unknown }).attestationObject === 'string';\n}\n\n/**\n * HTTP Request body for the relay server's /create_account_and_register_user endpoint\n */\nexport interface CreateAccountAndRegisterUserRequest {\n new_account_id: string;\n new_public_key: string;\n device_number: number;\n threshold_ed25519?: {\n client_verifying_share_b64u: string;\n };\n vrf_data: {\n // Send compact base64url strings; the relay normalizes to byte arrays for contract calls.\n vrf_input_data: string;\n vrf_output: string;\n vrf_proof: string;\n public_key: string;\n user_id: string;\n rp_id: string;\n block_height: number;\n block_hash: string;\n intent_digest_32: string;\n };\n webauthn_registration: WebAuthnRegistrationCredential;\n deterministic_vrf_public_key: number[];\n authenticator_options?: AuthenticatorOptions;\n}\n\n/**\n * Create account and register user using relay-server atomic endpoint\n * Makes a single call to the relay-server's /create_account_and_register_user endpoint\n * which calls the contract's atomic create_account_and_register_user function\n */\nexport async function createAccountAndRegisterWithRelayServer(\n context: PasskeyManagerContext,\n nearAccountId: string,\n publicKey: string,\n credential: WebAuthnRegistrationCredential | PublicKeyCredential,\n vrfChallenge: VRFChallenge,\n deterministicVrfPublicKey: string,\n authenticatorOptions?: AuthenticatorOptions,\n onEvent?: (event: RegistrationSSEEvent) => void,\n opts?: {\n thresholdEd25519?: {\n clientVerifyingShareB64u: string;\n };\n },\n): Promise<{\n success: boolean;\n transactionId?: string;\n thresholdEd25519?: {\n publicKey: string;\n relayerKeyId: string;\n relayerVerifyingShareB64u?: string;\n clientParticipantId?: number;\n relayerParticipantId?: number;\n participantIds?: number[];\n };\n error?: string;\n}> {\n const { configs } = context;\n\n if (!configs.relayer.url) {\n throw new Error('Relay server URL is required for atomic registration');\n }\n\n try {\n onEvent?.({\n step: 4,\n phase: RegistrationPhase.STEP_4_ACCESS_KEY_ADDITION,\n status: RegistrationStatus.PROGRESS,\n message: 'Creating account and adding access key...',\n });\n\n // Serialize the WebAuthn credential properly for the contract.\n // Accept both live PublicKeyCredential and already-serialized credentials from secureConfirm.\n const isSerialized = isSerializedRegistrationCredential(credential);\n\n // Ensure proper serialization + normalization regardless of source\n const serialized: WebAuthnRegistrationCredential = isSerialized\n ? normalizeRegistrationCredential(credential)\n : serializeRegistrationCredential(credential);\n\n // Strip PRF outputs before sending to relay/contract\n const serializedCredential = removePrfOutputGuard<WebAuthnRegistrationCredential>(serialized);\n // Normalize transports to an array (avoid null)\n if (!Array.isArray(serializedCredential?.response?.transports)) {\n serializedCredential.response.transports = [];\n }\n\n // Prepare data for atomic endpoint\n const intentDigestB64u = String(vrfChallenge.intentDigest || '').trim();\n const intentDigestBytes = base64UrlDecode(intentDigestB64u);\n if (intentDigestBytes.length !== 32) {\n throw new Error('Missing or invalid vrfChallenge.intentDigest (expected base64url-encoded 32 bytes)');\n }\n const requestData: CreateAccountAndRegisterUserRequest = {\n new_account_id: nearAccountId,\n new_public_key: publicKey,\n device_number: 1, // First device gets device number 1 (1-indexed)\n ...(opts?.thresholdEd25519?.clientVerifyingShareB64u\n ? {\n threshold_ed25519: {\n client_verifying_share_b64u: opts.thresholdEd25519.clientVerifyingShareB64u,\n },\n }\n : {}),\n vrf_data: {\n vrf_input_data: vrfChallenge.vrfInput,\n vrf_output: vrfChallenge.vrfOutput,\n vrf_proof: vrfChallenge.vrfProof,\n public_key: vrfChallenge.vrfPublicKey,\n user_id: vrfChallenge.userId,\n rp_id: vrfChallenge.rpId,\n block_height: Number(vrfChallenge.blockHeight),\n block_hash: vrfChallenge.blockHash,\n intent_digest_32: intentDigestB64u,\n },\n webauthn_registration: serializedCredential,\n deterministic_vrf_public_key: Array.from(base64UrlDecode(deterministicVrfPublicKey)),\n authenticator_options: authenticatorOptions || context.configs.authenticatorOptions,\n };\n\n onEvent?.({\n step: 5,\n phase: RegistrationPhase.STEP_5_CONTRACT_REGISTRATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Registering user with Web3Authn contract...',\n });\n\n // Call the atomic endpoint\n const response = await fetch(`${configs.relayer.url}/create_account_and_register_user`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify(requestData)\n });\n\n // Handle both successful and failed responses\n const result: CreateAccountAndRegisterResult = await response.json();\n\n if (!response.ok) {\n // Extract specific error message from relay server response\n const errorMessage = result.error || result.message || `HTTP ${response.status}: ${response.statusText}`;\n throw new Error(errorMessage);\n }\n\n if (!result.success) {\n throw new Error(result.error || 'Atomic registration failed');\n }\n\n onEvent?.({\n step: 5,\n phase: RegistrationPhase.STEP_5_CONTRACT_REGISTRATION,\n status: RegistrationStatus.SUCCESS,\n message: 'User registered with Web3Authn contract successfully',\n });\n\n return {\n success: true,\n transactionId: result.transactionHash,\n thresholdEd25519: result.thresholdEd25519\n ? {\n publicKey: result.thresholdEd25519.publicKey,\n relayerKeyId: result.thresholdEd25519.relayerKeyId,\n relayerVerifyingShareB64u: result.thresholdEd25519.relayerVerifyingShareB64u,\n clientParticipantId: result.thresholdEd25519.clientParticipantId,\n relayerParticipantId: result.thresholdEd25519.relayerParticipantId,\n participantIds: result.thresholdEd25519.participantIds,\n }\n : undefined,\n };\n\n } catch (error: unknown) {\n console.error('Atomic registration failed:', error);\n\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: 'Registration failed',\n error: errorMessage(error),\n });\n\n return {\n success: false,\n error: errorMessage(error),\n };\n }\n}\n"],"mappings":";;;;;;;AAWA,SAAS,mCACP,YAC8C;AAC9C,KAAI,CAAC,SAAS,YAAa,QAAO;CAClC,MAAM,OAAQ,WAAsC;AACpD,KAAI,CAAC,SAAS,MAAO,QAAO;AAC5B,QAAO,OAAQ,KAAyC,sBAAsB;;;;;;;AAmChF,eAAsB,wCACpB,SACA,eACA,WACA,YACA,cACA,2BACA,sBACA,SACA,MAiBC;CACD,MAAM,EAAE,YAAY;AAEpB,KAAI,CAAC,QAAQ,QAAQ,IACnB,OAAM,IAAI,MAAM;AAGlB,KAAI;AACF,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;;EAKX,MAAM,eAAe,mCAAmC;EAGxD,MAAMA,aAA6C,eAC/C,gCAAgC,cAChC,gCAAgC;EAGpC,MAAM,uBAAuB,qBAAqD;AAElF,MAAI,CAAC,MAAM,QAAQ,sBAAsB,UAAU,YACjD,sBAAqB,SAAS,aAAa;EAI7C,MAAM,mBAAmB,OAAO,aAAa,gBAAgB,IAAI;EACjE,MAAM,oBAAoB,gBAAgB;AAC1C,MAAI,kBAAkB,WAAW,GAC/B,OAAM,IAAI,MAAM;EAElB,MAAMC,cAAmD;GACvD,gBAAgB;GAChB,gBAAgB;GAChB,eAAe;GACf,GAAI,MAAM,kBAAkB,2BACxB,EACA,mBAAmB,EACjB,6BAA6B,KAAK,iBAAiB,+BAGrD;GACJ,UAAU;IACR,gBAAgB,aAAa;IAC7B,YAAY,aAAa;IACzB,WAAW,aAAa;IACxB,YAAY,aAAa;IACzB,SAAS,aAAa;IACtB,OAAO,aAAa;IACpB,cAAc,OAAO,aAAa;IAClC,YAAY,aAAa;IACzB,kBAAkB;;GAEpB,uBAAuB;GACvB,8BAA8B,MAAM,KAAK,gBAAgB;GACzD,uBAAuB,wBAAwB,QAAQ,QAAQ;;AAGjE,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;;EAIX,MAAM,WAAW,MAAM,MAAM,GAAG,QAAQ,QAAQ,IAAI,oCAAoC;GACtF,QAAQ;GACR,SAAS,EAAE,gBAAgB;GAC3B,MAAM,KAAK,UAAU;;EAIvB,MAAMC,SAAyC,MAAM,SAAS;AAE9D,MAAI,CAAC,SAAS,IAAI;GAEhB,MAAMC,iBAAe,OAAO,SAAS,OAAO,WAAW,QAAQ,SAAS,OAAO,IAAI,SAAS;AAC5F,SAAM,IAAI,MAAMA;;AAGlB,MAAI,CAAC,OAAO,QACV,OAAM,IAAI,MAAM,OAAO,SAAS;AAGlC,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;;AAGX,SAAO;GACL,SAAS;GACT,eAAe,OAAO;GACtB,kBAAkB,OAAO,mBACrB;IACA,WAAW,OAAO,iBAAiB;IACnC,cAAc,OAAO,iBAAiB;IACtC,2BAA2B,OAAO,iBAAiB;IACnD,qBAAqB,OAAO,iBAAiB;IAC7C,sBAAsB,OAAO,iBAAiB;IAC9C,gBAAgB,OAAO,iBAAiB;OAExC;;UAGCC,OAAgB;AACvB,UAAQ,MAAM,+BAA+B;AAE7C,YAAU;GACR,MAAM;GACN,OAAO,kBAAkB;GACzB,QAAQ,mBAAmB;GAC3B,SAAS;GACT,OAAO,aAAa;;AAGtB,SAAO;GACL,SAAS;GACT,OAAO,aAAa"}
|
|
@@ -532,7 +532,7 @@ async function handleLoginUnlockVRF(context, nearAccountId, onEvent, onError, af
|
|
|
532
532
|
const relayerUrl$1 = context.configs.relayer?.url;
|
|
533
533
|
if (usedFallbackTouchId && relayerUrl$1) {
|
|
534
534
|
const refreshed = await webAuthnManager.shamir3PassEncryptCurrentVrfKeypair();
|
|
535
|
-
await webAuthnManager.updateServerEncryptedVrfKeypair(nearAccountId, refreshed);
|
|
535
|
+
await webAuthnManager.updateServerEncryptedVrfKeypair(nearAccountId, refreshed, activeDeviceNumber);
|
|
536
536
|
}
|
|
537
537
|
} catch (refreshErr) {
|
|
538
538
|
console.warn("Non-fatal: Failed to refresh serverEncryptedVrfKeypair:", refreshErr?.message || refreshErr);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.js","names":["err: any","signingSession: SigningSessionStatus","e: any","clientVerifyingShareB64u: string | null","serverSessionJwt: string | undefined","loginResult: LoginResult","hintUserPromise: Promise<ClientUserData | null>","userData: ClientUserData | null","unlockResult: { success: boolean; error?: string }","unlockCredential: WebAuthnAuthenticationCredential | undefined","error: any","relayerUrl","refreshErr: any","result: LoginResult"],"sources":["../../../../src/core/TatchiPasskey/login.ts"],"sourcesContent":["import type {\n AfterCall,\n LoginHooksOptions,\n LoginSSEvent,\n} from '../types/sdkSentEvents';\nimport { LoginPhase, LoginStatus } from '../types/sdkSentEvents';\nimport type {\n GetRecentLoginsResult,\n LoginAndCreateSessionResult,\n LoginResult,\n LoginSession,\n LoginState,\n SigningSessionStatus,\n} from '../types/tatchi';\nimport type { PasskeyManagerContext } from './index';\nimport type { AccountId } from '../types/accountIds';\nimport type { WebAuthnAuthenticationCredential } from '../types/webauthn';\nimport { getUserFriendlyErrorMessage } from '../../utils/errors';\nimport { createRandomVRFChallenge, VRFChallenge } from '../types/vrf-worker';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport type { ClientAuthenticatorData, ClientUserData } from '../IndexedDBManager';\nimport { verifyAuthenticationResponse } from '../rpcCalls';\nimport { computeLoginIntentDigest } from '../digests/intentDigest';\nimport { buildThresholdSessionPolicy } from '../threshold/thresholdSessionPolicy';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\nimport {\n clearAllCachedThresholdEd25519AuthSessions,\n makeThresholdEd25519AuthSessionCacheKey,\n mintThresholdEd25519AuthSession,\n putCachedThresholdEd25519AuthSession,\n} from '../threshold/thresholdEd25519AuthSession';\nimport { normalizeThresholdEd25519ParticipantIds } from '../../threshold/participants';\n\ntype WarmSigningSessionPolicy = { ttlMs: number; remainingUses: number };\n\ntype ThresholdSessionPlan = {\n sessionKind: 'jwt';\n relayerUrl: string;\n relayerKeyId: string;\n cacheKey: string;\n policy: Awaited<ReturnType<typeof buildThresholdSessionPolicy>>;\n deviceNumber: number;\n};\n\n/**\n * Core login function that handles passkey authentication without React dependencies.\n *\n * - Unlocks the VRF keypair (Shamir 3‑pass auto‑unlock when possible; falls back to TouchID).\n * - Updates local login state and returns success with account/public key info.\n * - Optional: mints a server session when `options.session` is provided.\n * - Generates a fresh, chain‑anchored VRF challenge (using latest block).\n * - Collects a WebAuthn assertion over the VRF output and posts to the relay route\n * (defaults to `/verify-authentication-response`).\n * - When `kind: 'jwt'`, returns the token in `result.jwt`.\n * - When `kind: 'cookie'`, the server sets an HttpOnly cookie and no JWT is returned.\n */\nexport async function loginAndCreateSession(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options?: LoginHooksOptions\n): Promise<LoginAndCreateSessionResult> {\n const { onEvent, onError, afterCall } = options || {};\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 1,\n phase: LoginPhase.STEP_1_PREPARATION,\n status: LoginStatus.PROGRESS,\n message: `Starting login for ${nearAccountId}`\n });\n\n const prevStatus = await webAuthnManager.checkVrfStatus();\n const prevVrfAccountId = prevStatus?.active ? prevStatus.nearAccountId : null;\n\n // If this call activates VRF then fails, clear the partial session.\n const rollbackVrfOnFailure = async () => {\n const status = await webAuthnManager.checkVrfStatus();\n const current = status?.active ? status.nearAccountId : null;\n if (current && current !== prevVrfAccountId) {\n await logoutAndClearSession(context);\n }\n };\n\n try {\n // Validation\n if (!window.isSecureContext) {\n const errorMessage = 'Passkey operations require a secure context (HTTPS or localhost).';\n return await finalizeLoginError({\n message: errorMessage,\n error: new Error(errorMessage),\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callAfterCall: false,\n });\n }\n\n const session = options?.session;\n const wantsServerSession = session !== undefined;\n const deviceNumberHint = parseDeviceNumber(options?.deviceNumber, { min: 1 });\n const base = await handleLoginUnlockVRF(\n context,\n nearAccountId,\n onEvent,\n onError,\n afterCall,\n // Defer final 'login-complete' event & afterCall until warm signing session is minted\n true,\n deviceNumberHint\n );\n // If base login failed, just return\n if (!base.result.success) {\n return base.result;\n }\n\n const preferredDeviceNumber = deviceNumberHint ?? base.activeDeviceNumber;\n const warmPolicy = resolveWarmSigningSessionPolicy(context, options);\n\n const wantsThresholdSession =\n webAuthnManager.getUserPreferences().getSignerMode().mode === 'threshold-signer';\n const relayUrl = (session?.relayUrl || context.configs.relayer.url).trim();\n const verifyRoute = (session?.route || '/verify-authentication-response').trim();\n\n const thresholdPlan = await prepareThresholdSessionPlan({\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n wantsThresholdSession,\n });\n\n const wantsRelayerSession = wantsServerSession || thresholdPlan !== null;\n if (wantsRelayerSession && !relayUrl) {\n console.warn('[login] No relayUrl provided for session-style signing');\n }\n\n if (wantsRelayerSession && relayUrl) {\n return await runRelayerSessionFlow({\n context,\n nearAccountId,\n baseLoginResult: base.result,\n baseUnlockCredential: base.unlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n\n // No relayer session requested (or relayUrl missing): mint/refresh warm signing session only.\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential: base.unlockCredential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult: base.result,\n onEvent,\n afterCall,\n });\n } catch (err: any) {\n const errorMessage =\n getUserFriendlyErrorMessage(err, 'login') || err?.message || 'Login failed';\n return await finalizeLoginError({\n message: errorMessage,\n error: err,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\nfunction resolveWarmSigningSessionPolicy(\n context: PasskeyManagerContext,\n options?: LoginHooksOptions\n): WarmSigningSessionPolicy {\n const defaults = context.configs.signingSessionDefaults;\n return {\n ttlMs: options?.signingSession?.ttlMs ?? defaults.ttlMs,\n remainingUses: options?.signingSession?.remainingUses ?? defaults.remainingUses,\n };\n}\n\nasync function attachSigningSessionStatus(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult } = args;\n if (!loginResult.success) return loginResult;\n try {\n const signingSession: SigningSessionStatus =\n await webAuthnManager.getWarmSigningSessionStatus(nearAccountId);\n return { ...loginResult, signingSession };\n } catch {\n return loginResult;\n }\n}\n\nasync function finalizeLoginSuccess(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n onEvent?: (event: LoginSSEvent) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult, onEvent, afterCall } = args;\n const finalResult = await attachSigningSessionStatus({\n webAuthnManager,\n nearAccountId,\n loginResult,\n });\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId,\n clientNearPublicKey: loginResult.clientNearPublicKey ?? '',\n });\n await afterCall?.(true, finalResult);\n return finalResult;\n}\n\nasync function finalizeLoginError(args: {\n message: string;\n error?: unknown;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n callOnError?: boolean;\n callAfterCall?: boolean;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n message,\n error,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callOnError = true,\n callAfterCall = true,\n } = args;\n\n try { await rollbackVrfOnFailure(); } catch {}\n\n if (callOnError) {\n onError?.(error as any);\n }\n\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message,\n error: message,\n });\n\n if (callAfterCall) {\n await afterCall?.(false);\n }\n return { success: false, error: message };\n}\n\nasync function prepareThresholdSessionPlan(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n preferredDeviceNumber: number | null;\n relayUrl: string;\n ttlMs: number;\n remainingUses: number;\n wantsThresholdSession: boolean;\n}): Promise<ThresholdSessionPlan | null> {\n const {\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs,\n remainingUses,\n wantsThresholdSession,\n } = args;\n if (!wantsThresholdSession || !relayUrl) return null;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) throw new Error('Missing rpId for threshold session');\n\n const lastUser = await webAuthnManager.getLastUser();\n const deviceNumber = preferredDeviceNumber ??\n (lastUser?.nearAccountId === nearAccountId\n ? lastUser.deviceNumber\n : (await IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId))\n ?.deviceNumber ?? null);\n\n if (deviceNumber === null) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n const thresholdKeyMaterial = await IndexedDBManager.nearKeysDB.getThresholdKeyMaterial(\n nearAccountId,\n deviceNumber\n );\n const relayerKeyId = thresholdKeyMaterial?.relayerKeyId || null;\n const participantIds = thresholdKeyMaterial?.participants?.map((p) => p.id) || null;\n const normalizedParticipantIds = normalizeThresholdEd25519ParticipantIds(participantIds);\n\n if (!relayerKeyId) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n if (!normalizedParticipantIds || normalizedParticipantIds.length < 2) {\n console.warn('[login] threshold key material missing/invalid participantIds; skipping threshold session mint');\n return null;\n }\n\n const policy = await buildThresholdSessionPolicy({\n nearAccountId,\n rpId,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n ttlMs,\n remainingUses,\n });\n\n const cacheKey = makeThresholdEd25519AuthSessionCacheKey({\n nearAccountId,\n rpId,\n relayerUrl: relayUrl,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n });\n\n return {\n sessionKind: 'jwt',\n relayerUrl: relayUrl,\n relayerKeyId,\n cacheKey,\n policy,\n deviceNumber,\n };\n } catch (e: any) {\n console.warn(\n '[login] failed to prepare threshold session policy; skipping threshold session mint:',\n e?.message || e\n );\n return null;\n }\n}\n\nasync function mintThresholdSessionBestEffort(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n plan: ThresholdSessionPlan;\n vrfChallenge: VRFChallenge;\n credential: WebAuthnAuthenticationCredential;\n}): Promise<void> {\n const { context, nearAccountId, plan, vrfChallenge, credential } = args;\n const { webAuthnManager } = context;\n\n let clientVerifyingShareB64u: string | null = null;\n try {\n const localKeyMaterial = await IndexedDBManager.nearKeysDB.getLocalKeyMaterial(\n nearAccountId,\n plan.deviceNumber\n );\n const wrapKeySalt = String(localKeyMaterial?.wrapKeySalt || '').trim();\n if (wrapKeySalt) {\n const derived =\n await webAuthnManager.deriveThresholdEd25519ClientVerifyingShareFromCredential({\n credential,\n nearAccountId,\n wrapKeySalt,\n });\n if (derived.success && derived.clientVerifyingShareB64u) {\n clientVerifyingShareB64u = derived.clientVerifyingShareB64u;\n }\n }\n } catch (e: any) {\n console.warn(\n '[login] failed to derive clientVerifyingShareB64u for threshold session mint:',\n e?.message || e\n );\n }\n\n if (!clientVerifyingShareB64u) {\n console.warn(\n '[login] threshold session mint skipped: missing clientVerifyingShareB64u'\n );\n return;\n }\n\n const minted = await mintThresholdEd25519AuthSession({\n relayerUrl: plan.relayerUrl,\n sessionKind: plan.sessionKind,\n relayerKeyId: plan.relayerKeyId,\n clientVerifyingShareB64u,\n sessionPolicy: plan.policy.policy,\n vrfChallenge,\n webauthnAuthentication: credential,\n });\n\n if (minted.ok && minted.jwt) {\n putCachedThresholdEd25519AuthSession(plan.cacheKey, {\n sessionKind: plan.sessionKind,\n policy: plan.policy.policy,\n policyJson: plan.policy.policyJson,\n sessionPolicyDigest32: plan.policy.sessionPolicyDigest32,\n jwt: minted.jwt,\n ...(minted.expiresAtMs ? { expiresAtMs: minted.expiresAtMs } : {}),\n });\n return;\n }\n\n if (!minted.ok) {\n console.warn(\n '[login] threshold session mint failed:',\n minted.code || minted.message || 'unknown error'\n );\n }\n}\n\nasync function runRelayerSessionFlow(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n baseLoginResult: LoginResult;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n preferredDeviceNumber: number | null;\n session: LoginHooksOptions['session'] | undefined;\n relayUrl: string;\n verifyRoute: string;\n thresholdPlan: ThresholdSessionPlan | null;\n warmPolicy: WarmSigningSessionPolicy;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n context,\n nearAccountId,\n baseLoginResult,\n baseUnlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n } = args;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) {\n throw new Error('Missing rpId for VRF challenge generation during login');\n }\n\n const blockInfo = await context.nearClient.viewBlock({ finality: 'final' });\n const txBlockHash = blockInfo?.header?.hash;\n const txBlockHeight = String(blockInfo.header?.height ?? '');\n const intentDigest = await computeLoginIntentDigest({ nearAccountId, rpId });\n const vrfChallenge = await webAuthnManager.generateVrfChallengeOnce({\n userId: nearAccountId,\n rpId,\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n intentDigest,\n ...(thresholdPlan ? { sessionPolicyDigest32: thresholdPlan.policy.sessionPolicyDigest32 } : {}),\n });\n\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(\n authenticators,\n preferredDeviceNumber\n );\n const credential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: vrfChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n\n const effectiveLoginResult = await bindVrfToSelectedLoginPasskeyDevice({\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult: baseLoginResult,\n });\n\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n\n let serverSessionJwt: string | undefined;\n if (session) {\n const v = await verifyAuthenticationResponse(\n relayUrl,\n verifyRoute,\n session.kind,\n vrfChallenge,\n credential\n );\n if (!v.success || !v.verified) {\n const errMsg = v.error || 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n rollbackVrfOnFailure,\n onEvent,\n afterCall,\n callOnError: false,\n });\n }\n serverSessionJwt = v.jwt;\n }\n\n if (thresholdPlan) {\n await mintThresholdSessionBestEffort({\n context,\n nearAccountId,\n plan: thresholdPlan,\n vrfChallenge,\n credential,\n });\n }\n\n const loginResult: LoginResult = serverSessionJwt\n ? { ...effectiveLoginResult, jwt: serverSessionJwt }\n : effectiveLoginResult;\n\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult,\n onEvent,\n afterCall,\n });\n } catch (e: any) {\n console.error('[login] Failed to start session:', e);\n const errMsg =\n getUserFriendlyErrorMessage(e, 'login') ||\n e?.message ||\n 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n error: e,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\n/**\n * When multiple passkeys exist for the same account, the user can select any of them in the WebAuthn prompt.\n * If the VRF worker was auto-unlocked using a different device (e.g. Shamir 3-pass based on the \"latest\" row),\n * we must rebind the VRF worker to the same deviceNumber as the selected credential. Otherwise, WrapKeySeed\n * derivation can mix PRF.first(from selected credential) with vrf_sk(from a different device), which later\n * causes vault decryption failures (e.g. `aead::Error` during private key export).\n */\nasync function bindVrfToSelectedLoginPasskeyDevice(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n credential: WebAuthnAuthenticationCredential;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n baseLoginResult: LoginResult;\n}): Promise<LoginResult> {\n const {\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult,\n } = args;\n\n // Start with the base login result (may reflect whichever VRF keypair was auto-unlocked).\n // If the user selects a different passkey below, update it to match the chosen device.\n let effectiveLoginResult = baseLoginResult;\n\n if (authenticators.length <= 1) {\n return effectiveLoginResult;\n }\n\n const rawId = credential.rawId;\n const matched = authenticators.find((a) => a.credentialId === rawId);\n const selectedDeviceNumber = matched?.deviceNumber ?? null;\n\n if (selectedDeviceNumber === null) {\n return effectiveLoginResult;\n }\n\n const userForDevice = await IndexedDBManager.clientDB\n .getUserByDevice(nearAccountId, selectedDeviceNumber)\n .catch(() => null);\n\n if (userForDevice?.clientNearPublicKey) {\n effectiveLoginResult = {\n ...effectiveLoginResult,\n clientNearPublicKey: userForDevice.clientNearPublicKey,\n };\n }\n\n const shouldRebindVrf = !baseUnlockCredential || baseUnlockCredential.rawId !== rawId;\n if (shouldRebindVrf) {\n const shamir = userForDevice?.serverEncryptedVrfKeypair;\n if (!shamir?.ciphertextVrfB64u || !shamir?.kek_s_b64u || !shamir?.serverKeyId) {\n throw new Error(\n `Missing serverEncryptedVrfKeypair for account ${nearAccountId} device ${selectedDeviceNumber}. ` +\n 'Open the wallet once online to refresh local state, then try again.'\n );\n }\n\n const unlock = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n if (!unlock.success) {\n throw new Error(\n unlock.error ||\n 'Failed to bind VRF keypair to the passkey you selected. Please try again.'\n );\n }\n }\n\n // Persist the deviceNumber that the user actually selected so subsequent flows (export/signing)\n // use the correct vault entry.\n await webAuthnManager.setLastUser(nearAccountId, selectedDeviceNumber);\n // Best-effort: stamp the selected device as the one last used for login.\n await webAuthnManager.updateLastLogin(nearAccountId).catch(() => undefined);\n\n return effectiveLoginResult;\n}\n\n/**\n * Mint or refresh a \"warm signing session\" in the VRF worker.\n *\n * Notes:\n * - Reuses a previously collected WebAuthn credential when provided (e.g. TouchID fallback),\n * to avoid prompting the user twice in a single login flow.\n * - Session TTL/remainingUses policy is enforced by the VRF worker.\n */\nasync function mintWarmSigningSession(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n onEvent?: (event: LoginSSEvent) => void;\n credential?: WebAuthnAuthenticationCredential;\n ttlMs: number;\n remainingUses: number;\n}): Promise<void> {\n const { context, nearAccountId, onEvent, credential, ttlMs, remainingUses } = args;\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking warm signing session...'\n });\n\n // If we already performed a TouchID ceremony (e.g., VRF unlock fallback),\n // reuse that credential to avoid a second prompt.\n let effectiveCredential = credential;\n if (!effectiveCredential) {\n const challenge = createRandomVRFChallenge();\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const { authenticatorsForPrompt } = await IndexedDBManager.clientDB.ensureCurrentPasskey(\n nearAccountId,\n authenticators,\n );\n effectiveCredential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n }\n\n await webAuthnManager.mintSigningSessionFromCredential({\n nearAccountId,\n credential: effectiveCredential,\n ttlMs,\n remainingUses,\n });\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'Warm signing session unlocked'\n });\n}\n\n/**\n * Handle onchain (serverless) login using VRF flow per docs/vrf_challenges.md\n *\n * VRF AUTHENTICATION FLOW:\n * 1. Unlock VRF keypair in VRF Worker memory, either\n * - Decrypt via Shamir 3-pass (when Relayer is present), or\n * - Re-derive the VRF via credentials inside VRF worker dynamically\n * 2. Generate VRF challenge using stored VRF keypair + NEAR block data (no TouchID needed)\n * 3. Use VRF output as WebAuthn challenge for authentication\n * 4. Verify VRF proof and WebAuthn response on contract simultaneously\n * - VRF proof assures WebAuthn challenge is fresh and valid (replay protection)\n * - WebAuthn verification for origin + biometric credentials + device authenticity\n */\nasync function handleLoginUnlockVRF(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n onEvent?: (event: LoginSSEvent) => void,\n onError?: (error: Error) => void,\n afterCall?: AfterCall<LoginAndCreateSessionResult>,\n deferCompletionHooks?: boolean,\n deviceNumberHint: number | null = null,\n): Promise<{\n result: LoginResult;\n usedFallbackTouchId: boolean;\n unlockCredential?: WebAuthnAuthenticationCredential;\n activeDeviceNumber: number | null;\n}> {\n const { webAuthnManager } = context;\n\n try {\n // Step 1: Get VRF credentials and authenticators, and validate them\n const hintUserPromise: Promise<ClientUserData | null> =\n deviceNumberHint !== null\n ? webAuthnManager.getUserByDevice(nearAccountId, deviceNumberHint).catch(() => null)\n : Promise.resolve(null);\n\n const [hintUser, lastUser, latestByAccount, authenticators] = await Promise.all([\n hintUserPromise,\n webAuthnManager.getLastUser(),\n IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId),\n webAuthnManager.getAuthenticatorsByUser(nearAccountId),\n ]);\n\n // Prefer the most recently updated record for this account; fall back to lastUser pointer.\n let userData: ClientUserData | null = null;\n if (hintUser && hintUser.nearAccountId === nearAccountId) {\n userData = hintUser;\n } else if (latestByAccount && latestByAccount.nearAccountId === nearAccountId) {\n userData = latestByAccount;\n } else if (lastUser && lastUser.nearAccountId === nearAccountId) {\n userData = lastUser;\n } else {\n userData = await webAuthnManager.getUserByDevice(nearAccountId, 1);\n }\n\n // Validate user data and authenticators\n if (!userData) {\n throw new Error(`User data not found for ${nearAccountId} in IndexedDB. Please register an account.`);\n }\n if (!userData.clientNearPublicKey) {\n throw new Error(`No NEAR public key found for ${nearAccountId}. Please register an account.`);\n }\n if (\n !userData.encryptedVrfKeypair?.encryptedVrfDataB64u ||\n !userData.encryptedVrfKeypair?.chacha20NonceB64u\n ) {\n throw new Error('No VRF credentials found. Please register an account.');\n }\n if (authenticators.length === 0) {\n throw new Error(`No authenticators found for account ${nearAccountId}. Please register.`);\n }\n\n // Step 2: Try Shamir 3-pass commutative unlock first (no TouchID required), fallback to TouchID\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking VRF keys...'\n });\n\n let unlockResult: { success: boolean; error?: string } = { success: false };\n let usedFallbackTouchId = false;\n let unlockCredential: WebAuthnAuthenticationCredential | undefined;\n let activeDeviceNumber = userData.deviceNumber;\n // Effective user row whose VRF/NEAR keys are actually used for this login.\n // May be switched when multiple devices exist and the user picks a different passkey.\n let effectiveUserData = userData;\n\n const shamir = userData.serverEncryptedVrfKeypair;\n const relayerUrl = context.configs.relayer?.url;\n const useShamir3PassVRFKeyUnlock = !!relayerUrl && !!shamir?.serverKeyId;\n\n if (useShamir3PassVRFKeyUnlock && shamir) {\n try {\n if (!shamir.ciphertextVrfB64u || !shamir.kek_s_b64u) {\n throw new Error('Missing Shamir3Pass fields (ciphertextVrfB64u/kek_s_b64u)');\n }\n\n unlockResult = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n\n if (unlockResult.success) {\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const active = vrfStatus.active && vrfStatus.nearAccountId === nearAccountId;\n if (!active) {\n unlockResult = { success: false, error: 'VRF session inactive after Shamir3Pass' };\n }\n if (active) {\n // Proactive rotation if serverKeyId changed and we unlocked via Shamir\n await webAuthnManager.maybeProactiveShamirRefresh(nearAccountId);\n }\n } else {\n throw new Error(`Shamir3Pass auto-unlock failed: ${unlockResult.error}`);\n }\n } catch (error: any) {\n unlockResult = { success: false, error: error.message };\n }\n }\n\n // Fallback to TouchID if Shamir3Pass decryption failed\n if (!unlockResult.success) {\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(authenticators, deviceNumberHint);\n const fallback = await fallbackUnlockVrfKeypairWithTouchId({\n webAuthnManager,\n nearAccountId,\n authenticators: authenticatorsForPrompt,\n userData,\n onEvent,\n });\n unlockResult = fallback.unlockResult;\n usedFallbackTouchId = fallback.usedFallbackTouchId;\n unlockCredential = fallback.unlockCredential;\n effectiveUserData = fallback.effectiveUserData;\n activeDeviceNumber = fallback.activeDeviceNumber;\n }\n\n if (!unlockResult.success) {\n throw new Error(`Failed to unlock VRF keypair: ${unlockResult.error}`);\n }\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'VRF keypair unlocked...'\n });\n\n // Proactive refresh: if Shamir3Pass failed and we used TouchID, re-encrypt under current server key\n try {\n const relayerUrl = context.configs.relayer?.url;\n if (usedFallbackTouchId && relayerUrl) {\n const refreshed = await webAuthnManager.shamir3PassEncryptCurrentVrfKeypair();\n await webAuthnManager.updateServerEncryptedVrfKeypair(nearAccountId, refreshed);\n }\n } catch (refreshErr: any) {\n console.warn('Non-fatal: Failed to refresh serverEncryptedVrfKeypair:', refreshErr?.message || refreshErr);\n }\n\n // Step 3: Update local data and return success\n // Ensure last-user deviceNumber reflects the passkey actually used for login.\n try {\n await webAuthnManager.setLastUser(nearAccountId, activeDeviceNumber);\n } catch {\n // Non-fatal; continue even if last-user update fails.\n }\n await webAuthnManager.updateLastLogin(nearAccountId);\n\n const result: LoginResult = {\n success: true,\n loggedInNearAccountId: nearAccountId,\n // Ensure the clientNearPublicKey reflects the device whose VRF credentials\n // are actually active for this login.\n clientNearPublicKey: effectiveUserData.clientNearPublicKey,\n nearAccountId: nearAccountId\n };\n\n if (!deferCompletionHooks) {\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId: nearAccountId,\n clientNearPublicKey: effectiveUserData.clientNearPublicKey\n });\n afterCall?.(true, result);\n }\n return { result, usedFallbackTouchId, unlockCredential, activeDeviceNumber };\n\n } catch (error: any) {\n // Use centralized error handling\n const errorMessage = getUserFriendlyErrorMessage(error, 'login');\n\n onError?.(error);\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message: errorMessage,\n error: errorMessage\n });\n\n const result: LoginResult = { success: false, error: errorMessage };\n afterCall?.(false);\n return { result, usedFallbackTouchId: false, activeDeviceNumber: null };\n }\n}\n\n/**\n * TouchID fallback path for VRF unlock.\n *\n * Used when Shamir 3-pass auto-unlock fails/unavailable:\n * - Prompts WebAuthn to obtain a serialized credential with PRF.first + PRF.second.\n * - Aligns the local encrypted VRF keypair blob with the passkey the user actually chose\n * (important when multiple devices/passkeys exist for the same account).\n * - Unlocks the VRF keypair inside the VRF worker and returns updated effective user context.\n */\nasync function fallbackUnlockVrfKeypairWithTouchId(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n userData: ClientUserData;\n onEvent?: (event: LoginSSEvent) => void;\n}): Promise<{\n unlockResult: { success: boolean; error?: string };\n usedFallbackTouchId: boolean;\n unlockCredential: WebAuthnAuthenticationCredential;\n effectiveUserData: ClientUserData;\n activeDeviceNumber: number;\n}> {\n const { webAuthnManager, nearAccountId, authenticators, userData, onEvent } = args;\n\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Logging in, unlocking VRF credentials...'\n });\n\n const challenge = createRandomVRFChallenge();\n const credential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n credentialIds: authenticators.map((a) => a.credentialId),\n });\n\n let effectiveUserData = userData;\n let activeDeviceNumber = userData.deviceNumber;\n\n // If multiple authenticators exist, align VRF credentials with the passkey\n // the user actually chose, based on credentialId → deviceNumber.\n if (authenticators.length > 1) {\n const rawId = credential.rawId;\n const matched = authenticators.find(a => a.credentialId === rawId);\n if (matched) {\n try {\n const byDevice = await IndexedDBManager.clientDB.getUserByDevice(nearAccountId, matched.deviceNumber);\n if (byDevice) {\n effectiveUserData = byDevice;\n activeDeviceNumber = matched.deviceNumber;\n }\n } catch {\n // If lookup by device fails, fall back to the base userData.\n }\n }\n }\n\n const unlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: effectiveUserData.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: effectiveUserData.encryptedVrfKeypair.chacha20NonceB64u,\n },\n credential: credential,\n });\n\n return {\n unlockResult,\n usedFallbackTouchId: unlockResult.success,\n unlockCredential: credential,\n effectiveUserData,\n activeDeviceNumber,\n };\n}\n\n/**\n * High-level login snapshot used by React contexts/UI.\n *\n * Returns:\n * - `login`: derived from IndexedDB last-user pointer + VRF worker status\n * - `signingSession`: warm signing session status when available\n *\n * The `nearAccountId` argument is treated as a \"query hint\" and must match the\n * last logged-in account to be considered logged in (prevents accidental cross-account reads).\n */\nexport async function getLoginSession(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginSession> {\n const login = await getLoginStateInternal(context, nearAccountId);\n if (!login?.isLoggedIn || !login.nearAccountId) return { login, signingSession: null };\n try {\n const signingSession = await context.webAuthnManager.getWarmSigningSessionStatus(login.nearAccountId);\n return { login, signingSession };\n } catch {\n return { login, signingSession: null };\n }\n}\n\n/**\n * Internal helper for computing `LoginState`.\n *\n * Implementation detail:\n * - Trusts the IndexedDB last-user pointer for account selection, then confirms\n * that the VRF worker is actually active for that same account.\n */\nasync function getLoginStateInternal(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginState> {\n const { webAuthnManager } = context;\n try {\n // Determine target account strictly from the last logged-in device.\n const lastUser = await webAuthnManager.getLastUser();\n const targetAccountId = nearAccountId ?? lastUser?.nearAccountId ?? null;\n\n // If caller requested a specific account, it must match the last logged-in account.\n if (!lastUser || (targetAccountId && lastUser.nearAccountId !== targetAccountId)) {\n return {\n isLoggedIn: false,\n nearAccountId: targetAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n\n const userData = lastUser;\n const publicKey = userData?.clientNearPublicKey || null;\n\n // Check actual VRF worker status\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const vrfActive = vrfStatus.active && vrfStatus.nearAccountId === targetAccountId;\n\n // Determine if user is considered \"logged in\"\n // User is logged in if they have user data and VRF is active\n const isLoggedIn = !!(userData && userData.clientNearPublicKey && vrfActive);\n\n return {\n isLoggedIn,\n nearAccountId: targetAccountId,\n publicKey,\n vrfActive,\n userData,\n vrfSessionDuration: vrfStatus.sessionDuration || 0\n };\n\n } catch (error: any) {\n console.warn('Error getting login state:', error);\n return {\n isLoggedIn: false,\n nearAccountId: nearAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n}\n\n/**\n * List recently used accounts from IndexedDB.\n *\n * Used for account picker UIs and initial app bootstrap state.\n */\nexport async function getRecentLogins(\n context: PasskeyManagerContext\n): Promise<GetRecentLoginsResult> {\n const { webAuthnManager } = context;\n // Get all user accounts from IndexDB\n const allUsersData = await webAuthnManager.getAllUsers();\n const accountIds = allUsersData.map(user => user.nearAccountId);\n // Get last used account for initial state\n const lastUsedAccount = await webAuthnManager.getLastUser();\n return {\n accountIds,\n lastUsedAccount,\n };\n}\n\n/**\n * Clear the active VRF session and any client-side nonce caches.\n *\n * This is the canonical \"logout\" operation for the SDK (does not delete accounts).\n */\nexport async function logoutAndClearSession(context: PasskeyManagerContext): Promise<void> {\n const { webAuthnManager } = context;\n await webAuthnManager.clearVrfSession();\n try { webAuthnManager.getNonceManager().clear(); } catch {}\n try { clearAllCachedThresholdEd25519AuthSessions(); } catch {}\n}\n\nfunction prioritizeAuthenticatorsByDeviceNumber(\n authenticators: ClientAuthenticatorData[],\n deviceNumber: number | null\n): ClientAuthenticatorData[] {\n if (authenticators.length <= 1) return authenticators;\n if (deviceNumber === null) return authenticators;\n const preferred = authenticators.filter((a) => a.deviceNumber === deviceNumber);\n if (preferred.length === 0) return authenticators;\n const rest = authenticators.filter((a) => a.deviceNumber !== deviceNumber);\n return [...preferred, ...rest];\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"login.js","names":["err: any","signingSession: SigningSessionStatus","e: any","clientVerifyingShareB64u: string | null","serverSessionJwt: string | undefined","loginResult: LoginResult","hintUserPromise: Promise<ClientUserData | null>","userData: ClientUserData | null","unlockResult: { success: boolean; error?: string }","unlockCredential: WebAuthnAuthenticationCredential | undefined","error: any","relayerUrl","refreshErr: any","result: LoginResult"],"sources":["../../../../src/core/TatchiPasskey/login.ts"],"sourcesContent":["import type {\n AfterCall,\n LoginHooksOptions,\n LoginSSEvent,\n} from '../types/sdkSentEvents';\nimport { LoginPhase, LoginStatus } from '../types/sdkSentEvents';\nimport type {\n GetRecentLoginsResult,\n LoginAndCreateSessionResult,\n LoginResult,\n LoginSession,\n LoginState,\n SigningSessionStatus,\n} from '../types/tatchi';\nimport type { PasskeyManagerContext } from './index';\nimport type { AccountId } from '../types/accountIds';\nimport type { WebAuthnAuthenticationCredential } from '../types/webauthn';\nimport { getUserFriendlyErrorMessage } from '../../utils/errors';\nimport { createRandomVRFChallenge, VRFChallenge } from '../types/vrf-worker';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport type { ClientAuthenticatorData, ClientUserData } from '../IndexedDBManager';\nimport { verifyAuthenticationResponse } from '../rpcCalls';\nimport { computeLoginIntentDigest } from '../digests/intentDigest';\nimport { buildThresholdSessionPolicy } from '../threshold/thresholdSessionPolicy';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\nimport {\n clearAllCachedThresholdEd25519AuthSessions,\n makeThresholdEd25519AuthSessionCacheKey,\n mintThresholdEd25519AuthSession,\n putCachedThresholdEd25519AuthSession,\n} from '../threshold/thresholdEd25519AuthSession';\nimport { normalizeThresholdEd25519ParticipantIds } from '../../threshold/participants';\n\ntype WarmSigningSessionPolicy = { ttlMs: number; remainingUses: number };\n\ntype ThresholdSessionPlan = {\n sessionKind: 'jwt';\n relayerUrl: string;\n relayerKeyId: string;\n cacheKey: string;\n policy: Awaited<ReturnType<typeof buildThresholdSessionPolicy>>;\n deviceNumber: number;\n};\n\n/**\n * Core login function that handles passkey authentication without React dependencies.\n *\n * - Unlocks the VRF keypair (Shamir 3‑pass auto‑unlock when possible; falls back to TouchID).\n * - Updates local login state and returns success with account/public key info.\n * - Optional: mints a server session when `options.session` is provided.\n * - Generates a fresh, chain‑anchored VRF challenge (using latest block).\n * - Collects a WebAuthn assertion over the VRF output and posts to the relay route\n * (defaults to `/verify-authentication-response`).\n * - When `kind: 'jwt'`, returns the token in `result.jwt`.\n * - When `kind: 'cookie'`, the server sets an HttpOnly cookie and no JWT is returned.\n */\nexport async function loginAndCreateSession(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options?: LoginHooksOptions\n): Promise<LoginAndCreateSessionResult> {\n const { onEvent, onError, afterCall } = options || {};\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 1,\n phase: LoginPhase.STEP_1_PREPARATION,\n status: LoginStatus.PROGRESS,\n message: `Starting login for ${nearAccountId}`\n });\n\n const prevStatus = await webAuthnManager.checkVrfStatus();\n const prevVrfAccountId = prevStatus?.active ? prevStatus.nearAccountId : null;\n\n // If this call activates VRF then fails, clear the partial session.\n const rollbackVrfOnFailure = async () => {\n const status = await webAuthnManager.checkVrfStatus();\n const current = status?.active ? status.nearAccountId : null;\n if (current && current !== prevVrfAccountId) {\n await logoutAndClearSession(context);\n }\n };\n\n try {\n // Validation\n if (!window.isSecureContext) {\n const errorMessage = 'Passkey operations require a secure context (HTTPS or localhost).';\n return await finalizeLoginError({\n message: errorMessage,\n error: new Error(errorMessage),\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callAfterCall: false,\n });\n }\n\n const session = options?.session;\n const wantsServerSession = session !== undefined;\n const deviceNumberHint = parseDeviceNumber(options?.deviceNumber, { min: 1 });\n const base = await handleLoginUnlockVRF(\n context,\n nearAccountId,\n onEvent,\n onError,\n afterCall,\n // Defer final 'login-complete' event & afterCall until warm signing session is minted\n true,\n deviceNumberHint\n );\n // If base login failed, just return\n if (!base.result.success) {\n return base.result;\n }\n\n const preferredDeviceNumber = deviceNumberHint ?? base.activeDeviceNumber;\n const warmPolicy = resolveWarmSigningSessionPolicy(context, options);\n\n const wantsThresholdSession =\n webAuthnManager.getUserPreferences().getSignerMode().mode === 'threshold-signer';\n const relayUrl = (session?.relayUrl || context.configs.relayer.url).trim();\n const verifyRoute = (session?.route || '/verify-authentication-response').trim();\n\n const thresholdPlan = await prepareThresholdSessionPlan({\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n wantsThresholdSession,\n });\n\n const wantsRelayerSession = wantsServerSession || thresholdPlan !== null;\n if (wantsRelayerSession && !relayUrl) {\n console.warn('[login] No relayUrl provided for session-style signing');\n }\n\n if (wantsRelayerSession && relayUrl) {\n return await runRelayerSessionFlow({\n context,\n nearAccountId,\n baseLoginResult: base.result,\n baseUnlockCredential: base.unlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n\n // No relayer session requested (or relayUrl missing): mint/refresh warm signing session only.\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential: base.unlockCredential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult: base.result,\n onEvent,\n afterCall,\n });\n } catch (err: any) {\n const errorMessage =\n getUserFriendlyErrorMessage(err, 'login') || err?.message || 'Login failed';\n return await finalizeLoginError({\n message: errorMessage,\n error: err,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\nfunction resolveWarmSigningSessionPolicy(\n context: PasskeyManagerContext,\n options?: LoginHooksOptions\n): WarmSigningSessionPolicy {\n const defaults = context.configs.signingSessionDefaults;\n return {\n ttlMs: options?.signingSession?.ttlMs ?? defaults.ttlMs,\n remainingUses: options?.signingSession?.remainingUses ?? defaults.remainingUses,\n };\n}\n\nasync function attachSigningSessionStatus(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult } = args;\n if (!loginResult.success) return loginResult;\n try {\n const signingSession: SigningSessionStatus =\n await webAuthnManager.getWarmSigningSessionStatus(nearAccountId);\n return { ...loginResult, signingSession };\n } catch {\n return loginResult;\n }\n}\n\nasync function finalizeLoginSuccess(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n loginResult: LoginResult;\n onEvent?: (event: LoginSSEvent) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const { webAuthnManager, nearAccountId, loginResult, onEvent, afterCall } = args;\n const finalResult = await attachSigningSessionStatus({\n webAuthnManager,\n nearAccountId,\n loginResult,\n });\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId,\n clientNearPublicKey: loginResult.clientNearPublicKey ?? '',\n });\n await afterCall?.(true, finalResult);\n return finalResult;\n}\n\nasync function finalizeLoginError(args: {\n message: string;\n error?: unknown;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n callOnError?: boolean;\n callAfterCall?: boolean;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n message,\n error,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n callOnError = true,\n callAfterCall = true,\n } = args;\n\n try { await rollbackVrfOnFailure(); } catch {}\n\n if (callOnError) {\n onError?.(error as any);\n }\n\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message,\n error: message,\n });\n\n if (callAfterCall) {\n await afterCall?.(false);\n }\n return { success: false, error: message };\n}\n\nasync function prepareThresholdSessionPlan(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n preferredDeviceNumber: number | null;\n relayUrl: string;\n ttlMs: number;\n remainingUses: number;\n wantsThresholdSession: boolean;\n}): Promise<ThresholdSessionPlan | null> {\n const {\n context,\n nearAccountId,\n preferredDeviceNumber,\n relayUrl,\n ttlMs,\n remainingUses,\n wantsThresholdSession,\n } = args;\n if (!wantsThresholdSession || !relayUrl) return null;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) throw new Error('Missing rpId for threshold session');\n\n const lastUser = await webAuthnManager.getLastUser();\n const deviceNumber = preferredDeviceNumber ??\n (lastUser?.nearAccountId === nearAccountId\n ? lastUser.deviceNumber\n : (await IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId))\n ?.deviceNumber ?? null);\n\n if (deviceNumber === null) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n const thresholdKeyMaterial = await IndexedDBManager.nearKeysDB.getThresholdKeyMaterial(\n nearAccountId,\n deviceNumber\n );\n const relayerKeyId = thresholdKeyMaterial?.relayerKeyId || null;\n const participantIds = thresholdKeyMaterial?.participants?.map((p) => p.id) || null;\n const normalizedParticipantIds = normalizeThresholdEd25519ParticipantIds(participantIds);\n\n if (!relayerKeyId) {\n console.warn('[login] threshold-signer configured but no threshold key material found; skipping threshold session');\n return null;\n }\n\n if (!normalizedParticipantIds || normalizedParticipantIds.length < 2) {\n console.warn('[login] threshold key material missing/invalid participantIds; skipping threshold session mint');\n return null;\n }\n\n const policy = await buildThresholdSessionPolicy({\n nearAccountId,\n rpId,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n ttlMs,\n remainingUses,\n });\n\n const cacheKey = makeThresholdEd25519AuthSessionCacheKey({\n nearAccountId,\n rpId,\n relayerUrl: relayUrl,\n relayerKeyId,\n ...(normalizedParticipantIds?.length ? { participantIds: normalizedParticipantIds } : {}),\n });\n\n return {\n sessionKind: 'jwt',\n relayerUrl: relayUrl,\n relayerKeyId,\n cacheKey,\n policy,\n deviceNumber,\n };\n } catch (e: any) {\n console.warn(\n '[login] failed to prepare threshold session policy; skipping threshold session mint:',\n e?.message || e\n );\n return null;\n }\n}\n\nasync function mintThresholdSessionBestEffort(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n plan: ThresholdSessionPlan;\n vrfChallenge: VRFChallenge;\n credential: WebAuthnAuthenticationCredential;\n}): Promise<void> {\n const { context, nearAccountId, plan, vrfChallenge, credential } = args;\n const { webAuthnManager } = context;\n\n let clientVerifyingShareB64u: string | null = null;\n try {\n const localKeyMaterial = await IndexedDBManager.nearKeysDB.getLocalKeyMaterial(\n nearAccountId,\n plan.deviceNumber\n );\n const wrapKeySalt = String(localKeyMaterial?.wrapKeySalt || '').trim();\n if (wrapKeySalt) {\n const derived =\n await webAuthnManager.deriveThresholdEd25519ClientVerifyingShareFromCredential({\n credential,\n nearAccountId,\n wrapKeySalt,\n });\n if (derived.success && derived.clientVerifyingShareB64u) {\n clientVerifyingShareB64u = derived.clientVerifyingShareB64u;\n }\n }\n } catch (e: any) {\n console.warn(\n '[login] failed to derive clientVerifyingShareB64u for threshold session mint:',\n e?.message || e\n );\n }\n\n if (!clientVerifyingShareB64u) {\n console.warn(\n '[login] threshold session mint skipped: missing clientVerifyingShareB64u'\n );\n return;\n }\n\n const minted = await mintThresholdEd25519AuthSession({\n relayerUrl: plan.relayerUrl,\n sessionKind: plan.sessionKind,\n relayerKeyId: plan.relayerKeyId,\n clientVerifyingShareB64u,\n sessionPolicy: plan.policy.policy,\n vrfChallenge,\n webauthnAuthentication: credential,\n });\n\n if (minted.ok && minted.jwt) {\n putCachedThresholdEd25519AuthSession(plan.cacheKey, {\n sessionKind: plan.sessionKind,\n policy: plan.policy.policy,\n policyJson: plan.policy.policyJson,\n sessionPolicyDigest32: plan.policy.sessionPolicyDigest32,\n jwt: minted.jwt,\n ...(minted.expiresAtMs ? { expiresAtMs: minted.expiresAtMs } : {}),\n });\n return;\n }\n\n if (!minted.ok) {\n console.warn(\n '[login] threshold session mint failed:',\n minted.code || minted.message || 'unknown error'\n );\n }\n}\n\nasync function runRelayerSessionFlow(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n baseLoginResult: LoginResult;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n preferredDeviceNumber: number | null;\n session: LoginHooksOptions['session'] | undefined;\n relayUrl: string;\n verifyRoute: string;\n thresholdPlan: ThresholdSessionPlan | null;\n warmPolicy: WarmSigningSessionPolicy;\n rollbackVrfOnFailure: () => Promise<void>;\n onEvent?: (event: LoginSSEvent) => void;\n onError?: (error: Error) => void;\n afterCall?: AfterCall<LoginAndCreateSessionResult>;\n}): Promise<LoginAndCreateSessionResult> {\n const {\n context,\n nearAccountId,\n baseLoginResult,\n baseUnlockCredential,\n preferredDeviceNumber,\n session,\n relayUrl,\n verifyRoute,\n thresholdPlan,\n warmPolicy,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n } = args;\n\n const { webAuthnManager } = context;\n\n try {\n const rpId = webAuthnManager.getRpId();\n if (!rpId) {\n throw new Error('Missing rpId for VRF challenge generation during login');\n }\n\n const blockInfo = await context.nearClient.viewBlock({ finality: 'final' });\n const txBlockHash = blockInfo?.header?.hash;\n const txBlockHeight = String(blockInfo.header?.height ?? '');\n const intentDigest = await computeLoginIntentDigest({ nearAccountId, rpId });\n const vrfChallenge = await webAuthnManager.generateVrfChallengeOnce({\n userId: nearAccountId,\n rpId,\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n intentDigest,\n ...(thresholdPlan ? { sessionPolicyDigest32: thresholdPlan.policy.sessionPolicyDigest32 } : {}),\n });\n\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(\n authenticators,\n preferredDeviceNumber\n );\n const credential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: vrfChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n\n const effectiveLoginResult = await bindVrfToSelectedLoginPasskeyDevice({\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult: baseLoginResult,\n });\n\n await mintWarmSigningSession({\n context,\n nearAccountId,\n onEvent,\n credential,\n ttlMs: warmPolicy.ttlMs,\n remainingUses: warmPolicy.remainingUses,\n });\n\n let serverSessionJwt: string | undefined;\n if (session) {\n const v = await verifyAuthenticationResponse(\n relayUrl,\n verifyRoute,\n session.kind,\n vrfChallenge,\n credential\n );\n if (!v.success || !v.verified) {\n const errMsg = v.error || 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n rollbackVrfOnFailure,\n onEvent,\n afterCall,\n callOnError: false,\n });\n }\n serverSessionJwt = v.jwt;\n }\n\n if (thresholdPlan) {\n await mintThresholdSessionBestEffort({\n context,\n nearAccountId,\n plan: thresholdPlan,\n vrfChallenge,\n credential,\n });\n }\n\n const loginResult: LoginResult = serverSessionJwt\n ? { ...effectiveLoginResult, jwt: serverSessionJwt }\n : effectiveLoginResult;\n\n return await finalizeLoginSuccess({\n webAuthnManager,\n nearAccountId,\n loginResult,\n onEvent,\n afterCall,\n });\n } catch (e: any) {\n console.error('[login] Failed to start session:', e);\n const errMsg =\n getUserFriendlyErrorMessage(e, 'login') ||\n e?.message ||\n 'Session verification failed';\n return await finalizeLoginError({\n message: errMsg,\n error: e,\n rollbackVrfOnFailure,\n onEvent,\n onError,\n afterCall,\n });\n }\n}\n\n/**\n * When multiple passkeys exist for the same account, the user can select any of them in the WebAuthn prompt.\n * If the VRF worker was auto-unlocked using a different device (e.g. Shamir 3-pass based on the \"latest\" row),\n * we must rebind the VRF worker to the same deviceNumber as the selected credential. Otherwise, WrapKeySeed\n * derivation can mix PRF.first(from selected credential) with vrf_sk(from a different device), which later\n * causes vault decryption failures (e.g. `aead::Error` during private key export).\n */\nasync function bindVrfToSelectedLoginPasskeyDevice(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n credential: WebAuthnAuthenticationCredential;\n baseUnlockCredential?: WebAuthnAuthenticationCredential;\n baseLoginResult: LoginResult;\n}): Promise<LoginResult> {\n const {\n webAuthnManager,\n nearAccountId,\n authenticators,\n credential,\n baseUnlockCredential,\n baseLoginResult,\n } = args;\n\n // Start with the base login result (may reflect whichever VRF keypair was auto-unlocked).\n // If the user selects a different passkey below, update it to match the chosen device.\n let effectiveLoginResult = baseLoginResult;\n\n if (authenticators.length <= 1) {\n return effectiveLoginResult;\n }\n\n const rawId = credential.rawId;\n const matched = authenticators.find((a) => a.credentialId === rawId);\n const selectedDeviceNumber = matched?.deviceNumber ?? null;\n\n if (selectedDeviceNumber === null) {\n return effectiveLoginResult;\n }\n\n const userForDevice = await IndexedDBManager.clientDB\n .getUserByDevice(nearAccountId, selectedDeviceNumber)\n .catch(() => null);\n\n if (userForDevice?.clientNearPublicKey) {\n effectiveLoginResult = {\n ...effectiveLoginResult,\n clientNearPublicKey: userForDevice.clientNearPublicKey,\n };\n }\n\n const shouldRebindVrf = !baseUnlockCredential || baseUnlockCredential.rawId !== rawId;\n if (shouldRebindVrf) {\n const shamir = userForDevice?.serverEncryptedVrfKeypair;\n if (!shamir?.ciphertextVrfB64u || !shamir?.kek_s_b64u || !shamir?.serverKeyId) {\n throw new Error(\n `Missing serverEncryptedVrfKeypair for account ${nearAccountId} device ${selectedDeviceNumber}. ` +\n 'Open the wallet once online to refresh local state, then try again.'\n );\n }\n\n const unlock = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n if (!unlock.success) {\n throw new Error(\n unlock.error ||\n 'Failed to bind VRF keypair to the passkey you selected. Please try again.'\n );\n }\n }\n\n // Persist the deviceNumber that the user actually selected so subsequent flows (export/signing)\n // use the correct vault entry.\n await webAuthnManager.setLastUser(nearAccountId, selectedDeviceNumber);\n // Best-effort: stamp the selected device as the one last used for login.\n await webAuthnManager.updateLastLogin(nearAccountId).catch(() => undefined);\n\n return effectiveLoginResult;\n}\n\n/**\n * Mint or refresh a \"warm signing session\" in the VRF worker.\n *\n * Notes:\n * - Reuses a previously collected WebAuthn credential when provided (e.g. TouchID fallback),\n * to avoid prompting the user twice in a single login flow.\n * - Session TTL/remainingUses policy is enforced by the VRF worker.\n */\nasync function mintWarmSigningSession(args: {\n context: PasskeyManagerContext;\n nearAccountId: AccountId;\n onEvent?: (event: LoginSSEvent) => void;\n credential?: WebAuthnAuthenticationCredential;\n ttlMs: number;\n remainingUses: number;\n}): Promise<void> {\n const { context, nearAccountId, onEvent, credential, ttlMs, remainingUses } = args;\n const { webAuthnManager } = context;\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking warm signing session...'\n });\n\n // If we already performed a TouchID ceremony (e.g., VRF unlock fallback),\n // reuse that credential to avoid a second prompt.\n let effectiveCredential = credential;\n if (!effectiveCredential) {\n const challenge = createRandomVRFChallenge();\n const authenticators = await webAuthnManager.getAuthenticatorsByUser(nearAccountId);\n const { authenticatorsForPrompt } = await IndexedDBManager.clientDB.ensureCurrentPasskey(\n nearAccountId,\n authenticators,\n );\n effectiveCredential = await webAuthnManager.getAuthenticationCredentialsSerialized({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n allowCredentials: authenticatorsToAllowCredentials(authenticatorsForPrompt),\n });\n }\n\n await webAuthnManager.mintSigningSessionFromCredential({\n nearAccountId,\n credential: effectiveCredential,\n ttlMs,\n remainingUses,\n });\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'Warm signing session unlocked'\n });\n}\n\n/**\n * Handle onchain (serverless) login using VRF flow per docs/vrf_challenges.md\n *\n * VRF AUTHENTICATION FLOW:\n * 1. Unlock VRF keypair in VRF Worker memory, either\n * - Decrypt via Shamir 3-pass (when Relayer is present), or\n * - Re-derive the VRF via credentials inside VRF worker dynamically\n * 2. Generate VRF challenge using stored VRF keypair + NEAR block data (no TouchID needed)\n * 3. Use VRF output as WebAuthn challenge for authentication\n * 4. Verify VRF proof and WebAuthn response on contract simultaneously\n * - VRF proof assures WebAuthn challenge is fresh and valid (replay protection)\n * - WebAuthn verification for origin + biometric credentials + device authenticity\n */\nasync function handleLoginUnlockVRF(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n onEvent?: (event: LoginSSEvent) => void,\n onError?: (error: Error) => void,\n afterCall?: AfterCall<LoginAndCreateSessionResult>,\n deferCompletionHooks?: boolean,\n deviceNumberHint: number | null = null,\n): Promise<{\n result: LoginResult;\n usedFallbackTouchId: boolean;\n unlockCredential?: WebAuthnAuthenticationCredential;\n activeDeviceNumber: number | null;\n}> {\n const { webAuthnManager } = context;\n\n try {\n // Step 1: Get VRF credentials and authenticators, and validate them\n const hintUserPromise: Promise<ClientUserData | null> =\n deviceNumberHint !== null\n ? webAuthnManager.getUserByDevice(nearAccountId, deviceNumberHint).catch(() => null)\n : Promise.resolve(null);\n\n const [hintUser, lastUser, latestByAccount, authenticators] = await Promise.all([\n hintUserPromise,\n webAuthnManager.getLastUser(),\n IndexedDBManager.clientDB.getLastDBUpdatedUser(nearAccountId),\n webAuthnManager.getAuthenticatorsByUser(nearAccountId),\n ]);\n\n // Prefer the most recently updated record for this account; fall back to lastUser pointer.\n let userData: ClientUserData | null = null;\n if (hintUser && hintUser.nearAccountId === nearAccountId) {\n userData = hintUser;\n } else if (latestByAccount && latestByAccount.nearAccountId === nearAccountId) {\n userData = latestByAccount;\n } else if (lastUser && lastUser.nearAccountId === nearAccountId) {\n userData = lastUser;\n } else {\n userData = await webAuthnManager.getUserByDevice(nearAccountId, 1);\n }\n\n // Validate user data and authenticators\n if (!userData) {\n throw new Error(`User data not found for ${nearAccountId} in IndexedDB. Please register an account.`);\n }\n if (!userData.clientNearPublicKey) {\n throw new Error(`No NEAR public key found for ${nearAccountId}. Please register an account.`);\n }\n if (\n !userData.encryptedVrfKeypair?.encryptedVrfDataB64u ||\n !userData.encryptedVrfKeypair?.chacha20NonceB64u\n ) {\n throw new Error('No VRF credentials found. Please register an account.');\n }\n if (authenticators.length === 0) {\n throw new Error(`No authenticators found for account ${nearAccountId}. Please register.`);\n }\n\n // Step 2: Try Shamir 3-pass commutative unlock first (no TouchID required), fallback to TouchID\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Unlocking VRF keys...'\n });\n\n let unlockResult: { success: boolean; error?: string } = { success: false };\n let usedFallbackTouchId = false;\n let unlockCredential: WebAuthnAuthenticationCredential | undefined;\n let activeDeviceNumber = userData.deviceNumber;\n // Effective user row whose VRF/NEAR keys are actually used for this login.\n // May be switched when multiple devices exist and the user picks a different passkey.\n let effectiveUserData = userData;\n\n const shamir = userData.serverEncryptedVrfKeypair;\n const relayerUrl = context.configs.relayer?.url;\n const useShamir3PassVRFKeyUnlock = !!relayerUrl && !!shamir?.serverKeyId;\n\n if (useShamir3PassVRFKeyUnlock && shamir) {\n try {\n if (!shamir.ciphertextVrfB64u || !shamir.kek_s_b64u) {\n throw new Error('Missing Shamir3Pass fields (ciphertextVrfB64u/kek_s_b64u)');\n }\n\n unlockResult = await webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId,\n kek_s_b64u: shamir.kek_s_b64u,\n ciphertextVrfB64u: shamir.ciphertextVrfB64u,\n serverKeyId: shamir.serverKeyId,\n });\n\n if (unlockResult.success) {\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const active = vrfStatus.active && vrfStatus.nearAccountId === nearAccountId;\n if (!active) {\n unlockResult = { success: false, error: 'VRF session inactive after Shamir3Pass' };\n }\n if (active) {\n // Proactive rotation if serverKeyId changed and we unlocked via Shamir\n await webAuthnManager.maybeProactiveShamirRefresh(nearAccountId);\n }\n } else {\n throw new Error(`Shamir3Pass auto-unlock failed: ${unlockResult.error}`);\n }\n } catch (error: any) {\n unlockResult = { success: false, error: error.message };\n }\n }\n\n // Fallback to TouchID if Shamir3Pass decryption failed\n if (!unlockResult.success) {\n const authenticatorsForPrompt = prioritizeAuthenticatorsByDeviceNumber(authenticators, deviceNumberHint);\n const fallback = await fallbackUnlockVrfKeypairWithTouchId({\n webAuthnManager,\n nearAccountId,\n authenticators: authenticatorsForPrompt,\n userData,\n onEvent,\n });\n unlockResult = fallback.unlockResult;\n usedFallbackTouchId = fallback.usedFallbackTouchId;\n unlockCredential = fallback.unlockCredential;\n effectiveUserData = fallback.effectiveUserData;\n activeDeviceNumber = fallback.activeDeviceNumber;\n }\n\n if (!unlockResult.success) {\n throw new Error(`Failed to unlock VRF keypair: ${unlockResult.error}`);\n }\n\n onEvent?.({\n step: 3,\n phase: LoginPhase.STEP_3_VRF_UNLOCK,\n status: LoginStatus.SUCCESS,\n message: 'VRF keypair unlocked...'\n });\n\n // Proactive refresh: if Shamir3Pass failed and we used TouchID, re-encrypt under current server key\n try {\n const relayerUrl = context.configs.relayer?.url;\n if (usedFallbackTouchId && relayerUrl) {\n const refreshed = await webAuthnManager.shamir3PassEncryptCurrentVrfKeypair();\n await webAuthnManager.updateServerEncryptedVrfKeypair(nearAccountId, refreshed, activeDeviceNumber);\n }\n } catch (refreshErr: any) {\n console.warn('Non-fatal: Failed to refresh serverEncryptedVrfKeypair:', refreshErr?.message || refreshErr);\n }\n\n // Step 3: Update local data and return success\n // Ensure last-user deviceNumber reflects the passkey actually used for login.\n try {\n await webAuthnManager.setLastUser(nearAccountId, activeDeviceNumber);\n } catch {\n // Non-fatal; continue even if last-user update fails.\n }\n await webAuthnManager.updateLastLogin(nearAccountId);\n\n const result: LoginResult = {\n success: true,\n loggedInNearAccountId: nearAccountId,\n // Ensure the clientNearPublicKey reflects the device whose VRF credentials\n // are actually active for this login.\n clientNearPublicKey: effectiveUserData.clientNearPublicKey,\n nearAccountId: nearAccountId\n };\n\n if (!deferCompletionHooks) {\n onEvent?.({\n step: 4,\n phase: LoginPhase.STEP_4_LOGIN_COMPLETE,\n status: LoginStatus.SUCCESS,\n message: 'Login completed successfully',\n nearAccountId: nearAccountId,\n clientNearPublicKey: effectiveUserData.clientNearPublicKey\n });\n afterCall?.(true, result);\n }\n return { result, usedFallbackTouchId, unlockCredential, activeDeviceNumber };\n\n } catch (error: any) {\n // Use centralized error handling\n const errorMessage = getUserFriendlyErrorMessage(error, 'login');\n\n onError?.(error);\n onEvent?.({\n step: 0,\n phase: LoginPhase.LOGIN_ERROR,\n status: LoginStatus.ERROR,\n message: errorMessage,\n error: errorMessage\n });\n\n const result: LoginResult = { success: false, error: errorMessage };\n afterCall?.(false);\n return { result, usedFallbackTouchId: false, activeDeviceNumber: null };\n }\n}\n\n/**\n * TouchID fallback path for VRF unlock.\n *\n * Used when Shamir 3-pass auto-unlock fails/unavailable:\n * - Prompts WebAuthn to obtain a serialized credential with PRF.first + PRF.second.\n * - Aligns the local encrypted VRF keypair blob with the passkey the user actually chose\n * (important when multiple devices/passkeys exist for the same account).\n * - Unlocks the VRF keypair inside the VRF worker and returns updated effective user context.\n */\nasync function fallbackUnlockVrfKeypairWithTouchId(args: {\n webAuthnManager: PasskeyManagerContext['webAuthnManager'];\n nearAccountId: AccountId;\n authenticators: ClientAuthenticatorData[];\n userData: ClientUserData;\n onEvent?: (event: LoginSSEvent) => void;\n}): Promise<{\n unlockResult: { success: boolean; error?: string };\n usedFallbackTouchId: boolean;\n unlockCredential: WebAuthnAuthenticationCredential;\n effectiveUserData: ClientUserData;\n activeDeviceNumber: number;\n}> {\n const { webAuthnManager, nearAccountId, authenticators, userData, onEvent } = args;\n\n onEvent?.({\n step: 2,\n phase: LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n status: LoginStatus.PROGRESS,\n message: 'Logging in, unlocking VRF credentials...'\n });\n\n const challenge = createRandomVRFChallenge();\n const credential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId,\n challenge: challenge as VRFChallenge,\n credentialIds: authenticators.map((a) => a.credentialId),\n });\n\n let effectiveUserData = userData;\n let activeDeviceNumber = userData.deviceNumber;\n\n // If multiple authenticators exist, align VRF credentials with the passkey\n // the user actually chose, based on credentialId → deviceNumber.\n if (authenticators.length > 1) {\n const rawId = credential.rawId;\n const matched = authenticators.find(a => a.credentialId === rawId);\n if (matched) {\n try {\n const byDevice = await IndexedDBManager.clientDB.getUserByDevice(nearAccountId, matched.deviceNumber);\n if (byDevice) {\n effectiveUserData = byDevice;\n activeDeviceNumber = matched.deviceNumber;\n }\n } catch {\n // If lookup by device fails, fall back to the base userData.\n }\n }\n }\n\n const unlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: effectiveUserData.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: effectiveUserData.encryptedVrfKeypair.chacha20NonceB64u,\n },\n credential: credential,\n });\n\n return {\n unlockResult,\n usedFallbackTouchId: unlockResult.success,\n unlockCredential: credential,\n effectiveUserData,\n activeDeviceNumber,\n };\n}\n\n/**\n * High-level login snapshot used by React contexts/UI.\n *\n * Returns:\n * - `login`: derived from IndexedDB last-user pointer + VRF worker status\n * - `signingSession`: warm signing session status when available\n *\n * The `nearAccountId` argument is treated as a \"query hint\" and must match the\n * last logged-in account to be considered logged in (prevents accidental cross-account reads).\n */\nexport async function getLoginSession(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginSession> {\n const login = await getLoginStateInternal(context, nearAccountId);\n if (!login?.isLoggedIn || !login.nearAccountId) return { login, signingSession: null };\n try {\n const signingSession = await context.webAuthnManager.getWarmSigningSessionStatus(login.nearAccountId);\n return { login, signingSession };\n } catch {\n return { login, signingSession: null };\n }\n}\n\n/**\n * Internal helper for computing `LoginState`.\n *\n * Implementation detail:\n * - Trusts the IndexedDB last-user pointer for account selection, then confirms\n * that the VRF worker is actually active for that same account.\n */\nasync function getLoginStateInternal(\n context: PasskeyManagerContext,\n nearAccountId?: AccountId\n): Promise<LoginState> {\n const { webAuthnManager } = context;\n try {\n // Determine target account strictly from the last logged-in device.\n const lastUser = await webAuthnManager.getLastUser();\n const targetAccountId = nearAccountId ?? lastUser?.nearAccountId ?? null;\n\n // If caller requested a specific account, it must match the last logged-in account.\n if (!lastUser || (targetAccountId && lastUser.nearAccountId !== targetAccountId)) {\n return {\n isLoggedIn: false,\n nearAccountId: targetAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n\n const userData = lastUser;\n const publicKey = userData?.clientNearPublicKey || null;\n\n // Check actual VRF worker status\n const vrfStatus = await webAuthnManager.checkVrfStatus();\n const vrfActive = vrfStatus.active && vrfStatus.nearAccountId === targetAccountId;\n\n // Determine if user is considered \"logged in\"\n // User is logged in if they have user data and VRF is active\n const isLoggedIn = !!(userData && userData.clientNearPublicKey && vrfActive);\n\n return {\n isLoggedIn,\n nearAccountId: targetAccountId,\n publicKey,\n vrfActive,\n userData,\n vrfSessionDuration: vrfStatus.sessionDuration || 0\n };\n\n } catch (error: any) {\n console.warn('Error getting login state:', error);\n return {\n isLoggedIn: false,\n nearAccountId: nearAccountId || null,\n publicKey: null,\n vrfActive: false,\n userData: null\n };\n }\n}\n\n/**\n * List recently used accounts from IndexedDB.\n *\n * Used for account picker UIs and initial app bootstrap state.\n */\nexport async function getRecentLogins(\n context: PasskeyManagerContext\n): Promise<GetRecentLoginsResult> {\n const { webAuthnManager } = context;\n // Get all user accounts from IndexDB\n const allUsersData = await webAuthnManager.getAllUsers();\n const accountIds = allUsersData.map(user => user.nearAccountId);\n // Get last used account for initial state\n const lastUsedAccount = await webAuthnManager.getLastUser();\n return {\n accountIds,\n lastUsedAccount,\n };\n}\n\n/**\n * Clear the active VRF session and any client-side nonce caches.\n *\n * This is the canonical \"logout\" operation for the SDK (does not delete accounts).\n */\nexport async function logoutAndClearSession(context: PasskeyManagerContext): Promise<void> {\n const { webAuthnManager } = context;\n await webAuthnManager.clearVrfSession();\n try { webAuthnManager.getNonceManager().clear(); } catch {}\n try { clearAllCachedThresholdEd25519AuthSessions(); } catch {}\n}\n\nfunction prioritizeAuthenticatorsByDeviceNumber(\n authenticators: ClientAuthenticatorData[],\n deviceNumber: number | null\n): ClientAuthenticatorData[] {\n if (authenticators.length <= 1) return authenticators;\n if (deviceNumber === null) return authenticators;\n const preferred = authenticators.filter((a) => a.deviceNumber === deviceNumber);\n if (preferred.length === 0) return authenticators;\n const rest = authenticators.filter((a) => a.deviceNumber !== deviceNumber);\n return [...preferred, ...rest];\n}\n"],"mappings":"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"}
|