@tatchi-xyz/sdk 0.19.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/core/EmailRecovery/index.js +25 -0
- package/dist/cjs/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js +135 -77
- package/dist/cjs/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/index.js +2 -1
- package/dist/cjs/core/TatchiPasskey/index.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/cjs/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/cjs/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/cjs/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/cjs/core/WalletIframe/client/router.js +1 -1
- package/dist/cjs/core/WalletIframe/client/router.js.map +1 -1
- package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
- package/dist/cjs/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/cjs/core/defaultConfigs.js +3 -7
- package/dist/cjs/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/core/nearCrypto.js +29 -5
- package/dist/cjs/core/nearCrypto.js.map +1 -1
- package/dist/cjs/core/rpcCalls.js +56 -26
- package/dist/cjs/core/rpcCalls.js.map +1 -1
- package/dist/cjs/core/types/emailRecovery.js +33 -0
- package/dist/cjs/core/types/emailRecovery.js.map +1 -0
- package/dist/cjs/index.js +4 -0
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → cjs/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
- package/dist/{esm/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → cjs/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
- package/dist/cjs/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/client.js +30 -8
- package/dist/cjs/react/components/PasskeyAuthMenu/client.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +22 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
- package/dist/cjs/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
- package/dist/cjs/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
- package/dist/cjs/react/context/useSDKFlowRuntime.js +183 -0
- package/dist/cjs/react/context/useSDKFlowRuntime.js.map +1 -0
- package/dist/cjs/react/context/useTatchiContextValue.js +24 -15
- package/dist/cjs/react/context/useTatchiContextValue.js.map +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js +96 -0
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -0
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js +26 -0
- package/dist/cjs/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js +135 -77
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js +2 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/cjs/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js +1 -1
- package/dist/cjs/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +3 -4
- package/dist/cjs/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/defaultConfigs.js +3 -7
- package/dist/cjs/react/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/nearCrypto.js +29 -5
- package/dist/cjs/react/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/rpcCalls.js +56 -26
- package/dist/cjs/react/sdk/src/core/rpcCalls.js.map +1 -1
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js +33 -0
- package/dist/cjs/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/cjs/server/email-recovery/emailParsers.js +2 -1
- package/dist/cjs/server/email-recovery/emailParsers.js.map +1 -1
- package/dist/cjs/server/email-recovery/index.js +6 -6
- package/dist/cjs/server/email-recovery/index.js.map +1 -1
- package/dist/cjs/server/email-recovery/rpcCalls.js +22 -3
- package/dist/cjs/server/email-recovery/rpcCalls.js.map +1 -1
- package/dist/cjs/server/router/cloudflare.js +8 -3
- package/dist/cjs/server/router/cloudflare.js.map +1 -1
- package/dist/cjs/server/router/express.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js +2 -4
- package/dist/cjs/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/cjs/server/sdk/src/core/nearCrypto.js +26 -7
- package/dist/cjs/server/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/core/EmailRecovery/index.js +25 -1
- package/dist/esm/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/emailRecovery.js +136 -78
- package/dist/esm/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/index.js +2 -1
- package/dist/esm/core/TatchiPasskey/index.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/esm/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/esm/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/esm/core/WalletIframe/client/router.js +1 -1
- package/dist/esm/core/WalletIframe/client/router.js.map +1 -1
- package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
- package/dist/esm/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/esm/core/defaultConfigs.js +3 -7
- package/dist/esm/core/defaultConfigs.js.map +1 -1
- package/dist/esm/core/nearCrypto.js +24 -6
- package/dist/esm/core/nearCrypto.js.map +1 -1
- package/dist/esm/core/rpcCalls.js +56 -26
- package/dist/esm/core/rpcCalls.js.map +1 -1
- package/dist/esm/core/types/emailRecovery.js +26 -0
- package/dist/esm/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/components/AccountMenuButton/{LinkedDevicesModal-CSSowiHP.css → LinkedDevicesModal-BRtht0XI.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/LinkedDevicesModal-CSSowiHP.css.map → esm/react/components/AccountMenuButton/LinkedDevicesModal-BRtht0XI.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{ProfileDropdown-CEPMZ1gY.css → ProfileDropdown-BG_6hcim.css} +1 -1
- package/dist/{cjs/react/components/AccountMenuButton/ProfileDropdown-CEPMZ1gY.css.map → esm/react/components/AccountMenuButton/ProfileDropdown-BG_6hcim.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css → Web3AuthProfileButton-k8_FAYFq.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/{Web3AuthProfileButton-DopOg7Xc.css.map → Web3AuthProfileButton-k8_FAYFq.css.map} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css → TouchIcon-C-RcGfr5.css} +1 -1
- package/dist/esm/react/components/AccountMenuButton/icons/{TouchIcon-BQWentvJ.css.map → TouchIcon-C-RcGfr5.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css → PasskeyAuthMenu-DKMiLeT9.css} +59 -4
- package/dist/esm/react/components/PasskeyAuthMenu/{PasskeyAuthMenu-DwrzWMYx.css.map → PasskeyAuthMenu-DKMiLeT9.css.map} +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/adapters/tatchi.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/client.js +30 -8
- package/dist/esm/react/components/PasskeyAuthMenu/client.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js +20 -0
- package/dist/esm/react/components/PasskeyAuthMenu/controller/useSDKEvents.js.map +1 -0
- package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js +17 -4
- package/dist/esm/react/components/PasskeyAuthMenu/ui/ContentSwitcher.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js +354 -154
- package/dist/esm/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.js.map +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css → ShowQRCode-CB0UCQ_h.css} +1 -1
- package/dist/esm/react/components/{ShowQRCode-CCN4h6Uv.css.map → ShowQRCode-CB0UCQ_h.css.map} +1 -1
- package/dist/esm/react/context/useSDKFlowRuntime.js +181 -0
- package/dist/esm/react/context/useSDKFlowRuntime.js.map +1 -0
- package/dist/esm/react/context/useTatchiContextValue.js +25 -16
- package/dist/esm/react/context/useTatchiContextValue.js.map +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js +94 -0
- package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -0
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js +25 -1
- package/dist/esm/react/sdk/src/core/EmailRecovery/index.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js +136 -78
- package/dist/esm/react/sdk/src/core/TatchiPasskey/emailRecovery.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js +2 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/index.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js +2 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/linkDevice.js.map +1 -1
- package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js +5 -3
- package/dist/esm/react/sdk/src/core/TatchiPasskey/scanDevice.js.map +1 -1
- package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js +1 -1
- package/dist/esm/react/sdk/src/core/WalletIframe/client/router.js.map +1 -1
- package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js +2 -3
- package/dist/esm/react/sdk/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.js.map +1 -1
- package/dist/esm/react/sdk/src/core/defaultConfigs.js +3 -7
- package/dist/esm/react/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/react/sdk/src/core/nearCrypto.js +24 -6
- package/dist/esm/react/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/react/sdk/src/core/rpcCalls.js +56 -26
- package/dist/esm/react/sdk/src/core/rpcCalls.js.map +1 -1
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js +26 -0
- package/dist/esm/react/sdk/src/core/types/emailRecovery.js.map +1 -0
- package/dist/esm/react/styles/styles.css +58 -3
- package/dist/esm/sdk/{defaultConfigs-DpslkAQd.js → defaultConfigs-CfQDV-ya.js} +3 -7
- package/dist/esm/sdk/{getDeviceNumber-fXizNGQl.js → getDeviceNumber-BpernPnM.js} +4 -8
- package/dist/esm/sdk/getDeviceNumber-BpernPnM.js.map +1 -0
- package/dist/esm/sdk/offline-export-app.js +23 -6
- package/dist/esm/sdk/offline-export-app.js.map +1 -1
- package/dist/esm/sdk/{router-DuGYOd3G.js → router-BWtacLJg.js} +1 -1
- package/dist/esm/sdk/{rpcCalls-BQrJMTdg.js → rpcCalls-CYGJSCgm.js} +3 -3
- package/dist/esm/sdk/{rpcCalls-YVeUVMk2.js → rpcCalls-DZZSa-sk.js} +57 -27
- package/dist/esm/sdk/{transactions-bqaAwL4k.js → transactions-Cn9xTWlK.js} +2 -2
- package/dist/esm/sdk/{transactions-bqaAwL4k.js.map → transactions-Cn9xTWlK.js.map} +1 -1
- package/dist/esm/sdk/{transactions-BalIhtJ9.js → transactions-DfdwDQCn.js} +1 -1
- package/dist/esm/sdk/wallet-iframe-host.js +660 -590
- package/dist/esm/server/email-recovery/emailParsers.js +3 -1
- package/dist/esm/server/email-recovery/emailParsers.js.map +1 -1
- package/dist/esm/server/email-recovery/index.js +6 -6
- package/dist/esm/server/email-recovery/index.js.map +1 -1
- package/dist/esm/server/email-recovery/rpcCalls.js +22 -3
- package/dist/esm/server/email-recovery/rpcCalls.js.map +1 -1
- package/dist/esm/server/router/cloudflare.js +8 -3
- package/dist/esm/server/router/cloudflare.js.map +1 -1
- package/dist/esm/server/router/express.js.map +1 -1
- package/dist/esm/server/sdk/src/core/defaultConfigs.js +2 -4
- package/dist/esm/server/sdk/src/core/defaultConfigs.js.map +1 -1
- package/dist/esm/server/sdk/src/core/nearCrypto.js +26 -8
- package/dist/esm/server/sdk/src/core/nearCrypto.js.map +1 -1
- package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts +8 -0
- package/dist/types/src/core/EmailRecovery/index.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts +8 -5
- package/dist/types/src/core/TatchiPasskey/emailRecovery.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/index.d.ts +1 -1
- package/dist/types/src/core/TatchiPasskey/index.d.ts.map +1 -1
- package/dist/types/src/core/TatchiPasskey/scanDevice.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/TatchiPasskeyIframe.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/client/router.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/client/router.d.ts.map +1 -1
- package/dist/types/src/core/WalletIframe/shared/messages.d.ts +1 -1
- package/dist/types/src/core/WalletIframe/shared/messages.d.ts.map +1 -1
- package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts +2 -1
- package/dist/types/src/core/WebAuthnManager/SignerWorkerManager/handlers/validation.d.ts.map +1 -1
- package/dist/types/src/core/defaultConfigs.d.ts.map +1 -1
- package/dist/types/src/core/nearCrypto.d.ts +14 -0
- package/dist/types/src/core/nearCrypto.d.ts.map +1 -1
- package/dist/types/src/core/rpcCalls.d.ts +11 -8
- package/dist/types/src/core/rpcCalls.d.ts.map +1 -1
- package/dist/types/src/core/types/emailRecovery.d.ts +10 -0
- package/dist/types/src/core/types/emailRecovery.d.ts.map +1 -0
- package/dist/types/src/core/types/index.d.ts +1 -0
- package/dist/types/src/core/types/index.d.ts.map +1 -1
- package/dist/types/src/core/types/tatchi.d.ts +0 -4
- package/dist/types/src/core/types/tatchi.d.ts.map +1 -1
- package/dist/types/src/index.d.ts +1 -0
- package/dist/types/src/index.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts +2 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/adapters/tatchi.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/client.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts +10 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/controller/useSDKEvents.d.ts.map +1 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts +8 -3
- package/dist/types/src/react/components/PasskeyAuthMenu/types.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts +2 -0
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/ContentSwitcher.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/ui/EmailRecoverySlide.d.ts.map +1 -1
- package/dist/types/src/react/context/useSDKFlowRuntime.d.ts +10 -0
- package/dist/types/src/react/context/useSDKFlowRuntime.d.ts.map +1 -0
- package/dist/types/src/react/context/useTatchiContextValue.d.ts.map +1 -1
- package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts +9 -0
- package/dist/types/src/react/context/useTatchiWithSdkFlow.d.ts.map +1 -0
- package/dist/types/src/react/types.d.ts +31 -0
- package/dist/types/src/react/types.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/emailParsers.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/index.d.ts +5 -6
- package/dist/types/src/server/email-recovery/index.d.ts.map +1 -1
- package/dist/types/src/server/email-recovery/rpcCalls.d.ts +1 -0
- package/dist/types/src/server/email-recovery/rpcCalls.d.ts.map +1 -1
- package/dist/types/src/server/router/cloudflare-adaptor.d.ts.map +1 -1
- package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
- package/package.json +1 -1
- package/dist/esm/sdk/getDeviceNumber-fXizNGQl.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linkDevice.js","names":["qrData: DeviceLinkingQRData","error: any","unlockError: any","loginError: any","actions: ActionArgsWasm[]"],"sources":["../../../../src/core/TatchiPasskey/linkDevice.ts"],"sourcesContent":["import { DEVICE_LINKING_CONFIG } from '../../config';\nimport { createNearKeypair } from '../nearCrypto';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport { ActionType, type ActionArgsWasm } from '../types/actions';\nimport { toAccountId, type AccountId } from '../types/accountIds';\nimport {\n VRFChallenge,\n type EncryptedVRFKeypair,\n type ServerEncryptedVrfKeypair\n} from '../types/vrf-worker';\nimport { getLoginSession } from './login';\nimport type { PasskeyManagerContext } from './index';\nimport type { WebAuthnRegistrationCredential } from '../types';\nimport { DEFAULT_WAIT_STATUS } from \"../types/rpc\";\nimport { getDeviceLinkingAccountContractCall } from \"../rpcCalls\";\n\n// Lazy-load QRCode to keep it an optional peer and reduce baseline bundle size\nasync function generateQRCodeDataURL(data: string): Promise<string> {\n const { default: QRCode } = await import('qrcode');\n return QRCode.toDataURL(data, {\n width: 256,\n margin: 2,\n color: {\n dark: '#000000',\n light: '#ffffff'\n },\n errorCorrectionLevel: 'M'\n });\n}\nimport type {\n DeviceLinkingQRData,\n DeviceLinkingSession,\n StartDeviceLinkingOptionsDevice2\n} from '../types/linkDevice';\nimport { DeviceLinkingError, DeviceLinkingErrorCode } from '../types/linkDevice';\nimport { DeviceLinkingPhase, DeviceLinkingStatus } from '../types/sdkSentEvents';\nimport type { DeviceLinkingSSEEvent } from '../types/sdkSentEvents';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\n\n\n/**\n * Device linking flow class - manages the complete device linking process\n *\n * Usage:\n * ```typescript\n * // Device2: Generate QR and start polling\n * const flow = new LinkDeviceFlow(context, options);\n * const { qrData, qrCodeDataURL } = await flow.generateQR(accountId);\n *\n * // Device1: Scan and authorize\n * const result = await LinkDeviceFlow.scanAndLink(context, options);\n *\n * // Device2: Flow automatically completes when AddKey is detected\n * const state = flow.getState();\n * ```\n */\nexport class LinkDeviceFlow {\n private context: PasskeyManagerContext;\n private options: StartDeviceLinkingOptionsDevice2;\n private session: DeviceLinkingSession | null = null;\n private error?: Error;\n // Track explicit cancellation to short-circuit in-flight ops and logs\n private cancelled: boolean = false;\n // AddKey polling\n private pollingInterval?: NodeJS.Timeout;\n private pollGeneration = 0; // invalidate late ticks when stopping/restarting\n private readonly KEY_POLLING_INTERVAL = DEVICE_LINKING_CONFIG.TIMEOUTS.POLLING_INTERVAL_MS;\n // Registration retries\n private registrationRetryTimeout?: NodeJS.Timeout;\n private registrationRetryCount = 0;\n private readonly MAX_REGISTRATION_RETRIES = DEVICE_LINKING_CONFIG.RETRY.MAX_REGISTRATION_ATTEMPTS;\n private readonly RETRY_DELAY_MS = DEVICE_LINKING_CONFIG.TIMEOUTS.REGISTRATION_RETRY_DELAY_MS;\n // Temporary key cleanup\n private tempKeyCleanupTimer?: NodeJS.Timeout;\n private readonly TEMP_KEY_CLEANUP_DELAY_MS = DEVICE_LINKING_CONFIG.TIMEOUTS.TEMP_KEY_CLEANUP_MS;\n\n private get hookOptions() {\n return this.options?.options;\n }\n\n constructor(\n context: PasskeyManagerContext,\n options: StartDeviceLinkingOptionsDevice2\n ) {\n this.context = context;\n this.options = options;\n }\n\n // Guard helpers\n private ifActive<T>(fn: () => T): T | undefined {\n if (this.cancelled) return;\n return fn();\n }\n\n private safeOnEvent(evt: DeviceLinkingSSEEvent) {\n this.ifActive(() => this.hookOptions?.onEvent?.(evt));\n }\n\n /**\n * Device2 (companion device): Generate QR code and start polling for AddKey transaction\n *\n * Single flow:\n * - Generate a temporary NEAR keypair, discover the real account via AddKey mapping,\n * then swap the temporary key for a deterministic key derived from a passkey.\n */\n async generateQR(): Promise<{ qrData: DeviceLinkingQRData; qrCodeDataURL: string }> {\n try {\n // Generate temporary NEAR keypair WITHOUT TouchID/VRF (just for QR generation)\n const tempNearKeyResult = await this.generateTemporaryNearKeypair();\n\n // Create session with null accountId (will be discovered from polling)\n this.session = {\n accountId: null, // Will be discovered from contract polling\n deviceNumber: undefined, // Will be discovered from contract polling\n nearPublicKey: tempNearKeyResult.publicKey,\n credential: null, // Will be generated later when we know real account\n vrfChallenge: null, // Will be generated later\n phase: DeviceLinkingPhase.IDLE,\n createdAt: Date.now(),\n expiresAt: Date.now() + DEVICE_LINKING_CONFIG.TIMEOUTS.SESSION_EXPIRATION_MS,\n tempPrivateKey: tempNearKeyResult.privateKey // Store temp private key for signing later\n };\n\n // Generate QR data (works for both options)\n const qrData: DeviceLinkingQRData = {\n device2PublicKey: this.session.nearPublicKey,\n accountId: this.session.accountId || undefined, // Convert null to undefined for optional field\n timestamp: Date.now(),\n version: '1.0'\n };\n\n // Create QR code data URL\n const qrDataString = JSON.stringify(qrData);\n const qrCodeDataURL = await generateQRCodeDataURL(qrDataString);\n this.safeOnEvent({\n step: 1,\n phase: DeviceLinkingPhase.STEP_1_QR_CODE_GENERATED,\n status: DeviceLinkingStatus.PROGRESS,\n message: `QR code generated for device linking, waiting for Device1 to scan and authorize...`\n });\n\n // Start polling for AddKey transaction (guard if cancelled before reaching here)\n if (!this.cancelled) {\n this.startPolling();\n }\n this.safeOnEvent({\n step: 4,\n phase: DeviceLinkingPhase.STEP_4_POLLING,\n status: DeviceLinkingStatus.PROGRESS,\n message: `Polling contract for linked account...`\n });\n\n return { qrData, qrCodeDataURL };\n\n } catch (error: any) {\n this.error = error;\n this.safeOnEvent({\n step: 0,\n phase: DeviceLinkingPhase.DEVICE_LINKING_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: error.message,\n message: error.message,\n });\n throw new DeviceLinkingError(\n `Failed to generate device linking QR: ${error.message}`,\n DeviceLinkingErrorCode.REGISTRATION_FAILED,\n 'generation'\n );\n }\n }\n\n /**\n * Generate temporary NEAR keypair without TouchID/VRF for Option F flow\n * This creates a proper Ed25519 keypair that can be used for the QR code\n * Includes memory cleanup and automatic expiration\n */\n private async generateTemporaryNearKeypair(): Promise<{ publicKey: string; privateKey: string }> {\n // Generate a temporary random NEAR Ed25519 keypair (browser-safe)\n const { publicKey: publicKeyNear, privateKey: privateKeyNear } = await createNearKeypair();\n // Schedule automatic cleanup of the temporary key from memory\n this.scheduleTemporaryKeyCleanup(publicKeyNear);\n return {\n publicKey: publicKeyNear,\n privateKey: privateKeyNear\n };\n }\n\n /**\n * Schedule automatic cleanup of temporary private key from memory\n * This provides defense-in-depth against memory exposure\n */\n private scheduleTemporaryKeyCleanup(publicKey: string): void {\n // Clear any existing cleanup timer\n if (this.tempKeyCleanupTimer) {\n clearTimeout(this.tempKeyCleanupTimer);\n }\n this.tempKeyCleanupTimer = setTimeout(() => {\n this.cleanupTemporaryKeyFromMemory();\n }, this.TEMP_KEY_CLEANUP_DELAY_MS);\n }\n\n /**\n * Immediately clean up temporary private key from memory\n * Called on successful completion, cancellation, or timeout\n */\n private cleanupTemporaryKeyFromMemory(): void {\n if (this.session?.tempPrivateKey) {\n // Overwrite the private key string with zeros\n const keyLength = this.session.tempPrivateKey.length;\n this.session.tempPrivateKey = '0'.repeat(keyLength);\n // Then set to empty string to release memory\n this.session.tempPrivateKey = '';\n }\n // Clear the cleanup timer\n if (this.tempKeyCleanupTimer) {\n clearTimeout(this.tempKeyCleanupTimer);\n this.tempKeyCleanupTimer = undefined;\n }\n }\n\n /**\n * Device2: Start polling blockchain for AddKey transaction\n */\n private startPolling(): void {\n if (!this.session || this.cancelled) return;\n\n // Stop any existing schedule and invalidate late ticks\n this.stopPolling();\n const myGen = ++this.pollGeneration;\n\n const tick = async () => {\n if (this.cancelled || this.pollGeneration !== myGen) return;\n\n if (!this.shouldContinuePolling()) {\n this.stopPolling();\n return;\n }\n try {\n const hasKeyAdded = await this.checkForDeviceKeyAdded();\n if (this.cancelled || this.pollGeneration !== myGen) return;\n if (hasKeyAdded && this.session) {\n this.stopPolling();\n this.safeOnEvent({\n step: 5,\n phase: DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'AddKey transaction detected, starting registration...'\n });\n this.session.phase = DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED;\n this.startRegistrationWithRetries();\n return;\n }\n } catch (error: any) {\n if (this.cancelled || this.pollGeneration !== myGen) return;\n console.error('Polling error:', error);\n if (error.message?.includes('Account not found')) {\n console.warn('Account not found - stopping polling');\n this.stopPolling();\n return;\n }\n }\n\n if (!this.cancelled && this.pollGeneration === myGen) {\n this.pollingInterval = setTimeout(tick, this.KEY_POLLING_INTERVAL) as any as NodeJS.Timeout;\n }\n };\n\n this.pollingInterval = setTimeout(tick, this.KEY_POLLING_INTERVAL) as any as NodeJS.Timeout;\n }\n\n private shouldContinuePolling(): boolean {\n if (!this.session) return false;\n\n // Stop polling if we've detected AddKey and moved to registration\n if (this.session.phase === DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED) {\n return false;\n }\n // Stop polling if we've completed successfully\n if (this.session.phase === DeviceLinkingPhase.STEP_7_LINKING_COMPLETE) {\n return false;\n }\n if (Date.now() > this.session.expiresAt) {\n this.error = new Error('Session expired');\n this.safeOnEvent({\n step: 0,\n phase: DeviceLinkingPhase.DEVICE_LINKING_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: this.error?.message,\n message: 'Device linking session expired',\n });\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Device2: Check if device key has been added by polling contract HashMap\n */\n private async checkForDeviceKeyAdded(): Promise<boolean> {\n // If polling was cancelled or cleared, bail out early to avoid noisy logs\n if (this.cancelled || !this.pollingInterval) {\n return false;\n }\n if (!this.session?.nearPublicKey) {\n console.error(`LinkDeviceFlow: No session or public key available for polling`);\n return false;\n }\n\n try {\n const linkingResult = await getDeviceLinkingAccountContractCall(\n this.context.nearClient,\n this.context.configs.contractId,\n this.session.nearPublicKey\n );\n\n // Check again after RPC returns in case cancel happened mid-flight\n if (this.cancelled || !this.pollingInterval) {\n return false;\n }\n\n this.safeOnEvent({\n step: 4,\n phase: DeviceLinkingPhase.STEP_4_POLLING,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Polling contract for linked account...'\n });\n\n if (\n linkingResult\n && linkingResult.linkedAccountId\n && linkingResult.deviceNumber !== undefined\n ) {\n // contract returns current deviceNumber, device should be assigned next number\n const currentCounter = parseDeviceNumber(linkingResult.deviceNumber, { min: 0 });\n if (currentCounter === null) {\n console.warn(\n 'LinkDeviceFlow: Invalid deviceNumber counter returned from contract:',\n linkingResult.deviceNumber\n );\n return false;\n }\n const nextDeviceNumber = currentCounter + 1;\n console.debug(`LinkDeviceFlow: Success! Discovered linked account:`, {\n linkedAccountId: linkingResult.linkedAccountId,\n currentCounter,\n nextDeviceNumber: nextDeviceNumber,\n });\n this.session.accountId = linkingResult.linkedAccountId as AccountId;\n this.session.deviceNumber = nextDeviceNumber;\n // Store the next device number for this device\n return true;\n } else {\n if (!this.cancelled) {\n console.log(`LinkDeviceFlow: No mapping found yet...`);\n }\n }\n\n return false;\n } catch (error: any) {\n console.error(`LinkDeviceFlow: Error checking for device key addition:`, {\n error: error.message,\n stack: error.stack,\n name: error.name,\n code: error.code\n });\n\n return false;\n }\n }\n\n /**\n * Device2: Start registration process with retry logic\n */\n private startRegistrationWithRetries(): void {\n this.registrationRetryCount = 0;\n this.attemptRegistration();\n }\n\n /**\n * Device2: Attempt registration with retry logic\n */\n private attemptRegistration(): void {\n this.swapKeysAndRegisterAccount().catch((error: any) => {\n // Check if this is a retryable error\n if (this.isRetryableError(error)) {\n this.registrationRetryCount++;\n\n if (this.registrationRetryCount > this.MAX_REGISTRATION_RETRIES) {\n console.error('LinkDeviceFlow: Max registration retries exceeded, failing permanently');\n // Non-retryable error - fail permanently\n this.session!.phase = DeviceLinkingPhase.REGISTRATION_ERROR;\n this.error = error;\n this.hookOptions?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.REGISTRATION_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: error.message,\n message: error.message,\n });\n } else {\n console.warn(`LinkDeviceFlow: Registration failed with retryable error (attempt ${this.registrationRetryCount}/${this.MAX_REGISTRATION_RETRIES}), will retry in ${this.RETRY_DELAY_MS}ms:`, error.message);\n this.hookOptions?.onEvent?.({\n step: 5,\n phase: DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED,\n status: DeviceLinkingStatus.PROGRESS,\n message: `Registration failed (${error.message}), retrying in ${this.RETRY_DELAY_MS}ms... (${this.registrationRetryCount}/${this.MAX_REGISTRATION_RETRIES})`\n });\n // Schedule retry with setTimeout\n this.registrationRetryTimeout = setTimeout(() => {\n this.attemptRegistration();\n }, this.RETRY_DELAY_MS);\n }\n } else {\n // Non-retryable error - fail permanently\n this.session!.phase = DeviceLinkingPhase.REGISTRATION_ERROR;\n this.error = error;\n this.hookOptions?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.REGISTRATION_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: error.message,\n message: error.message,\n });\n }\n });\n }\n\n /**\n * Device2: Complete device linking\n * 1. Derives deterministic VRF and NEAR keys using real accountID (instead of temporary keys)\n * 2. Executes Key Replacement transaction to replace temporary key with the real key\n * 3. Stores authenticator and VRF data locally, performs on-chain Device2 registration, and then auto-login.\n */\n private async swapKeysAndRegisterAccount(): Promise<void> {\n if (!this.session || !this.session.accountId) {\n throw new Error('AccountID not available for registration');\n }\n\n const realAccountId = this.session.accountId;\n\n this.safeOnEvent({\n step: 6,\n phase: DeviceLinkingPhase.STEP_6_REGISTRATION,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Storing device authenticator data locally...'\n });\n\n // Migrate/generate deterministic VRF credentials for the real account\n const deterministicKeysResult = await this.deriveDeterministicKeysAndRegisterAccount();\n\n if (!deterministicKeysResult) {\n throw new Error('Failed to derive deterministic keys');\n }\n\n if (!deterministicKeysResult.vrfChallenge) {\n throw new Error('Missing VRF challenge from deterministic key derivation');\n }\n\n // Store authenticator data locally on Device2\n await this.storeDeviceAuthenticator(deterministicKeysResult);\n\n // === NEW: Sign Device2 registration WITHOUT new prompt ===\n // Use the credential we already collected to sign the registration transaction\n // This reuses PRF outputs from the stored credential to re-derive WrapKeySeed and sign\n const registrationResult = await this.context.webAuthnManager.signDevice2RegistrationWithStoredKey({\n nearAccountId: realAccountId,\n credential: deterministicKeysResult.credential,\n vrfChallenge: deterministicKeysResult.vrfChallenge,\n deterministicVrfPublicKey: deterministicKeysResult.vrfPublicKey,\n deviceNumber: this.session.deviceNumber!,\n });\n\n if (!registrationResult.success || !registrationResult.signedTransaction) {\n throw new Error(registrationResult.error || 'Device2 registration failed');\n }\n\n // Send the signed registration transaction to NEAR\n await this.context.nearClient.sendTransaction(\n registrationResult.signedTransaction,\n DEFAULT_WAIT_STATUS.linkDeviceRegistration,\n );\n\n this.session.phase = DeviceLinkingPhase.STEP_7_LINKING_COMPLETE;\n this.registrationRetryCount = 0; // Reset retry counter on success\n this.safeOnEvent({\n step: 7,\n phase: DeviceLinkingPhase.STEP_7_LINKING_COMPLETE,\n status: DeviceLinkingStatus.SUCCESS,\n message: 'Device linking completed successfully'\n });\n\n // Auto-login for Device2 after successful device linking\n await this.attemptAutoLogin(deterministicKeysResult, this.options);\n }\n\n /**\n * Check if an error is retryable (temporary issues that can be resolved)\n */\n private isRetryableError(error: any): boolean {\n const retryableErrorMessages = [\n 'page does not have focus',\n 'a request is already pending',\n 'request is already pending',\n 'operationerror',\n 'notallowederror',\n 'the operation is not allowed at this time',\n 'network error',\n 'timeout',\n 'temporary',\n 'transient'\n ];\n\n const errorMessage = error.message?.toLowerCase() || '';\n const errorName = error.name?.toLowerCase() || '';\n\n return retryableErrorMessages.some(msg =>\n errorMessage.includes(msg.toLowerCase()) ||\n errorName.includes(msg.toLowerCase())\n );\n }\n\n /**\n * Device2: Attempt auto-login after successful device linking\n */\n private async attemptAutoLogin(\n deterministicKeysResult?: {\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n nearPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n },\n options?: StartDeviceLinkingOptionsDevice2\n ): Promise<void> {\n try {\n if (this.cancelled) {\n console.warn('LinkDeviceFlow: Auto-login aborted because flow was cancelled');\n return;\n }\n\n const sessionSnapshot = this.session;\n\n // Send additional event after successful auto-login to update React state\n options?.options?.onEvent?.({\n step: 8,\n phase: DeviceLinkingPhase.STEP_8_AUTO_LOGIN,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Logging in...'\n });\n\n if (\n !sessionSnapshot || !sessionSnapshot.accountId ||\n !sessionSnapshot.credential || !deterministicKeysResult\n ) {\n const missing = [];\n if (!sessionSnapshot) missing.push('session');\n if (!sessionSnapshot?.accountId) missing.push('accountId');\n if (!sessionSnapshot?.credential) missing.push('credential');\n if (!deterministicKeysResult) missing.push('deterministicKeysResult');\n throw new Error(`Missing required data for auto-login: ${missing.join(', ')}`);\n }\n\n const { accountId } = sessionSnapshot;\n const deviceNumberRaw = sessionSnapshot.deviceNumber;\n\n if (deviceNumberRaw == null) {\n throw new Error('Device number missing for auto-login');\n }\n\n const deviceNumber = parseDeviceNumber(deviceNumberRaw, { min: 1 });\n if (deviceNumber === null) {\n throw new Error(`Invalid device number for auto-login: ${String(deviceNumberRaw)}`);\n }\n\n // Try Shamir 3-pass unlock first if available\n if (\n deterministicKeysResult.serverEncryptedVrfKeypair &&\n deterministicKeysResult.serverEncryptedVrfKeypair.serverKeyId &&\n this.context.configs.vrfWorkerConfigs?.shamir3pass?.relayServerUrl\n ) {\n try {\n const unlockResult = await this.context.webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId: accountId,\n kek_s_b64u: deterministicKeysResult.serverEncryptedVrfKeypair.kek_s_b64u,\n ciphertextVrfB64u: deterministicKeysResult.serverEncryptedVrfKeypair.ciphertextVrfB64u,\n serverKeyId: deterministicKeysResult.serverEncryptedVrfKeypair.serverKeyId,\n });\n\n if (unlockResult.success) {\n console.log('LinkDeviceFlow: Shamir 3-pass unlock successful for auto-login');\n\n if (this.cancelled) {\n console.warn('LinkDeviceFlow: Auto-login aborted after Shamir unlock because flow was cancelled');\n return;\n }\n\n // Initialize current user after successful VRF unlock\n await this.context.webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n // Ensure last-user device number reflects Device2 for future lookups\n await this.context.webAuthnManager.setLastUser(accountId, deviceNumber);\n\n this.hookOptions?.onEvent?.({\n step: 8,\n phase: DeviceLinkingPhase.STEP_8_AUTO_LOGIN,\n status: DeviceLinkingStatus.SUCCESS,\n message: `Welcome ${accountId}`\n });\n return; // Success, no need to try TouchID\n } else {\n console.log('LinkDeviceFlow: Shamir 3-pass unlock failed, falling back to TouchID');\n }\n } catch (error) {\n console.log('LinkDeviceFlow: Shamir 3-pass unlock error, falling back to TouchID:', error);\n }\n }\n\n // TouchID fallback unlock (no Shamir): unlock VRF keypair directly using the\n // encrypted VRF key from deterministicKeysResult. This ensures that the VRF\n // session in WASM is bound to the correct deterministic VRF key for this device.\n try {\n const nonceManager = this.context.webAuthnManager.getNonceManager();\n const {\n nextNonce: _nextNonce,\n txBlockHash,\n txBlockHeight\n } = await nonceManager.getNonceBlockHashAndHeight(this.context.nearClient);\n\n const authChallenge = await this.context.webAuthnManager.generateVrfChallengeOnce({\n userId: accountId,\n rpId: this.context.webAuthnManager.getRpId(),\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n });\n\n const authenticators = await this.context.webAuthnManager.getAuthenticatorsByUser(accountId);\n const authCredential = await this.context.webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId: accountId,\n challenge: authChallenge,\n credentialIds: authenticators.map((a) => a.credentialId),\n });\n\n const vrfUnlockResult = await this.context.webAuthnManager.unlockVRFKeypair({\n nearAccountId: accountId,\n encryptedVrfKeypair: deterministicKeysResult.encryptedVrfKeypair,\n credential: authCredential,\n });\n\n if (!vrfUnlockResult.success) {\n throw new Error(vrfUnlockResult.error || 'VRF unlock failed during auto-login');\n }\n\n // Initialize current user after successful VRF unlock\n await this.context.webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n // Ensure last-user device number reflects Device2 for future lookups\n await this.context.webAuthnManager.setLastUser(accountId, deviceNumber);\n\n this.hookOptions?.onEvent?.({\n step: 8,\n phase: DeviceLinkingPhase.STEP_8_AUTO_LOGIN,\n status: DeviceLinkingStatus.SUCCESS,\n message: `Welcome ${accountId}`\n });\n // Refresh local login state so downstream consumers pick up the device-specific\n // public key without requiring a manual re-login.\n try { await getLoginSession(this.context, accountId); } catch {}\n } catch (unlockError: any) {\n console.warn('LinkDeviceFlow: TouchID VRF unlock failed during auto-login:', unlockError);\n // Initialize current user even if VRF unlock fails; transactions will surface\n // a clearer error if VRF session is missing.\n await this.context.webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n await this.context.webAuthnManager.setLastUser(accountId, deviceNumber);\n\n this.hookOptions?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.LOGIN_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: unlockError.message,\n message: unlockError.message,\n });\n }\n } catch(loginError: any) {\n console.warn('Login failed after device linking:', loginError);\n // Don't fail the whole linking process if auto-login fails\n options?.options?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.LOGIN_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: loginError.message,\n message: loginError.message,\n });\n }\n }\n\n /**\n * Device2: Store authenticator data locally on Device2\n */\n private async storeDeviceAuthenticator(deterministicKeysResult: {\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n nearPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n } | undefined): Promise<void> {\n if (!this.session || !this.session.accountId) {\n throw new Error('Session or account ID not available for storing authenticator');\n }\n\n try {\n const { webAuthnManager } = this.context;\n const { credential, accountId } = this.session;\n\n // check for credential after migration (should be available for both Option E and F)\n if (!credential) {\n throw new Error('WebAuthn credential not available after VRF migration');\n }\n if (!deterministicKeysResult?.encryptedVrfKeypair) {\n throw new Error('VRF credentials not available after migration');\n }\n\n if (this.session.deviceNumber === undefined || this.session.deviceNumber === null) {\n throw new Error('Device number not available - cannot determine device-specific account ID');\n }\n\n const deviceNumber = parseDeviceNumber(this.session.deviceNumber, { min: 1 });\n if (deviceNumber === null) {\n throw new Error(`Invalid device number in session: ${String(this.session.deviceNumber)}`);\n }\n // Normalize to a number in case something wrote a numeric string.\n this.session.deviceNumber = deviceNumber;\n\n console.debug(\"Storing device authenticator data with device number: \", deviceNumber);\n // Generate device-specific account ID for storage with deviceNumber\n await webAuthnManager.storeUserData({\n nearAccountId: accountId,\n deviceNumber,\n clientNearPublicKey: deterministicKeysResult.nearPublicKey,\n lastUpdated: Date.now(),\n passkeyCredential: {\n id: credential.id,\n rawId: credential.rawId\n },\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: deterministicKeysResult.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: deterministicKeysResult.encryptedVrfKeypair.chacha20NonceB64u,\n },\n serverEncryptedVrfKeypair: deterministicKeysResult.serverEncryptedVrfKeypair || undefined, // Device linking now uses Shamir 3-pass encryption\n });\n\n // Store authenticator with deviceNumber\n const attestationB64u = credential.response.attestationObject;\n const credentialPublicKey = await webAuthnManager.extractCosePublicKey(attestationB64u);\n await webAuthnManager.storeAuthenticator({\n nearAccountId: accountId,\n deviceNumber,\n credentialId: credential.rawId,\n credentialPublicKey,\n transports: ['internal'],\n name: `Device ${deviceNumber} Passkey for ${accountId.split('.')[0]}`,\n registered: new Date().toISOString(),\n syncedAt: new Date().toISOString(),\n vrfPublicKey: deterministicKeysResult.vrfPublicKey,\n });\n\n } catch (error) {\n console.error(`LinkDeviceFlow: Failed to store authenticator data:`, error);\n // Clean up any partial data on failure\n await this.cleanupFailedLinkingAttempt();\n throw error;\n }\n }\n\n /**\n * 1. Derives deterministic VRF and NEAR keys using real accountID (instead of temporary keys)\n * 2. Executes Key Replacement transaction to replace temporary key with the real key\n * 3. Stores authenticator and VRF data; on-chain registration is handled separately via VRF-driven flow.\n */\n private async deriveDeterministicKeysAndRegisterAccount(): Promise<{\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n nearPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n } | undefined> {\n\n if (!this.session || !this.session.accountId) {\n throw new Error('Session account ID not available for migration');\n };\n const realAccountId = this.session.accountId;\n\n // Use secureConfirm to collect passkey with device number inside wallet iframe\n const confirmerText = this.hookOptions?.confirmerText;\n const confirmationConfigOverride = this.hookOptions?.confirmationConfig;\n const confirm = await this.context.webAuthnManager.requestRegistrationCredentialConfirmation({\n nearAccountId: realAccountId,\n deviceNumber: this.session.deviceNumber!,\n confirmerText,\n confirmationConfigOverride,\n });\n if (!confirm.confirmed || !confirm.credential) {\n throw new Error('User cancelled link-device confirmation');\n }\n\n // Store serialized credential and vrf challenge in session\n this.session.credential = confirm.credential;\n this.session.vrfChallenge = confirm.vrfChallenge || null;\n\n // Derive deterministic VRF keypair from PRF output embedded in the credential.\n // This also loads the VRF keypair into the worker's memory (saveInMemory=true by default)\n // and automatically tracks the account ID at the TypeScript level.\n const vrfDerivationResult = await this.context.webAuthnManager.deriveVrfKeypair({\n credential: confirm.credential,\n nearAccountId: realAccountId,\n });\n\n if (!vrfDerivationResult.success || !vrfDerivationResult.encryptedVrfKeypair) {\n throw new Error('Failed to derive VRF keypair from PRF for real account');\n }\n\n // === STEP 1: Generate NEAR keypair (deterministic, no transaction signing) ===\n // Use base account ID for consistent keypair derivation across devices\n const nearKeyResultStep1 = await this.context.webAuthnManager.deriveNearKeypairAndEncryptFromSerialized({\n nearAccountId: realAccountId, // Use base account ID for consistency\n credential: confirm.credential,\n options: { deviceNumber: this.session.deviceNumber },\n // No options - just generate the keypair, don't sign registration tx yet.\n // We need the deterministic NEAR public key to get the nonce for the key replacement transaction first\n // Then once the key replacement transaction is executed, we use the deterministic key\n // to sign the registration transaction\n });\n\n if (!nearKeyResultStep1.success || !nearKeyResultStep1.publicKey) {\n throw new Error('Failed to derive NEAR keypair in step 1');\n }\n\n // === STEP 2: Execute Key Replacement Transaction ===\n this.context.webAuthnManager.getNonceManager().initializeUser(realAccountId, this.session!.nearPublicKey);\n // Initialize NonceManager with current (temporary) on-chain key\n const {\n nextNonce,\n txBlockHash\n } = await this.context.webAuthnManager.getNonceManager()\n .getNonceBlockHashAndHeight(this.context.nearClient);\n\n await this.executeKeySwapTransaction(\n nearKeyResultStep1.publicKey,\n nextNonce,\n txBlockHash\n );\n\n // After the key swap, initialize NonceManager with the newly added deterministic key\n // so future transactions and VRF flows use the correct on-chain key.\n this.context.webAuthnManager.getNonceManager().initializeUser(\n realAccountId,\n nearKeyResultStep1.publicKey\n );\n\n // Clean up any temp account VRF data.\n if (this.session?.tempPrivateKey) {\n try {\n await IndexedDBManager.nearKeysDB.deleteEncryptedKey('temp-device-linking.testnet');\n } catch {}\n // Clean up temporary private key from memory after successful completion\n this.cleanupTemporaryKeyFromMemory();\n }\n\n if (!this.session.credential) {\n throw new Error('WebAuthn credential not available after VRF migration');\n }\n\n // Return all derived values.\n const result = {\n encryptedVrfKeypair: vrfDerivationResult.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: vrfDerivationResult.serverEncryptedVrfKeypair,\n vrfPublicKey: vrfDerivationResult.vrfPublicKey,\n nearPublicKey: nearKeyResultStep1.publicKey,\n credential: this.session.credential,\n vrfChallenge: this.session.vrfChallenge!\n };\n\n return result;\n }\n\n /**\n * Execute key replacement transaction for Option F flow\n * Replace temporary key with properly derived key using AddKey + DeleteKey\n */\n private async executeKeySwapTransaction(\n newPublicKey: string,\n nextNonce: string,\n txBlockHash: string\n ): Promise<void> {\n if (!this.session?.tempPrivateKey || !this.session?.accountId) {\n throw new Error('Missing temporary private key or account ID for key replacement');\n }\n\n const { tempPrivateKey, accountId, nearPublicKey: oldPublicKey } = this.session;\n\n try {\n // Build actions: AddKey new + DeleteKey old\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.AddKey,\n public_key: newPublicKey,\n access_key: JSON.stringify({\n // NEAR-style AccessKey JSON shape: { nonce, permission: { FullAccess: {} } }\n nonce: 0,\n permission: { FullAccess: {} },\n })\n },\n {\n action_type: ActionType.DeleteKey,\n public_key: oldPublicKey\n }\n ];\n\n // Use the webAuthnManager to sign with the temporary private key\n const keySwapResult = await this.context.webAuthnManager.signTransactionWithKeyPair({\n nearPrivateKey: tempPrivateKey,\n signerAccountId: accountId,\n receiverId: accountId,\n nonce: nextNonce,\n blockHash: txBlockHash,\n actions\n });\n\n // Broadcast the transaction\n const txResult = await this.context.nearClient.sendTransaction(\n keySwapResult.signedTransaction,\n DEFAULT_WAIT_STATUS.linkDeviceSwapKey\n );\n // Keep NonceManager in sync for the temporary key that signed this swap\n try {\n await this.context.webAuthnManager.getNonceManager().updateNonceFromBlockchain(\n this.context.nearClient,\n nextNonce\n );\n } catch (e) {\n console.warn('[LinkDeviceFlow]: Failed to update nonce after key swap broadcast:', e);\n }\n\n } catch (error) {\n console.error(`LinkDeviceFlow: Key replacement transaction failed:`, error);\n throw new Error(`Key replacement failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n }\n\n /**\n * Clean up failed linking attempts - remove any partially stored data\n */\n private async cleanupFailedLinkingAttempt(): Promise<void> {\n if (!this.session) {\n return;\n }\n try {\n const { credential, accountId, nearPublicKey } = this.session;\n // Clean up temporary private key from memory first\n this.cleanupTemporaryKeyFromMemory();\n // Remove any authenticator data for both base and device-specific accounts (if they were discovered)\n if (accountId && credential) {\n try { await IndexedDBManager.clientDB.deleteAllAuthenticatorsForUser(accountId); } catch {}\n try { await IndexedDBManager.clientDB.deleteUser(accountId); } catch {}\n try { await IndexedDBManager.nearKeysDB.deleteEncryptedKey(accountId); } catch {}\n }\n // Always clean up temp account VRF data (this is where initial QR generation stores data)\n try { await IndexedDBManager.nearKeysDB.deleteEncryptedKey('temp-device-linking.testnet'); } catch {}\n } catch (error) {\n console.error(`LinkDeviceFlow: Error during cleanup:`, error);\n }\n }\n\n /**\n * Stop polling - guaranteed to clear any existing interval\n */\n private stopPolling(): void {\n if (this.pollingInterval) {\n clearTimeout(this.pollingInterval);\n this.pollingInterval = undefined;\n }\n this.pollGeneration++;\n }\n\n /**\n * Stop registration retry timeout\n */\n private stopRegistrationRetry(): void {\n if (this.registrationRetryTimeout) {\n clearTimeout(this.registrationRetryTimeout);\n this.registrationRetryTimeout = undefined;\n }\n }\n\n /**\n * Get current flow state\n */\n getState() {\n return {\n phase: this.session?.phase,\n session: this.session,\n error: this.error,\n };\n }\n\n /**\n * Cancel the flow and cleanup\n */\n cancel(): void {\n this.cancelled = true;\n this.stopPolling();\n this.stopRegistrationRetry();\n this.cleanupTemporaryKeyFromMemory(); // Clean up temporary private key\n this.session = null;\n this.error = undefined;\n this.registrationRetryCount = 0;\n }\n\n /**\n * Reset flow to initial state\n */\n reset(): void {\n this.cancel();\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"linkDevice.js","names":["qrData: DeviceLinkingQRData","error: any","unlockError: any","loginError: any","actions: ActionArgsWasm[]"],"sources":["../../../../src/core/TatchiPasskey/linkDevice.ts"],"sourcesContent":["import { DEVICE_LINKING_CONFIG } from '../../config';\nimport { createNearKeypair } from '../nearCrypto';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport { ActionType, type ActionArgsWasm } from '../types/actions';\nimport { toAccountId, type AccountId } from '../types/accountIds';\nimport {\n VRFChallenge,\n type EncryptedVRFKeypair,\n type ServerEncryptedVrfKeypair\n} from '../types/vrf-worker';\nimport { getLoginSession } from './login';\nimport type { PasskeyManagerContext } from './index';\nimport type { WebAuthnRegistrationCredential } from '../types';\nimport { DEFAULT_WAIT_STATUS } from \"../types/rpc\";\nimport { getDeviceLinkingAccountContractCall } from \"../rpcCalls\";\n\n// Lazy-load QRCode to keep it an optional peer and reduce baseline bundle size\nasync function generateQRCodeDataURL(data: string): Promise<string> {\n const { default: QRCode } = await import('qrcode');\n return QRCode.toDataURL(data, {\n width: 256,\n margin: 2,\n color: {\n dark: '#000000',\n light: '#ffffff'\n },\n errorCorrectionLevel: 'M'\n });\n}\nimport type {\n DeviceLinkingQRData,\n DeviceLinkingSession,\n StartDeviceLinkingOptionsDevice2\n} from '../types/linkDevice';\nimport { DeviceLinkingError, DeviceLinkingErrorCode } from '../types/linkDevice';\nimport { DeviceLinkingPhase, DeviceLinkingStatus } from '../types/sdkSentEvents';\nimport type { DeviceLinkingSSEEvent } from '../types/sdkSentEvents';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { parseDeviceNumber } from '../WebAuthnManager/SignerWorkerManager/getDeviceNumber';\n\n\n/**\n * Device linking flow class - manages the complete device linking process\n *\n * Usage:\n * ```typescript\n * // Device2: Generate QR and start polling\n * const flow = new LinkDeviceFlow(context, options);\n * const { qrData, qrCodeDataURL } = await flow.generateQR(accountId);\n *\n * // Device1: Scan and authorize\n * const result = await LinkDeviceFlow.scanAndLink(context, options);\n *\n * // Device2: Flow automatically completes when AddKey is detected\n * const state = flow.getState();\n * ```\n */\nexport class LinkDeviceFlow {\n private context: PasskeyManagerContext;\n private options: StartDeviceLinkingOptionsDevice2;\n private session: DeviceLinkingSession | null = null;\n private error?: Error;\n // Track explicit cancellation to short-circuit in-flight ops and logs\n private cancelled: boolean = false;\n // AddKey polling\n private pollingInterval?: NodeJS.Timeout;\n private pollGeneration = 0; // invalidate late ticks when stopping/restarting\n private readonly KEY_POLLING_INTERVAL = DEVICE_LINKING_CONFIG.TIMEOUTS.POLLING_INTERVAL_MS;\n // Registration retries\n private registrationRetryTimeout?: NodeJS.Timeout;\n private registrationRetryCount = 0;\n private readonly MAX_REGISTRATION_RETRIES = DEVICE_LINKING_CONFIG.RETRY.MAX_REGISTRATION_ATTEMPTS;\n private readonly RETRY_DELAY_MS = DEVICE_LINKING_CONFIG.TIMEOUTS.REGISTRATION_RETRY_DELAY_MS;\n // Temporary key cleanup\n private tempKeyCleanupTimer?: NodeJS.Timeout;\n private readonly TEMP_KEY_CLEANUP_DELAY_MS = DEVICE_LINKING_CONFIG.TIMEOUTS.TEMP_KEY_CLEANUP_MS;\n\n private get hookOptions() {\n return this.options?.options;\n }\n\n constructor(\n context: PasskeyManagerContext,\n options: StartDeviceLinkingOptionsDevice2\n ) {\n this.context = context;\n this.options = options;\n }\n\n // Guard helpers\n private ifActive<T>(fn: () => T): T | undefined {\n if (this.cancelled) return;\n return fn();\n }\n\n private safeOnEvent(evt: DeviceLinkingSSEEvent) {\n this.ifActive(() => this.hookOptions?.onEvent?.(evt));\n }\n\n /**\n * Device2 (companion device): Generate QR code and start polling for AddKey transaction\n *\n * Single flow:\n * - Generate a temporary NEAR keypair, discover the real account via AddKey mapping,\n * then swap the temporary key for a deterministic key derived from a passkey.\n */\n async generateQR(): Promise<{ qrData: DeviceLinkingQRData; qrCodeDataURL: string }> {\n try {\n // Generate temporary NEAR keypair WITHOUT TouchID/VRF (just for QR generation)\n const tempNearKeyResult = await this.generateTemporaryNearKeypair();\n\n // Create session with null accountId (will be discovered from polling)\n this.session = {\n accountId: null, // Will be discovered from contract polling\n deviceNumber: undefined, // Will be discovered from contract polling\n nearPublicKey: tempNearKeyResult.publicKey,\n credential: null, // Will be generated later when we know real account\n vrfChallenge: null, // Will be generated later\n phase: DeviceLinkingPhase.IDLE,\n createdAt: Date.now(),\n expiresAt: Date.now() + DEVICE_LINKING_CONFIG.TIMEOUTS.SESSION_EXPIRATION_MS,\n tempPrivateKey: tempNearKeyResult.privateKey // Store temp private key for signing later\n };\n\n // Generate QR data (works for both options)\n const qrData: DeviceLinkingQRData = {\n device2PublicKey: this.session.nearPublicKey,\n accountId: this.session.accountId || undefined, // Convert null to undefined for optional field\n timestamp: Date.now(),\n version: '1.0'\n };\n\n // Create QR code data URL\n const qrDataString = JSON.stringify(qrData);\n const qrCodeDataURL = await generateQRCodeDataURL(qrDataString);\n this.safeOnEvent({\n step: 1,\n phase: DeviceLinkingPhase.STEP_1_QR_CODE_GENERATED,\n status: DeviceLinkingStatus.PROGRESS,\n message: `QR code generated for device linking, waiting for Device1 to scan and authorize...`\n });\n\n // Start polling for AddKey transaction (guard if cancelled before reaching here)\n if (!this.cancelled) {\n this.startPolling();\n }\n this.safeOnEvent({\n step: 4,\n phase: DeviceLinkingPhase.STEP_4_POLLING,\n status: DeviceLinkingStatus.PROGRESS,\n message: `Polling contract for linked account...`\n });\n\n return { qrData, qrCodeDataURL };\n\n } catch (error: any) {\n this.error = error;\n this.safeOnEvent({\n step: 0,\n phase: DeviceLinkingPhase.DEVICE_LINKING_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: error.message,\n message: error.message,\n });\n throw new DeviceLinkingError(\n `Failed to generate device linking QR: ${error.message}`,\n DeviceLinkingErrorCode.REGISTRATION_FAILED,\n 'generation'\n );\n }\n }\n\n /**\n * Generate temporary NEAR keypair without TouchID/VRF for Option F flow\n * This creates a proper Ed25519 keypair that can be used for the QR code\n * Includes memory cleanup and automatic expiration\n */\n private async generateTemporaryNearKeypair(): Promise<{ publicKey: string; privateKey: string }> {\n // Generate a temporary random NEAR Ed25519 keypair (browser-safe)\n const { publicKey: publicKeyNear, privateKey: privateKeyNear } = await createNearKeypair();\n // Schedule automatic cleanup of the temporary key from memory\n this.scheduleTemporaryKeyCleanup(publicKeyNear);\n return {\n publicKey: publicKeyNear,\n privateKey: privateKeyNear\n };\n }\n\n /**\n * Schedule automatic cleanup of temporary private key from memory\n * This provides defense-in-depth against memory exposure\n */\n private scheduleTemporaryKeyCleanup(publicKey: string): void {\n // Clear any existing cleanup timer\n if (this.tempKeyCleanupTimer) {\n clearTimeout(this.tempKeyCleanupTimer);\n }\n this.tempKeyCleanupTimer = setTimeout(() => {\n this.cleanupTemporaryKeyFromMemory();\n }, this.TEMP_KEY_CLEANUP_DELAY_MS);\n }\n\n /**\n * Immediately clean up temporary private key from memory\n * Called on successful completion, cancellation, or timeout\n */\n private cleanupTemporaryKeyFromMemory(): void {\n if (this.session?.tempPrivateKey) {\n // Overwrite the private key string with zeros\n const keyLength = this.session.tempPrivateKey.length;\n this.session.tempPrivateKey = '0'.repeat(keyLength);\n // Then set to empty string to release memory\n this.session.tempPrivateKey = '';\n }\n // Clear the cleanup timer\n if (this.tempKeyCleanupTimer) {\n clearTimeout(this.tempKeyCleanupTimer);\n this.tempKeyCleanupTimer = undefined;\n }\n }\n\n /**\n * Device2: Start polling blockchain for AddKey transaction\n */\n private startPolling(): void {\n if (!this.session || this.cancelled) return;\n\n // Stop any existing schedule and invalidate late ticks\n this.stopPolling();\n const myGen = ++this.pollGeneration;\n\n const tick = async () => {\n if (this.cancelled || this.pollGeneration !== myGen) return;\n\n if (!this.shouldContinuePolling()) {\n this.stopPolling();\n return;\n }\n try {\n const hasKeyAdded = await this.checkForDeviceKeyAdded();\n if (this.cancelled || this.pollGeneration !== myGen) return;\n if (hasKeyAdded && this.session) {\n this.stopPolling();\n this.safeOnEvent({\n step: 5,\n phase: DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'AddKey transaction detected, starting registration...'\n });\n this.session.phase = DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED;\n this.startRegistrationWithRetries();\n return;\n }\n } catch (error: any) {\n if (this.cancelled || this.pollGeneration !== myGen) return;\n console.error('Polling error:', error);\n if (error.message?.includes('Account not found')) {\n console.warn('Account not found - stopping polling');\n this.stopPolling();\n return;\n }\n }\n\n if (!this.cancelled && this.pollGeneration === myGen) {\n this.pollingInterval = setTimeout(tick, this.KEY_POLLING_INTERVAL) as any as NodeJS.Timeout;\n }\n };\n\n this.pollingInterval = setTimeout(tick, this.KEY_POLLING_INTERVAL) as any as NodeJS.Timeout;\n }\n\n private shouldContinuePolling(): boolean {\n if (!this.session) return false;\n\n // Stop polling if we've detected AddKey and moved to registration\n if (this.session.phase === DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED) {\n return false;\n }\n // Stop polling if we've completed successfully\n if (this.session.phase === DeviceLinkingPhase.STEP_7_LINKING_COMPLETE) {\n return false;\n }\n if (Date.now() > this.session.expiresAt) {\n this.error = new Error('Session expired');\n this.safeOnEvent({\n step: 0,\n phase: DeviceLinkingPhase.DEVICE_LINKING_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: this.error?.message,\n message: 'Device linking session expired',\n });\n\n return false;\n }\n\n return true;\n }\n\n /**\n * Device2: Check if device key has been added by polling contract HashMap\n */\n private async checkForDeviceKeyAdded(): Promise<boolean> {\n // If polling was cancelled or cleared, bail out early to avoid noisy logs\n if (this.cancelled || !this.pollingInterval) {\n return false;\n }\n if (!this.session?.nearPublicKey) {\n console.error(`LinkDeviceFlow: No session or public key available for polling`);\n return false;\n }\n\n try {\n const linkingResult = await getDeviceLinkingAccountContractCall(\n this.context.nearClient,\n this.context.configs.contractId,\n this.session.nearPublicKey\n );\n\n // Check again after RPC returns in case cancel happened mid-flight\n if (this.cancelled || !this.pollingInterval) {\n return false;\n }\n\n this.safeOnEvent({\n step: 4,\n phase: DeviceLinkingPhase.STEP_4_POLLING,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Polling contract for linked account...'\n });\n\n if (\n linkingResult\n && linkingResult.linkedAccountId\n && linkingResult.deviceNumber !== undefined\n ) {\n // contract returns current deviceNumber, device should be assigned next number\n const currentCounter = parseDeviceNumber(linkingResult.deviceNumber, { min: 0 });\n if (currentCounter === null) {\n console.warn(\n 'LinkDeviceFlow: Invalid deviceNumber counter returned from contract:',\n linkingResult.deviceNumber\n );\n return false;\n }\n const nextDeviceNumber = currentCounter + 1;\n console.debug(`LinkDeviceFlow: Success! Discovered linked account:`, {\n linkedAccountId: linkingResult.linkedAccountId,\n currentCounter,\n nextDeviceNumber: nextDeviceNumber,\n });\n this.session.accountId = linkingResult.linkedAccountId as AccountId;\n this.session.deviceNumber = nextDeviceNumber;\n // Store the next device number for this device\n return true;\n } else {\n if (!this.cancelled) {\n console.log(`LinkDeviceFlow: No mapping found yet...`);\n }\n }\n\n return false;\n } catch (error: any) {\n console.error(`LinkDeviceFlow: Error checking for device key addition:`, {\n error: error.message,\n stack: error.stack,\n name: error.name,\n code: error.code\n });\n\n return false;\n }\n }\n\n /**\n * Device2: Start registration process with retry logic\n */\n private startRegistrationWithRetries(): void {\n this.registrationRetryCount = 0;\n this.attemptRegistration();\n }\n\n /**\n * Device2: Attempt registration with retry logic\n */\n private attemptRegistration(): void {\n this.swapKeysAndRegisterAccount().catch((error: any) => {\n // Check if this is a retryable error\n if (this.isRetryableError(error)) {\n this.registrationRetryCount++;\n\n if (this.registrationRetryCount > this.MAX_REGISTRATION_RETRIES) {\n console.error('LinkDeviceFlow: Max registration retries exceeded, failing permanently');\n // Non-retryable error - fail permanently\n this.session!.phase = DeviceLinkingPhase.REGISTRATION_ERROR;\n this.error = error;\n this.hookOptions?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.REGISTRATION_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: error.message,\n message: error.message,\n });\n } else {\n console.warn(`LinkDeviceFlow: Registration failed with retryable error (attempt ${this.registrationRetryCount}/${this.MAX_REGISTRATION_RETRIES}), will retry in ${this.RETRY_DELAY_MS}ms:`, error.message);\n this.hookOptions?.onEvent?.({\n step: 5,\n phase: DeviceLinkingPhase.STEP_5_ADDKEY_DETECTED,\n status: DeviceLinkingStatus.PROGRESS,\n message: `Registration failed (${error.message}), retrying in ${this.RETRY_DELAY_MS}ms... (${this.registrationRetryCount}/${this.MAX_REGISTRATION_RETRIES})`\n });\n // Schedule retry with setTimeout\n this.registrationRetryTimeout = setTimeout(() => {\n this.attemptRegistration();\n }, this.RETRY_DELAY_MS);\n }\n } else {\n // Non-retryable error - fail permanently\n this.session!.phase = DeviceLinkingPhase.REGISTRATION_ERROR;\n this.error = error;\n this.hookOptions?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.REGISTRATION_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: error.message,\n message: error.message,\n });\n }\n });\n }\n\n /**\n * Device2: Complete device linking\n * 1. Derives deterministic VRF and NEAR keys using real accountID (instead of temporary keys)\n * 2. Executes Key Replacement transaction to replace temporary key with the real key\n * 3. Stores authenticator and VRF data locally, performs on-chain Device2 registration, and then auto-login.\n */\n private async swapKeysAndRegisterAccount(): Promise<void> {\n if (!this.session || !this.session.accountId) {\n throw new Error('AccountID not available for registration');\n }\n\n const realAccountId = this.session.accountId;\n\n this.safeOnEvent({\n step: 6,\n phase: DeviceLinkingPhase.STEP_6_REGISTRATION,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Storing device authenticator data locally...'\n });\n\n // Migrate/generate deterministic VRF credentials for the real account\n const deterministicKeysResult = await this.deriveDeterministicKeysAndRegisterAccount();\n\n if (!deterministicKeysResult) {\n throw new Error('Failed to derive deterministic keys');\n }\n\n if (!deterministicKeysResult.vrfChallenge) {\n throw new Error('Missing VRF challenge from deterministic key derivation');\n }\n\n // Store authenticator data locally on Device2\n await this.storeDeviceAuthenticator(deterministicKeysResult);\n\n // === NEW: Sign Device2 registration WITHOUT new prompt ===\n // Use the credential we already collected to sign the registration transaction\n // This reuses PRF outputs from the stored credential to re-derive WrapKeySeed and sign\n const registrationResult = await this.context.webAuthnManager.signDevice2RegistrationWithStoredKey({\n nearAccountId: realAccountId,\n credential: deterministicKeysResult.credential,\n vrfChallenge: deterministicKeysResult.vrfChallenge,\n deterministicVrfPublicKey: deterministicKeysResult.vrfPublicKey,\n deviceNumber: this.session.deviceNumber!,\n });\n\n if (!registrationResult.success || !registrationResult.signedTransaction) {\n throw new Error(registrationResult.error || 'Device2 registration failed');\n }\n\n // Send the signed registration transaction to NEAR\n await this.context.nearClient.sendTransaction(\n registrationResult.signedTransaction,\n DEFAULT_WAIT_STATUS.linkDeviceRegistration,\n );\n\n this.session.phase = DeviceLinkingPhase.STEP_7_LINKING_COMPLETE;\n this.registrationRetryCount = 0; // Reset retry counter on success\n this.safeOnEvent({\n step: 7,\n phase: DeviceLinkingPhase.STEP_7_LINKING_COMPLETE,\n status: DeviceLinkingStatus.SUCCESS,\n message: 'Device linking completed successfully'\n });\n\n // Auto-login for Device2 after successful device linking\n await this.attemptAutoLogin(deterministicKeysResult, this.options);\n }\n\n /**\n * Check if an error is retryable (temporary issues that can be resolved)\n */\n private isRetryableError(error: any): boolean {\n const retryableErrorMessages = [\n 'page does not have focus',\n 'a request is already pending',\n 'request is already pending',\n 'operationerror',\n 'notallowederror',\n 'the operation is not allowed at this time',\n 'network error',\n 'timeout',\n 'temporary',\n 'transient'\n ];\n\n const errorMessage = error.message?.toLowerCase() || '';\n const errorName = error.name?.toLowerCase() || '';\n\n return retryableErrorMessages.some(msg =>\n errorMessage.includes(msg.toLowerCase()) ||\n errorName.includes(msg.toLowerCase())\n );\n }\n\n /**\n * Device2: Attempt auto-login after successful device linking\n */\n private async attemptAutoLogin(\n deterministicKeysResult?: {\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n nearPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n },\n options?: StartDeviceLinkingOptionsDevice2\n ): Promise<void> {\n try {\n if (this.cancelled) {\n console.warn('LinkDeviceFlow: Auto-login aborted because flow was cancelled');\n return;\n }\n\n const sessionSnapshot = this.session;\n\n // Send additional event after successful auto-login to update React state\n options?.options?.onEvent?.({\n step: 8,\n phase: DeviceLinkingPhase.STEP_8_AUTO_LOGIN,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Logging in...'\n });\n\n if (\n !sessionSnapshot || !sessionSnapshot.accountId ||\n !sessionSnapshot.credential || !deterministicKeysResult\n ) {\n const missing = [];\n if (!sessionSnapshot) missing.push('session');\n if (!sessionSnapshot?.accountId) missing.push('accountId');\n if (!sessionSnapshot?.credential) missing.push('credential');\n if (!deterministicKeysResult) missing.push('deterministicKeysResult');\n throw new Error(`Missing required data for auto-login: ${missing.join(', ')}`);\n }\n\n const { accountId } = sessionSnapshot;\n const deviceNumberRaw = sessionSnapshot.deviceNumber;\n\n if (deviceNumberRaw == null) {\n throw new Error('Device number missing for auto-login');\n }\n\n const deviceNumber = parseDeviceNumber(deviceNumberRaw, { min: 1 });\n if (deviceNumber === null) {\n throw new Error(`Invalid device number for auto-login: ${String(deviceNumberRaw)}`);\n }\n\n // Try Shamir 3-pass unlock first if available\n if (\n deterministicKeysResult.serverEncryptedVrfKeypair &&\n deterministicKeysResult.serverEncryptedVrfKeypair.serverKeyId &&\n this.context.configs.vrfWorkerConfigs?.shamir3pass?.relayServerUrl\n ) {\n try {\n const unlockResult = await this.context.webAuthnManager.shamir3PassDecryptVrfKeypair({\n nearAccountId: accountId,\n kek_s_b64u: deterministicKeysResult.serverEncryptedVrfKeypair.kek_s_b64u,\n ciphertextVrfB64u: deterministicKeysResult.serverEncryptedVrfKeypair.ciphertextVrfB64u,\n serverKeyId: deterministicKeysResult.serverEncryptedVrfKeypair.serverKeyId,\n });\n\n if (unlockResult.success) {\n console.log('LinkDeviceFlow: Shamir 3-pass unlock successful for auto-login');\n\n if (this.cancelled) {\n console.warn('LinkDeviceFlow: Auto-login aborted after Shamir unlock because flow was cancelled');\n return;\n }\n\n // Initialize current user after successful VRF unlock\n await this.context.webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n // Ensure last-user device number reflects Device2 for future lookups\n await this.context.webAuthnManager.setLastUser(accountId, deviceNumber);\n\n this.hookOptions?.onEvent?.({\n step: 8,\n phase: DeviceLinkingPhase.STEP_8_AUTO_LOGIN,\n status: DeviceLinkingStatus.SUCCESS,\n message: `Welcome ${accountId}`\n });\n return; // Success, no need to try TouchID\n } else {\n console.log('LinkDeviceFlow: Shamir 3-pass unlock failed, falling back to TouchID');\n }\n } catch (error) {\n console.log('LinkDeviceFlow: Shamir 3-pass unlock error, falling back to TouchID:', error);\n }\n }\n\n // TouchID fallback unlock (no Shamir): unlock VRF keypair directly using the\n // encrypted VRF key from deterministicKeysResult. This ensures that the VRF\n // session in WASM is bound to the correct deterministic VRF key for this device.\n try {\n const nonceManager = this.context.webAuthnManager.getNonceManager();\n const {\n nextNonce: _nextNonce,\n txBlockHash,\n txBlockHeight\n } = await nonceManager.getNonceBlockHashAndHeight(this.context.nearClient);\n\n const authChallenge = await this.context.webAuthnManager.generateVrfChallengeOnce({\n userId: accountId,\n rpId: this.context.webAuthnManager.getRpId(),\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n });\n\n const authenticators = await this.context.webAuthnManager.getAuthenticatorsByUser(accountId);\n const authCredential = await this.context.webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId: accountId,\n challenge: authChallenge,\n credentialIds: authenticators.map((a) => a.credentialId),\n });\n\n const vrfUnlockResult = await this.context.webAuthnManager.unlockVRFKeypair({\n nearAccountId: accountId,\n encryptedVrfKeypair: deterministicKeysResult.encryptedVrfKeypair,\n credential: authCredential,\n });\n\n if (!vrfUnlockResult.success) {\n throw new Error(vrfUnlockResult.error || 'VRF unlock failed during auto-login');\n }\n\n // Initialize current user after successful VRF unlock\n await this.context.webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n // Ensure last-user device number reflects Device2 for future lookups\n await this.context.webAuthnManager.setLastUser(accountId, deviceNumber);\n\n this.hookOptions?.onEvent?.({\n step: 8,\n phase: DeviceLinkingPhase.STEP_8_AUTO_LOGIN,\n status: DeviceLinkingStatus.SUCCESS,\n message: `Welcome ${accountId}`\n });\n // Refresh local login state so downstream consumers pick up the device-specific\n // public key without requiring a manual re-login.\n try { await getLoginSession(this.context, accountId); } catch {}\n } catch (unlockError: any) {\n console.warn('LinkDeviceFlow: TouchID VRF unlock failed during auto-login:', unlockError);\n // Initialize current user even if VRF unlock fails; transactions will surface\n // a clearer error if VRF session is missing.\n await this.context.webAuthnManager.initializeCurrentUser(accountId, this.context.nearClient);\n await this.context.webAuthnManager.setLastUser(accountId, deviceNumber);\n\n this.hookOptions?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.LOGIN_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: unlockError.message,\n message: unlockError.message,\n });\n }\n } catch(loginError: any) {\n console.warn('Login failed after device linking:', loginError);\n // Don't fail the whole linking process if auto-login fails\n options?.options?.onEvent?.({\n step: 0,\n phase: DeviceLinkingPhase.LOGIN_ERROR,\n status: DeviceLinkingStatus.ERROR,\n error: loginError.message,\n message: loginError.message,\n });\n }\n }\n\n /**\n * Device2: Store authenticator data locally on Device2\n */\n private async storeDeviceAuthenticator(deterministicKeysResult: {\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n nearPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n } | undefined): Promise<void> {\n if (!this.session || !this.session.accountId) {\n throw new Error('Session or account ID not available for storing authenticator');\n }\n\n try {\n const { webAuthnManager } = this.context;\n const { credential, accountId } = this.session;\n\n // check for credential after migration (should be available for both Option E and F)\n if (!credential) {\n throw new Error('WebAuthn credential not available after VRF migration');\n }\n if (!deterministicKeysResult?.encryptedVrfKeypair) {\n throw new Error('VRF credentials not available after migration');\n }\n\n if (this.session.deviceNumber === undefined || this.session.deviceNumber === null) {\n throw new Error('Device number not available - cannot determine device-specific account ID');\n }\n\n const deviceNumber = parseDeviceNumber(this.session.deviceNumber, { min: 1 });\n if (deviceNumber === null) {\n throw new Error(`Invalid device number in session: ${String(this.session.deviceNumber)}`);\n }\n // Normalize to a number in case something wrote a numeric string.\n this.session.deviceNumber = deviceNumber;\n\n console.debug(\"Storing device authenticator data with device number: \", deviceNumber);\n // Generate device-specific account ID for storage with deviceNumber\n await webAuthnManager.storeUserData({\n nearAccountId: accountId,\n deviceNumber,\n clientNearPublicKey: deterministicKeysResult.nearPublicKey,\n lastUpdated: Date.now(),\n passkeyCredential: {\n id: credential.id,\n rawId: credential.rawId\n },\n encryptedVrfKeypair: {\n encryptedVrfDataB64u: deterministicKeysResult.encryptedVrfKeypair.encryptedVrfDataB64u,\n chacha20NonceB64u: deterministicKeysResult.encryptedVrfKeypair.chacha20NonceB64u,\n },\n serverEncryptedVrfKeypair: deterministicKeysResult.serverEncryptedVrfKeypair || undefined, // Device linking now uses Shamir 3-pass encryption\n });\n\n // Store authenticator with deviceNumber\n const attestationB64u = credential.response.attestationObject;\n const credentialPublicKey = await webAuthnManager.extractCosePublicKey(attestationB64u);\n await webAuthnManager.storeAuthenticator({\n nearAccountId: accountId,\n deviceNumber,\n credentialId: credential.rawId,\n credentialPublicKey,\n transports: ['internal'],\n name: `Device ${deviceNumber} Passkey for ${accountId.split('.')[0]}`,\n registered: new Date().toISOString(),\n syncedAt: new Date().toISOString(),\n vrfPublicKey: deterministicKeysResult.vrfPublicKey,\n });\n\n } catch (error) {\n console.error(`LinkDeviceFlow: Failed to store authenticator data:`, error);\n // Clean up any partial data on failure\n await this.cleanupFailedLinkingAttempt();\n throw error;\n }\n }\n\n /**\n * 1. Derives deterministic VRF and NEAR keys using real accountID (instead of temporary keys)\n * 2. Executes Key Replacement transaction to replace temporary key with the real key\n * 3. Stores authenticator and VRF data; on-chain registration is handled separately via VRF-driven flow.\n */\n private async deriveDeterministicKeysAndRegisterAccount(): Promise<{\n encryptedVrfKeypair: EncryptedVRFKeypair;\n serverEncryptedVrfKeypair: ServerEncryptedVrfKeypair | null;\n vrfPublicKey: string;\n nearPublicKey: string;\n credential: WebAuthnRegistrationCredential;\n vrfChallenge?: VRFChallenge;\n } | undefined> {\n\n if (!this.session || !this.session.accountId) {\n throw new Error('Session account ID not available for migration');\n };\n const realAccountId = this.session.accountId;\n\n // Use secureConfirm to collect passkey with device number inside wallet iframe\n const confirmerText = this.hookOptions?.confirmerText;\n const confirmationConfigOverride = this.hookOptions?.confirmationConfig;\n const confirm = await this.context.webAuthnManager.requestRegistrationCredentialConfirmation({\n nearAccountId: realAccountId,\n deviceNumber: this.session.deviceNumber!,\n confirmerText,\n confirmationConfigOverride,\n });\n if (!confirm.confirmed || !confirm.credential) {\n throw new Error('User cancelled link-device confirmation');\n }\n\n // Store serialized credential and vrf challenge in session\n this.session.credential = confirm.credential;\n this.session.vrfChallenge = confirm.vrfChallenge || null;\n\n // Derive deterministic VRF keypair from PRF output embedded in the credential.\n // This also loads the VRF keypair into the worker's memory (saveInMemory=true by default)\n // and automatically tracks the account ID at the TypeScript level.\n const vrfDerivationResult = await this.context.webAuthnManager.deriveVrfKeypair({\n credential: confirm.credential,\n nearAccountId: realAccountId,\n });\n\n if (!vrfDerivationResult.success || !vrfDerivationResult.encryptedVrfKeypair) {\n throw new Error('Failed to derive VRF keypair from PRF for real account');\n }\n\n // === STEP 1: Generate NEAR keypair (deterministic, no transaction signing) ===\n // Use base account ID for consistent keypair derivation across devices\n const nearKeyResultStep1 = await this.context.webAuthnManager.deriveNearKeypairAndEncryptFromSerialized({\n nearAccountId: realAccountId, // Use base account ID for consistency\n credential: confirm.credential,\n options: { deviceNumber: this.session.deviceNumber },\n // No options - just generate the keypair, don't sign registration tx yet.\n // We need the deterministic NEAR public key to get the nonce for the key replacement transaction first\n // Then once the key replacement transaction is executed, we use the deterministic key\n // to sign the registration transaction\n });\n\n if (!nearKeyResultStep1.success || !nearKeyResultStep1.publicKey) {\n throw new Error('Failed to derive NEAR keypair in step 1');\n }\n\n // === STEP 2: Execute Key Replacement Transaction ===\n this.context.webAuthnManager.getNonceManager().initializeUser(realAccountId, this.session!.nearPublicKey);\n // Initialize NonceManager with current (temporary) on-chain key\n const {\n nextNonce,\n txBlockHash\n } = await this.context.webAuthnManager.getNonceManager()\n .getNonceBlockHashAndHeight(this.context.nearClient);\n\n await this.executeKeySwapTransaction(\n nearKeyResultStep1.publicKey,\n nextNonce,\n txBlockHash\n );\n\n // After the key swap, initialize NonceManager with the newly added deterministic key\n // so future transactions and VRF flows use the correct on-chain key.\n this.context.webAuthnManager.getNonceManager().initializeUser(\n realAccountId,\n nearKeyResultStep1.publicKey\n );\n\n // Clean up any temp account VRF data.\n if (this.session?.tempPrivateKey) {\n try {\n await IndexedDBManager.nearKeysDB.deleteEncryptedKey('temp-device-linking.testnet');\n } catch {}\n // Clean up temporary private key from memory after successful completion\n this.cleanupTemporaryKeyFromMemory();\n }\n\n if (!this.session.credential) {\n throw new Error('WebAuthn credential not available after VRF migration');\n }\n\n // Return all derived values.\n const result = {\n encryptedVrfKeypair: vrfDerivationResult.encryptedVrfKeypair,\n serverEncryptedVrfKeypair: vrfDerivationResult.serverEncryptedVrfKeypair,\n vrfPublicKey: vrfDerivationResult.vrfPublicKey,\n nearPublicKey: nearKeyResultStep1.publicKey,\n credential: this.session.credential,\n vrfChallenge: this.session.vrfChallenge!\n };\n\n return result;\n }\n\n /**\n * Execute key replacement transaction for Option F flow\n * Replace temporary key with properly derived key using AddKey + DeleteKey\n */\n private async executeKeySwapTransaction(\n newPublicKey: string,\n nextNonce: string,\n txBlockHash: string\n ): Promise<void> {\n if (!this.session?.tempPrivateKey || !this.session?.accountId) {\n throw new Error('Missing temporary private key or account ID for key replacement');\n }\n\n const { tempPrivateKey, accountId, nearPublicKey: oldPublicKey } = this.session;\n\n try {\n // Build actions: AddKey new + DeleteKey old\n const actions: ActionArgsWasm[] = [\n {\n action_type: ActionType.AddKey,\n public_key: newPublicKey,\n access_key: JSON.stringify({\n // NEAR-style AccessKey JSON shape: { nonce, permission: { FullAccess: {} } }\n nonce: 0,\n permission: { FullAccess: {} },\n })\n },\n {\n action_type: ActionType.DeleteKey,\n public_key: oldPublicKey\n }\n ];\n\n // Use the webAuthnManager to sign with the temporary private key\n const keySwapResult = await this.context.webAuthnManager.signTransactionWithKeyPair({\n nearPrivateKey: tempPrivateKey,\n signerAccountId: accountId,\n receiverId: accountId,\n nonce: nextNonce,\n blockHash: txBlockHash,\n actions\n });\n\n // Broadcast the transaction\n const txResult = await this.context.nearClient.sendTransaction(\n keySwapResult.signedTransaction,\n DEFAULT_WAIT_STATUS.linkDeviceSwapKey\n );\n // Keep NonceManager in sync for the temporary key that signed this swap\n try {\n await this.context.webAuthnManager.getNonceManager().updateNonceFromBlockchain(\n this.context.nearClient,\n nextNonce\n );\n } catch (e) {\n console.warn('[LinkDeviceFlow]: Failed to update nonce after key swap broadcast:', e);\n }\n\n } catch (error) {\n console.error(`LinkDeviceFlow: Key replacement transaction failed:`, error);\n throw new Error(`Key replacement failed: ${error instanceof Error ? error.message : 'Unknown error'}`);\n }\n }\n\n /**\n * Clean up failed linking attempts - remove any partially stored data\n */\n private async cleanupFailedLinkingAttempt(): Promise<void> {\n if (!this.session) {\n return;\n }\n try {\n const { credential, accountId, nearPublicKey } = this.session;\n // Clean up temporary private key from memory first\n this.cleanupTemporaryKeyFromMemory();\n // Remove any authenticator data for both base and device-specific accounts (if they were discovered)\n if (accountId && credential) {\n try { await IndexedDBManager.clientDB.deleteAllAuthenticatorsForUser(accountId); } catch {}\n try { await IndexedDBManager.clientDB.deleteUser(accountId); } catch {}\n try { await IndexedDBManager.nearKeysDB.deleteEncryptedKey(accountId); } catch {}\n }\n // Always clean up temp account VRF data (this is where initial QR generation stores data)\n try { await IndexedDBManager.nearKeysDB.deleteEncryptedKey('temp-device-linking.testnet'); } catch {}\n } catch (error) {\n console.error(`LinkDeviceFlow: Error during cleanup:`, error);\n }\n }\n\n /**\n * Stop polling - guaranteed to clear any existing interval\n */\n private stopPolling(): void {\n if (this.pollingInterval) {\n clearTimeout(this.pollingInterval);\n this.pollingInterval = undefined;\n }\n this.pollGeneration++;\n }\n\n /**\n * Stop registration retry timeout\n */\n private stopRegistrationRetry(): void {\n if (this.registrationRetryTimeout) {\n clearTimeout(this.registrationRetryTimeout);\n this.registrationRetryTimeout = undefined;\n }\n }\n\n /**\n * Get current flow state\n */\n getState() {\n return {\n phase: this.session?.phase,\n session: this.session,\n error: this.error,\n };\n }\n\n /**\n * Cancel the flow and cleanup\n */\n cancel(): void {\n this.cancelled = true;\n this.stopPolling();\n this.stopRegistrationRetry();\n this.cleanupTemporaryKeyFromMemory(); // Clean up temporary private key\n this.session = null;\n this.error = undefined;\n this.registrationRetryCount = 0;\n }\n\n /**\n * Reset flow to initial state\n */\n reset(): void {\n this.cancel();\n }\n}\n"],"mappings":"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"}
|
|
@@ -2,6 +2,7 @@ import { init_validation, validateNearAccountId } from "../../utils/validation.j
|
|
|
2
2
|
import { DEVICE_LINKING_CONFIG } from "../../config.js";
|
|
3
3
|
import { DeviceLinkingPhase, DeviceLinkingStatus, init_sdkSentEvents } from "../types/sdkSentEvents.js";
|
|
4
4
|
import { executeDeviceLinkingContractCalls, init_rpcCalls } from "../rpcCalls.js";
|
|
5
|
+
import { ensureEd25519Prefix, init_nearCrypto } from "../nearCrypto.js";
|
|
5
6
|
import { getLoginSession, init_login } from "./login.js";
|
|
6
7
|
import { DeviceLinkingError, DeviceLinkingErrorCode } from "../types/linkDevice.js";
|
|
7
8
|
|
|
@@ -10,6 +11,7 @@ init_validation();
|
|
|
10
11
|
init_login();
|
|
11
12
|
init_sdkSentEvents();
|
|
12
13
|
init_rpcCalls();
|
|
14
|
+
init_nearCrypto();
|
|
13
15
|
/**
|
|
14
16
|
* Device1 (original device): Link device using pre-scanned QR data
|
|
15
17
|
*/
|
|
@@ -27,8 +29,8 @@ async function linkDeviceWithScannedQRData(context, qrData, options) {
|
|
|
27
29
|
if (!device1LoginState.isLoggedIn || !device1LoginState.nearAccountId) throw new Error("Device1 must be logged in to authorize device linking");
|
|
28
30
|
const device1AccountId = device1LoginState.nearAccountId;
|
|
29
31
|
const fundingAmount = options.fundingAmount;
|
|
30
|
-
const device2PublicKey = qrData.device2PublicKey;
|
|
31
|
-
if (!device2PublicKey.
|
|
32
|
+
const device2PublicKey = ensureEd25519Prefix(qrData.device2PublicKey);
|
|
33
|
+
if (!device2PublicKey || !/^ed25519:/i.test(device2PublicKey)) throw new Error("Invalid device public key format");
|
|
32
34
|
onEvent?.({
|
|
33
35
|
step: 3,
|
|
34
36
|
phase: DeviceLinkingPhase.STEP_3_AUTHORIZATION,
|
|
@@ -69,7 +71,7 @@ async function linkDeviceWithScannedQRData(context, qrData, options) {
|
|
|
69
71
|
});
|
|
70
72
|
const result = {
|
|
71
73
|
success: true,
|
|
72
|
-
device2PublicKey
|
|
74
|
+
device2PublicKey,
|
|
73
75
|
transactionId: addKeyTxResult?.transaction?.hash || storeDeviceLinkingTxResult?.transaction?.hash || "unknown",
|
|
74
76
|
fundingAmount,
|
|
75
77
|
linkedToAccount: device1AccountId,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanDevice.js","names":["vrfInputData: VRFInputData","error: any"],"sources":["../../../../src/core/TatchiPasskey/scanDevice.ts"],"sourcesContent":["import type { PasskeyManagerContext } from './index';\nimport { validateNearAccountId } from '../../utils/validation';\nimport { getLoginSession } from './login';\nimport type { VRFInputData } from '../types/vrf-worker';\nimport type {\n DeviceLinkingQRData,\n LinkDeviceResult,\n ScanAndLinkDeviceOptionsDevice1,\n} from '../types/linkDevice';\nimport { DeviceLinkingPhase, DeviceLinkingStatus } from '../types/sdkSentEvents';\nimport { DeviceLinkingError, DeviceLinkingErrorCode } from '../types/linkDevice';\nimport { DEVICE_LINKING_CONFIG } from '../../config.js';\nimport { executeDeviceLinkingContractCalls } from '../rpcCalls';\n\n/**\n * Device1 (original device): Link device using pre-scanned QR data\n */\nexport async function linkDeviceWithScannedQRData(\n context: PasskeyManagerContext,\n qrData: DeviceLinkingQRData,\n options: ScanAndLinkDeviceOptionsDevice1\n): Promise<LinkDeviceResult> {\n const { onEvent, onError } = options || {};\n\n try {\n onEvent?.({\n step: 2,\n phase: DeviceLinkingPhase.STEP_2_SCANNING,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Validating QR data...'\n });\n\n // Validate QR data\n validateDeviceLinkingQRData(qrData);\n\n // 3. Get Device1's current account (the account that will receive the new key)\n const { login: device1LoginState } = await getLoginSession(context);\n\n if (!device1LoginState.isLoggedIn || !device1LoginState.nearAccountId) {\n throw new Error('Device1 must be logged in to authorize device linking');\n }\n\n const device1AccountId = device1LoginState.nearAccountId;\n\n // 4. Execute batched transaction: AddKey + Contract notification\n const fundingAmount = options.fundingAmount;\n\n // Parse the device public key for AddKey action\n const device2PublicKey = qrData.device2PublicKey;\n if (!device2PublicKey.
|
|
1
|
+
{"version":3,"file":"scanDevice.js","names":["vrfInputData: VRFInputData","error: any"],"sources":["../../../../src/core/TatchiPasskey/scanDevice.ts"],"sourcesContent":["import type { PasskeyManagerContext } from './index';\nimport { validateNearAccountId } from '../../utils/validation';\nimport { getLoginSession } from './login';\nimport type { VRFInputData } from '../types/vrf-worker';\nimport type {\n DeviceLinkingQRData,\n LinkDeviceResult,\n ScanAndLinkDeviceOptionsDevice1,\n} from '../types/linkDevice';\nimport { DeviceLinkingPhase, DeviceLinkingStatus } from '../types/sdkSentEvents';\nimport { DeviceLinkingError, DeviceLinkingErrorCode } from '../types/linkDevice';\nimport { DEVICE_LINKING_CONFIG } from '../../config.js';\nimport { executeDeviceLinkingContractCalls } from '../rpcCalls';\nimport { ensureEd25519Prefix } from '../nearCrypto';\n\n/**\n * Device1 (original device): Link device using pre-scanned QR data\n */\nexport async function linkDeviceWithScannedQRData(\n context: PasskeyManagerContext,\n qrData: DeviceLinkingQRData,\n options: ScanAndLinkDeviceOptionsDevice1\n): Promise<LinkDeviceResult> {\n const { onEvent, onError } = options || {};\n\n try {\n onEvent?.({\n step: 2,\n phase: DeviceLinkingPhase.STEP_2_SCANNING,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'Validating QR data...'\n });\n\n // Validate QR data\n validateDeviceLinkingQRData(qrData);\n\n // 3. Get Device1's current account (the account that will receive the new key)\n const { login: device1LoginState } = await getLoginSession(context);\n\n if (!device1LoginState.isLoggedIn || !device1LoginState.nearAccountId) {\n throw new Error('Device1 must be logged in to authorize device linking');\n }\n\n const device1AccountId = device1LoginState.nearAccountId;\n\n // 4. Execute batched transaction: AddKey + Contract notification\n const fundingAmount = options.fundingAmount;\n\n // Parse the device public key for AddKey action\n const device2PublicKey = ensureEd25519Prefix(qrData.device2PublicKey);\n if (!device2PublicKey || !/^ed25519:/i.test(device2PublicKey)) {\n throw new Error('Invalid device public key format');\n }\n\n onEvent?.({\n step: 3,\n phase: DeviceLinkingPhase.STEP_3_AUTHORIZATION,\n status: DeviceLinkingStatus.PROGRESS,\n message: `Performing TouchID authentication for device linking...`\n });\n\n const userData = await context.webAuthnManager.getLastUser();\n const nearPublicKeyStr = userData?.clientNearPublicKey;\n if (!nearPublicKeyStr) {\n throw new Error('Client NEAR public key not found in user data');\n }\n // Generate VRF challenge once for both transactions\n const {\n accessKeyInfo,\n nextNonce,\n txBlockHeight,\n txBlockHash\n } = await context.webAuthnManager.getNonceManager().getNonceBlockHashAndHeight(context.nearClient);\n const nextNextNonce = (BigInt(nextNonce) + BigInt(1)).toString();\n const nextNextNextNonce = (BigInt(nextNonce) + BigInt(2)).toString();\n\n const vrfInputData: VRFInputData = {\n userId: device1AccountId,\n rpId: context.webAuthnManager.getRpId(),\n blockHeight: txBlockHeight,\n blockHash: txBlockHash,\n };\n\n const vrfChallenge = await context.webAuthnManager.generateVrfChallengeOnce(vrfInputData);\n\n onEvent?.({\n step: 6,\n phase: DeviceLinkingPhase.STEP_6_REGISTRATION,\n status: DeviceLinkingStatus.PROGRESS,\n message: 'TouchID successful! Signing AddKey transaction...'\n });\n\n // Execute device linking transactions using the centralized RPC function\n const {\n addKeyTxResult,\n storeDeviceLinkingTxResult,\n signedDeleteKeyTransaction\n } = await executeDeviceLinkingContractCalls({\n context,\n device1AccountId,\n device2PublicKey,\n nextNonce,\n nextNextNonce,\n nextNextNextNonce,\n txBlockHash,\n vrfChallenge,\n onEvent,\n confirmationConfigOverride: options?.confirmationConfig,\n confirmerText: options?.confirmerText,\n });\n\n const result = {\n success: true,\n device2PublicKey,\n transactionId: addKeyTxResult?.transaction?.hash\n || storeDeviceLinkingTxResult?.transaction?.hash\n || 'unknown',\n fundingAmount,\n linkedToAccount: device1AccountId, // Include which account the key was added to\n signedDeleteKeyTransaction\n };\n\n onEvent?.({\n step: 6,\n phase: DeviceLinkingPhase.STEP_6_REGISTRATION,\n status: DeviceLinkingStatus.SUCCESS,\n message: `Device2's key added to ${device1AccountId} successfully!`\n });\n\n return result;\n\n } catch (error: any) {\n console.error('LinkDeviceFlow: linkDeviceWithQRData caught error:', error);\n\n const errorMessage = `Failed to scan and link device: ${error.message}`;\n onError?.(new Error(errorMessage));\n\n throw new DeviceLinkingError(\n errorMessage,\n DeviceLinkingErrorCode.AUTHORIZATION_TIMEOUT,\n 'authorization'\n );\n }\n}\n\nexport function validateDeviceLinkingQRData(qrData: DeviceLinkingQRData): void {\n if (!qrData.device2PublicKey) {\n throw new DeviceLinkingError(\n 'Missing device public key',\n DeviceLinkingErrorCode.INVALID_QR_DATA,\n 'authorization'\n );\n }\n\n if (!qrData.timestamp) {\n throw new DeviceLinkingError(\n 'Missing timestamp',\n DeviceLinkingErrorCode.INVALID_QR_DATA,\n 'authorization'\n );\n }\n\n // Check timestamp is not too old (max 15 minutes)\n const maxAge = DEVICE_LINKING_CONFIG.TIMEOUTS.QR_CODE_MAX_AGE_MS;\n if (Date.now() - qrData.timestamp > maxAge) {\n throw new DeviceLinkingError(\n 'QR code expired',\n DeviceLinkingErrorCode.SESSION_EXPIRED,\n 'authorization'\n );\n }\n\n // Account ID is optional - Device2 discovers it from contract logs\n if (qrData.accountId) {\n validateNearAccountId(qrData.accountId);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAkBA,eAAsB,4BACpB,SACA,QACA,SAC2B;CAC3B,MAAM,EAAE,SAAS,YAAY,WAAW;AAExC,KAAI;AACF,YAAU;GACR,MAAM;GACN,OAAO,mBAAmB;GAC1B,QAAQ,oBAAoB;GAC5B,SAAS;;AAIX,8BAA4B;EAG5B,MAAM,EAAE,OAAO,sBAAsB,MAAM,gBAAgB;AAE3D,MAAI,CAAC,kBAAkB,cAAc,CAAC,kBAAkB,cACtD,OAAM,IAAI,MAAM;EAGlB,MAAM,mBAAmB,kBAAkB;EAG3C,MAAM,gBAAgB,QAAQ;EAG9B,MAAM,mBAAmB,oBAAoB,OAAO;AACpD,MAAI,CAAC,oBAAoB,CAAC,aAAa,KAAK,kBAC1C,OAAM,IAAI,MAAM;AAGlB,YAAU;GACR,MAAM;GACN,OAAO,mBAAmB;GAC1B,QAAQ,oBAAoB;GAC5B,SAAS;;EAGX,MAAM,WAAW,MAAM,QAAQ,gBAAgB;EAC/C,MAAM,mBAAmB,UAAU;AACnC,MAAI,CAAC,iBACH,OAAM,IAAI,MAAM;EAGlB,MAAM,EACJ,eACA,WACA,eACA,gBACE,MAAM,QAAQ,gBAAgB,kBAAkB,2BAA2B,QAAQ;EACvF,MAAM,iBAAiB,OAAO,aAAa,OAAO,IAAI;EACtD,MAAM,qBAAqB,OAAO,aAAa,OAAO,IAAI;EAE1D,MAAMA,eAA6B;GACjC,QAAQ;GACR,MAAM,QAAQ,gBAAgB;GAC9B,aAAa;GACb,WAAW;;EAGb,MAAM,eAAe,MAAM,QAAQ,gBAAgB,yBAAyB;AAE5E,YAAU;GACR,MAAM;GACN,OAAO,mBAAmB;GAC1B,QAAQ,oBAAoB;GAC5B,SAAS;;EAIX,MAAM,EACJ,gBACA,4BACA,+BACE,MAAM,kCAAkC;GAC1C;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA;GACA,4BAA4B,SAAS;GACrC,eAAe,SAAS;;EAG1B,MAAM,SAAS;GACb,SAAS;GACT;GACA,eAAe,gBAAgB,aAAa,QACvC,4BAA4B,aAAa,QACzC;GACL;GACA,iBAAiB;GACjB;;AAGF,YAAU;GACR,MAAM;GACN,OAAO,mBAAmB;GAC1B,QAAQ,oBAAoB;GAC5B,SAAS,0BAA0B,iBAAiB;;AAGtD,SAAO;UAEAC,OAAY;AACnB,UAAQ,MAAM,sDAAsD;EAEpE,MAAM,eAAe,mCAAmC,MAAM;AAC9D,YAAU,IAAI,MAAM;AAEpB,QAAM,IAAI,mBACR,cACA,uBAAuB,uBACvB;;;AAKN,SAAgB,4BAA4B,QAAmC;AAC7E,KAAI,CAAC,OAAO,iBACV,OAAM,IAAI,mBACR,6BACA,uBAAuB,iBACvB;AAIJ,KAAI,CAAC,OAAO,UACV,OAAM,IAAI,mBACR,qBACA,uBAAuB,iBACvB;CAKJ,MAAM,SAAS,sBAAsB,SAAS;AAC9C,KAAI,KAAK,QAAQ,OAAO,YAAY,OAClC,OAAM,IAAI,mBACR,mBACA,uBAAuB,iBACvB;AAKJ,KAAI,OAAO,UACT,uBAAsB,OAAO"}
|
|
@@ -658,7 +658,7 @@ var WalletIframeRouter = class {
|
|
|
658
658
|
type: "PM_START_EMAIL_RECOVERY",
|
|
659
659
|
payload: {
|
|
660
660
|
accountId: payload.accountId,
|
|
661
|
-
recoveryEmail: payload.recoveryEmail,
|
|
661
|
+
...payload.recoveryEmail ? { recoveryEmail: payload.recoveryEmail } : {},
|
|
662
662
|
options: safeOptions && Object.keys(safeOptions).length > 0 ? safeOptions : void 0
|
|
663
663
|
},
|
|
664
664
|
options: { onProgress: this.wrapOnEvent(payload.onEvent, isEmailRecoverySSEEvent) }
|