@tatchi-xyz/sdk 0.31.1 → 0.32.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/cjs/core/TatchiPasskey/registration.js +40 -7
- package/dist/cjs/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/cjs/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/cjs/core/types/sdkSentEvents.js +3 -2
- package/dist/cjs/core/types/sdkSentEvents.js.map +1 -1
- package/dist/cjs/react/components/PasskeyAuthMenu/shell.js +2 -44
- package/dist/cjs/react/components/PasskeyAuthMenu/shell.js.map +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js +1 -1
- package/dist/cjs/react/context/useTatchiWithSdkFlow.js.map +1 -1
- package/dist/cjs/react/src/core/TatchiPasskey/registration.js +40 -7
- package/dist/cjs/react/src/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/cjs/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/cjs/react/src/core/types/sdkSentEvents.js +3 -2
- package/dist/cjs/react/src/core/types/sdkSentEvents.js.map +1 -1
- package/dist/esm/core/TatchiPasskey/registration.js +40 -7
- package/dist/esm/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/esm/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/esm/core/types/sdkSentEvents.js +3 -2
- package/dist/esm/core/types/sdkSentEvents.js.map +1 -1
- package/dist/esm/react/components/PasskeyAuthMenu/shell.js +2 -44
- package/dist/esm/react/components/PasskeyAuthMenu/shell.js.map +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js +1 -1
- package/dist/esm/react/context/useTatchiWithSdkFlow.js.map +1 -1
- package/dist/esm/react/src/core/TatchiPasskey/registration.js +40 -7
- package/dist/esm/react/src/core/TatchiPasskey/registration.js.map +1 -1
- package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js +1 -1
- package/dist/esm/react/src/core/WalletIframe/client/on-events-progress-bus.js.map +1 -1
- package/dist/esm/react/src/core/types/sdkSentEvents.js +3 -2
- package/dist/esm/react/src/core/types/sdkSentEvents.js.map +1 -1
- package/dist/esm/sdk/{delegateAction-DdkvFFKA.js → delegateAction-Bq5zkOvn.js} +1 -1
- package/dist/esm/sdk/{emailRecovery-C0LSDleV.js → emailRecovery-B1hbE_sM.js} +3 -3
- package/dist/esm/sdk/{linkDevice-Ds1GNIDk.js → linkDevice-CRPf5aW2.js} +3 -3
- package/dist/esm/sdk/{login-BKhTuGcy.js → login-DUIWZHp_.js} +2 -2
- package/dist/esm/sdk/offline-export-app.js +3 -2
- package/dist/esm/sdk/offline-export-app.js.map +1 -1
- package/dist/esm/sdk/{relay-Dq9D7fhG.js → relay-BCEyWFew.js} +1 -1
- package/dist/esm/sdk/{router-2aGn-CTp.js → router-Cj2WexK-.js} +2 -2
- package/dist/esm/sdk/{rpcCalls-BPI0icZG.js → rpcCalls-C1sp-Epo.js} +2 -2
- package/dist/esm/sdk/{rpcCalls-BW3M_q3-.js → rpcCalls-VL4loDKP.js} +1 -1
- package/dist/esm/sdk/{scanDevice-BBSehlMx.js → scanDevice-C0HcnZym.js} +3 -3
- package/dist/esm/sdk/{sdkSentEvents-CzAZBFjP.js → sdkSentEvents-BfkcI7EN.js} +3 -2
- package/dist/esm/sdk/{signNEP413-DsyWH_Jo.js → signNEP413-lj0swHsD.js} +1 -1
- package/dist/esm/sdk/{syncAccount-DEZHBiRa.js → syncAccount-DnQ9AstS.js} +3 -3
- package/dist/esm/sdk/{syncAccount-DHKtl-xh.js → syncAccount-xh81Vppo.js} +2 -2
- package/dist/esm/sdk/wallet-iframe-host.js +54 -21
- package/dist/esm/wasm_vrf_worker/pkg/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/types/src/core/types/sdkSentEvents.d.ts +18 -7
- package/dist/types/src/core/types/sdkSentEvents.d.ts.map +1 -1
- package/dist/types/src/react/components/PasskeyAuthMenu/shell.d.ts.map +1 -1
- package/dist/workers/offline-export-sw.js +1 -156
- package/dist/workers/wasm_vrf_worker_bg.wasm +0 -0
- package/dist/workers/web3authn-signer.worker.js +2 -1360
- package/dist/workers/web3authn-vrf.worker.js +2 -2857
- package/package.json +4 -4
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"shell.js","names":["React","PasskeyAuthMenu: React.FC<PasskeyAuthMenuProps>","useTheme","PasskeyAuthMenuSkeletonInner","PasskeyAuthMenuThemeScope"],"sources":["../../../../../src/react/components/PasskeyAuthMenu/shell.tsx"],"sourcesContent":["import React from 'react';\nimport { PasskeyAuthMenuSkeletonInner } from './skeleton';\nimport { PasskeyAuthMenuThemeScope } from './themeScope';\nimport type { PasskeyAuthMenuProps } from './types';\nimport { useTheme } from '../theme';\nimport { preloadPasskeyAuthMenu } from './preload';\n\nfunction createClientLazy() {\n return React.lazy(() => import('./client').then((m) => ({ default: m.PasskeyAuthMenuClient })));\n}\n\nclass LazyErrorBoundary extends React.Component<\n {\n fallback: (args: { error: Error; retry: () => void }) => React.ReactNode;\n onRetry: () => void;\n children: React.ReactNode;\n },\n { error: Error | null }\n> {\n state: { error: Error | null } = { error: null };\n\n static getDerivedStateFromError(error: Error): { error: Error } {\n return { error };\n }\n\n retry = () => {\n this.setState({ error: null });\n this.props.onRetry();\n };\n\n render() {\n if (this.state.error) {\n return this.props.fallback({ error: this.state.error, retry: this.retry });\n }\n return this.props.children;\n }\n}\n\n/**\n * `PasskeyAuthMenu` — SSR-safe shell.\n *\n * - Server: renders a skeleton only.\n * - Client: lazy-loads the full implementation after mount.\n */\nexport const PasskeyAuthMenu: React.FC<PasskeyAuthMenuProps> = (props) => {\n const [isClient, setIsClient] = React.useState(false);\n const [retryKey, setRetryKey] = React.useState(0);\n const
|
|
1
|
+
{"version":3,"file":"shell.js","names":["React","PasskeyAuthMenu: React.FC<PasskeyAuthMenuProps>","useTheme","PasskeyAuthMenuSkeletonInner","PasskeyAuthMenuThemeScope"],"sources":["../../../../../src/react/components/PasskeyAuthMenu/shell.tsx"],"sourcesContent":["import React from 'react';\nimport { PasskeyAuthMenuSkeletonInner } from './skeleton';\nimport { PasskeyAuthMenuThemeScope } from './themeScope';\nimport type { PasskeyAuthMenuProps } from './types';\nimport { useTheme } from '../theme';\nimport { preloadPasskeyAuthMenu } from './preload';\n\nfunction createClientLazy() {\n return React.lazy(() => import('./client').then((m) => ({ default: m.PasskeyAuthMenuClient })));\n}\n\nclass LazyErrorBoundary extends React.Component<\n {\n fallback: (args: { error: Error; retry: () => void }) => React.ReactNode;\n onRetry: () => void;\n children: React.ReactNode;\n },\n { error: Error | null }\n> {\n state: { error: Error | null } = { error: null };\n\n static getDerivedStateFromError(error: Error): { error: Error } {\n return { error };\n }\n\n retry = () => {\n this.setState({ error: null });\n this.props.onRetry();\n };\n\n render() {\n if (this.state.error) {\n return this.props.fallback({ error: this.state.error, retry: this.retry });\n }\n return this.props.children;\n }\n}\n\n/**\n * `PasskeyAuthMenu` — SSR-safe shell.\n *\n * - Server: renders a skeleton only.\n * - Client: lazy-loads the full implementation after mount.\n */\nexport const PasskeyAuthMenu: React.FC<PasskeyAuthMenuProps> = (props) => {\n const [isClient, setIsClient] = React.useState(false);\n const [retryKey, setRetryKey] = React.useState(0);\n const ClientLazy = React.useMemo(() => createClientLazy(), [retryKey]);\n\n // Align with the SDK Theme boundary when present (TatchiPasskeyProvider wraps one by default).\n // Falls back to system preference when used standalone.\n const { theme } = useTheme();\n\n React.useEffect(() => {\n setIsClient(true);\n // Start fetching the client chunk immediately; the skeleton remains as the Suspense fallback.\n preloadPasskeyAuthMenu();\n }, []);\n\n const skeleton = (\n <PasskeyAuthMenuSkeletonInner className={props.className} style={props.style} />\n );\n\n return (\n <PasskeyAuthMenuThemeScope theme={theme}>\n {isClient ? (\n <LazyErrorBoundary\n onRetry={() => setRetryKey((k) => k + 1)}\n fallback={({ retry }) => (\n <div>\n {skeleton}\n <div style={{ marginTop: 10, fontSize: 12, textAlign: 'center', opacity: 0.9 }}>\n Failed to load menu.{' '}\n <button type=\"button\" onClick={retry} style={{ textDecoration: 'underline' }}>\n Retry\n </button>\n </div>\n </div>\n )}\n >\n <React.Suspense\n fallback={skeleton}\n >\n <ClientLazy {...props} />\n </React.Suspense>\n </LazyErrorBoundary>\n ) : (\n skeleton\n )}\n </PasskeyAuthMenuThemeScope>\n );\n};\n\nexport default PasskeyAuthMenu;\n"],"mappings":";;;;;;;;;;;;AAOA,SAAS,mBAAmB;AAC1B,QAAOA,cAAM,gDAAW,gBAAmB,MAAM,OAAO,EAAE,SAAS,EAAE;;AAGvE,IAAM,oBAAN,cAAgCA,cAAM,UAOpC;CACA,QAAiC,EAAE,OAAO;CAE1C,OAAO,yBAAyB,OAAgC;AAC9D,SAAO,EAAE;;CAGX,cAAc;AACZ,OAAK,SAAS,EAAE,OAAO;AACvB,OAAK,MAAM;;CAGb,SAAS;AACP,MAAI,KAAK,MAAM,MACb,QAAO,KAAK,MAAM,SAAS;GAAE,OAAO,KAAK,MAAM;GAAO,OAAO,KAAK;;AAEpE,SAAO,KAAK,MAAM;;;;;;;;;AAUtB,MAAaC,mBAAmD,UAAU;CACxE,MAAM,CAAC,UAAU,eAAeD,cAAM,SAAS;CAC/C,MAAM,CAAC,UAAU,eAAeA,cAAM,SAAS;CAC/C,MAAM,aAAaA,cAAM,cAAc,oBAAoB,CAAC;CAI5D,MAAM,EAAE,UAAUE;AAElB,eAAM,gBAAgB;AACpB,cAAY;AAEZ;IACC;CAEH,MAAM,WACJ,2CAACC;EAA6B,WAAW,MAAM;EAAW,OAAO,MAAM;;AAGzE,QACE,2CAACC;EAAiC;YAC/B,WACC,2CAAC;GACC,eAAe,aAAa,MAAM,IAAI;GACtC,WAAW,EAAE,YACX,4CAAC,oBACE,UACD,4CAAC;IAAI,OAAO;KAAE,WAAW;KAAI,UAAU;KAAI,WAAW;KAAU,SAAS;;;KAAO;KACzD;KACrB,2CAAC;MAAO,MAAK;MAAS,SAAS;MAAO,OAAO,EAAE,gBAAgB;gBAAe;;;;aAOpF,2CAACJ,cAAM;IACL,UAAU;cAEV,2CAAC,cAAW,GAAI;;OAIpB;;;AAMR,oBAAe"}
|
|
@@ -34,7 +34,7 @@ function useTatchiWithSdkFlow(args) {
|
|
|
34
34
|
...options,
|
|
35
35
|
onEvent: (event) => {
|
|
36
36
|
appendSdkEventMessage(seq, event.message);
|
|
37
|
-
if (event.phase === require_sdkSentEvents.RegistrationPhase.
|
|
37
|
+
if (event.phase === require_sdkSentEvents.RegistrationPhase.STEP_9_REGISTRATION_COMPLETE && event.status === require_sdkSentEvents.RegistrationStatus.SUCCESS) endSdkFlow("register", seq, "success");
|
|
38
38
|
else if (event.phase === require_sdkSentEvents.RegistrationPhase.REGISTRATION_ERROR || event.status === require_sdkSentEvents.RegistrationStatus.ERROR) {
|
|
39
39
|
const error = "error" in event ? event.error : event.message;
|
|
40
40
|
endSdkFlow("register", seq, "error", error || event.message);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useTatchiWithSdkFlow.js","names":["loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn","wrappedOptions: LoginHooksOptions","LoginPhase","LoginStatus","registerPasskeyWithSdkFlow: RegisterPasskeyFn","wrappedOptions: RegistrationHooksOptions","RegistrationPhase","RegistrationStatus","syncAccountWithSdkFlow: SyncAccountFn","args","options: SyncAccountHooksOptions | undefined","wrappedOptions: SyncAccountHooksOptions","SyncAccountPhase","SyncAccountStatus","value: unknown"],"sources":["../../../../src/react/context/useTatchiWithSdkFlow.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport {\n SyncAccountPhase,\n SyncAccountStatus,\n type SyncAccountHooksOptions,\n type SyncAccountSSEEvent,\n LoginPhase,\n LoginStatus,\n type LoginHooksOptions,\n type LoginSSEvent,\n RegistrationPhase,\n RegistrationStatus,\n type RegistrationHooksOptions,\n type RegistrationSSEEvent,\n} from '@/core/types/sdkSentEvents';\n\nexport function useTatchiWithSdkFlow(args: {\n tatchi: TatchiPasskey;\n beginSdkFlow: (kind: 'login' | 'register' | 'sync', accountId?: string) => number;\n appendSdkEventMessage: (seq: number, message: string) => void;\n endSdkFlow: (kind: 'login' | 'register' | 'sync', seq: number, status: 'success' | 'error', error?: string) => void;\n}): TatchiPasskey {\n const { tatchi, beginSdkFlow, appendSdkEventMessage, endSdkFlow } = args;\n\n return useMemo(() => {\n /**\n * We use a `Proxy` to instrument a few core flow entrypoints (login/register/sync)\n * while preserving the full `TatchiPasskey` API surface.\n *\n * This lets *all* callers (not just PasskeyAuthMenu) use `ctx.tatchi.*` directly and\n * still have `sdkFlow` update as events stream in.\n */\n type LoginAndCreateSessionFn = TatchiPasskey['loginAndCreateSession'];\n type RegisterPasskeyFn = TatchiPasskey['registerPasskey'];\n type SyncAccountFn = TatchiPasskey['syncAccount'];\n\n const loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('login', nearAccountId);\n const wrappedOptions: LoginHooksOptions = {\n ...options,\n onEvent: (event: LoginSSEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (event.phase === LoginPhase.STEP_4_LOGIN_COMPLETE && event.status === LoginStatus.SUCCESS) {\n endSdkFlow('login', seq, 'success');\n } else if (event.phase === LoginPhase.LOGIN_ERROR || event.status === LoginStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('login', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('login', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.loginAndCreateSession(nearAccountId, wrappedOptions);\n };\n\n const registerPasskeyWithSdkFlow: RegisterPasskeyFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('register', nearAccountId);\n const wrappedOptions: RegistrationHooksOptions = {\n ...options,\n onEvent: (event: RegistrationSSEEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (\n event.phase === RegistrationPhase.
|
|
1
|
+
{"version":3,"file":"useTatchiWithSdkFlow.js","names":["loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn","wrappedOptions: LoginHooksOptions","LoginPhase","LoginStatus","registerPasskeyWithSdkFlow: RegisterPasskeyFn","wrappedOptions: RegistrationHooksOptions","RegistrationPhase","RegistrationStatus","syncAccountWithSdkFlow: SyncAccountFn","args","options: SyncAccountHooksOptions | undefined","wrappedOptions: SyncAccountHooksOptions","SyncAccountPhase","SyncAccountStatus","value: unknown"],"sources":["../../../../src/react/context/useTatchiWithSdkFlow.ts"],"sourcesContent":["import { useMemo } from 'react';\nimport type { TatchiPasskey } from '@/core/TatchiPasskey';\nimport {\n SyncAccountPhase,\n SyncAccountStatus,\n type SyncAccountHooksOptions,\n type SyncAccountSSEEvent,\n LoginPhase,\n LoginStatus,\n type LoginHooksOptions,\n type LoginSSEvent,\n RegistrationPhase,\n RegistrationStatus,\n type RegistrationHooksOptions,\n type RegistrationSSEEvent,\n} from '@/core/types/sdkSentEvents';\n\nexport function useTatchiWithSdkFlow(args: {\n tatchi: TatchiPasskey;\n beginSdkFlow: (kind: 'login' | 'register' | 'sync', accountId?: string) => number;\n appendSdkEventMessage: (seq: number, message: string) => void;\n endSdkFlow: (kind: 'login' | 'register' | 'sync', seq: number, status: 'success' | 'error', error?: string) => void;\n}): TatchiPasskey {\n const { tatchi, beginSdkFlow, appendSdkEventMessage, endSdkFlow } = args;\n\n return useMemo(() => {\n /**\n * We use a `Proxy` to instrument a few core flow entrypoints (login/register/sync)\n * while preserving the full `TatchiPasskey` API surface.\n *\n * This lets *all* callers (not just PasskeyAuthMenu) use `ctx.tatchi.*` directly and\n * still have `sdkFlow` update as events stream in.\n */\n type LoginAndCreateSessionFn = TatchiPasskey['loginAndCreateSession'];\n type RegisterPasskeyFn = TatchiPasskey['registerPasskey'];\n type SyncAccountFn = TatchiPasskey['syncAccount'];\n\n const loginAndCreateSessionWithSdkFlow: LoginAndCreateSessionFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('login', nearAccountId);\n const wrappedOptions: LoginHooksOptions = {\n ...options,\n onEvent: (event: LoginSSEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (event.phase === LoginPhase.STEP_4_LOGIN_COMPLETE && event.status === LoginStatus.SUCCESS) {\n endSdkFlow('login', seq, 'success');\n } else if (event.phase === LoginPhase.LOGIN_ERROR || event.status === LoginStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('login', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('login', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.loginAndCreateSession(nearAccountId, wrappedOptions);\n };\n\n const registerPasskeyWithSdkFlow: RegisterPasskeyFn = async (\n nearAccountId,\n options,\n ) => {\n const seq = beginSdkFlow('register', nearAccountId);\n const wrappedOptions: RegistrationHooksOptions = {\n ...options,\n onEvent: (event: RegistrationSSEEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (\n event.phase === RegistrationPhase.STEP_9_REGISTRATION_COMPLETE &&\n event.status === RegistrationStatus.SUCCESS\n ) {\n endSdkFlow('register', seq, 'success');\n } else if (event.phase === RegistrationPhase.REGISTRATION_ERROR || event.status === RegistrationStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('register', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('register', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.registerPasskey(nearAccountId, wrappedOptions);\n };\n\n const syncAccountWithSdkFlow: SyncAccountFn = async (args) => {\n const seq = beginSdkFlow('sync', args?.accountId);\n const options: SyncAccountHooksOptions | undefined = args?.options;\n\n const wrappedOptions: SyncAccountHooksOptions = {\n ...options,\n onEvent: (event: SyncAccountSSEEvent) => {\n appendSdkEventMessage(seq, event.message);\n if (\n event.phase === SyncAccountPhase.STEP_5_SYNC_ACCOUNT_COMPLETE &&\n event.status === SyncAccountStatus.SUCCESS\n ) {\n endSdkFlow('sync', seq, 'success');\n } else if (event.phase === SyncAccountPhase.ERROR || event.status === SyncAccountStatus.ERROR) {\n const error = 'error' in event ? event.error : event.message;\n endSdkFlow('sync', seq, 'error', error || event.message);\n }\n options?.onEvent?.(event);\n },\n onError: (error: Error) => {\n appendSdkEventMessage(seq, error.message);\n endSdkFlow('sync', seq, 'error', error.message);\n options?.onError?.(error);\n },\n };\n\n return await tatchi.syncAccount({\n ...args,\n options: wrappedOptions,\n });\n };\n\n return new Proxy(tatchi, {\n get(target, prop, receiver) {\n if (prop === 'loginAndCreateSession') {\n return loginAndCreateSessionWithSdkFlow;\n }\n\n if (prop === 'registerPasskey') {\n return registerPasskeyWithSdkFlow;\n }\n\n if (prop === 'syncAccount') {\n return syncAccountWithSdkFlow;\n }\n\n const value: unknown = Reflect.get(target as object, prop, receiver);\n // For non-wrapped methods, bind to preserve `this` on the class instance.\n if (typeof value === 'function') return (value as (...args: unknown[]) => unknown).bind(target);\n return value;\n },\n });\n }, [appendSdkEventMessage, beginSdkFlow, endSdkFlow, tatchi]);\n}\n\nexport default useTatchiWithSdkFlow;\n"],"mappings":";;;;;;AAiBA,SAAgB,qBAAqB,MAKnB;CAChB,MAAM,EAAE,QAAQ,cAAc,uBAAuB,eAAe;AAEpE,iCAAqB;EAYnB,MAAMA,mCAA4D,OAChE,eACA,YACG;GACH,MAAM,MAAM,aAAa,SAAS;GAClC,MAAMC,iBAAoC;IACxC,GAAG;IACH,UAAU,UAAwB;AAChC,2BAAsB,KAAK,MAAM;AACjC,SAAI,MAAM,UAAUC,iCAAW,yBAAyB,MAAM,WAAWC,kCAAY,QACnF,YAAW,SAAS,KAAK;cAChB,MAAM,UAAUD,iCAAW,eAAe,MAAM,WAAWC,kCAAY,OAAO;MACvF,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,SAAS,KAAK,SAAS,SAAS,MAAM;;AAEnD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,SAAS,KAAK,SAAS,MAAM;AACxC,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,sBAAsB,eAAe;;EAG3D,MAAMC,6BAAgD,OACpD,eACA,YACG;GACH,MAAM,MAAM,aAAa,YAAY;GACrC,MAAMC,iBAA2C;IAC/C,GAAG;IACH,UAAU,UAAgC;AACxC,2BAAsB,KAAK,MAAM;AACjC,SACE,MAAM,UAAUC,wCAAkB,gCAClC,MAAM,WAAWC,yCAAmB,QAEpC,YAAW,YAAY,KAAK;cACnB,MAAM,UAAUD,wCAAkB,sBAAsB,MAAM,WAAWC,yCAAmB,OAAO;MAC5G,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,YAAY,KAAK,SAAS,SAAS,MAAM;;AAEtD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,YAAY,KAAK,SAAS,MAAM;AAC3C,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,gBAAgB,eAAe;;EAGrD,MAAMC,yBAAwC,OAAO,WAAS;GAC5D,MAAM,MAAM,aAAa,QAAQC,QAAM;GACvC,MAAMC,UAA+CD,QAAM;GAE3D,MAAME,iBAA0C;IAC9C,GAAG;IACH,UAAU,UAA+B;AACvC,2BAAsB,KAAK,MAAM;AACjC,SACE,MAAM,UAAUC,uCAAiB,gCACjC,MAAM,WAAWC,wCAAkB,QAEnC,YAAW,QAAQ,KAAK;cACf,MAAM,UAAUD,uCAAiB,SAAS,MAAM,WAAWC,wCAAkB,OAAO;MAC7F,MAAM,QAAQ,WAAW,QAAQ,MAAM,QAAQ,MAAM;AACrD,iBAAW,QAAQ,KAAK,SAAS,SAAS,MAAM;;AAElD,cAAS,UAAU;;IAErB,UAAU,UAAiB;AACzB,2BAAsB,KAAK,MAAM;AACjC,gBAAW,QAAQ,KAAK,SAAS,MAAM;AACvC,cAAS,UAAU;;;AAIvB,UAAO,MAAM,OAAO,YAAY;IAC9B,GAAGJ;IACH,SAAS;;;AAIb,SAAO,IAAI,MAAM,QAAQ,EACvB,IAAI,QAAQ,MAAM,UAAU;AAC1B,OAAI,SAAS,wBACX,QAAO;AAGT,OAAI,SAAS,kBACX,QAAO;AAGT,OAAI,SAAS,cACX,QAAO;GAGT,MAAMK,QAAiB,QAAQ,IAAI,QAAkB,MAAM;AAE3D,OAAI,OAAO,UAAU,WAAY,QAAQ,MAA0C,KAAK;AACxF,UAAO;;IAGV;EAAC;EAAuB;EAAc;EAAY"}
|
|
@@ -160,8 +160,8 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
160
160
|
onEvent
|
|
161
161
|
}).catch(() => {});
|
|
162
162
|
onEvent?.({
|
|
163
|
-
step:
|
|
164
|
-
phase: require_sdkSentEvents.RegistrationPhase.
|
|
163
|
+
step: 8,
|
|
164
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_8_DATABASE_STORAGE,
|
|
165
165
|
status: require_sdkSentEvents.RegistrationStatus.PROGRESS,
|
|
166
166
|
message: "Storing passkey wallet metadata..."
|
|
167
167
|
});
|
|
@@ -175,8 +175,8 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
175
175
|
});
|
|
176
176
|
registrationState.databaseStored = true;
|
|
177
177
|
onEvent?.({
|
|
178
|
-
step:
|
|
179
|
-
phase: require_sdkSentEvents.RegistrationPhase.
|
|
178
|
+
step: 8,
|
|
179
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_8_DATABASE_STORAGE,
|
|
180
180
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
181
181
|
message: "Registration metadata stored successfully"
|
|
182
182
|
});
|
|
@@ -237,8 +237,8 @@ async function registerPasskeyInternal(context, nearAccountId, options, authenti
|
|
|
237
237
|
console.warn("Failed to initialize current user after registration:", initErr);
|
|
238
238
|
}
|
|
239
239
|
onEvent?.({
|
|
240
|
-
step:
|
|
241
|
-
phase: require_sdkSentEvents.RegistrationPhase.
|
|
240
|
+
step: 9,
|
|
241
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_9_REGISTRATION_COMPLETE,
|
|
242
242
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
243
243
|
message: "Registration completed!"
|
|
244
244
|
});
|
|
@@ -365,8 +365,31 @@ async function activateThresholdEnrollmentPostRegistration(opts) {
|
|
|
365
365
|
const relayerKeyId = String(opts.relayerKeyId || "").trim();
|
|
366
366
|
const clientVerifyingShareB64u = String(opts.thresholdClientVerifyingShareB64u || "").trim();
|
|
367
367
|
const relayerVerifyingShareB64u = String(opts.relayerVerifyingShareB64u || "").trim();
|
|
368
|
-
|
|
368
|
+
const emitThresholdKeyEnrollmentResult = (input) => {
|
|
369
|
+
opts.onEvent?.({
|
|
370
|
+
step: 7,
|
|
371
|
+
phase: require_sdkSentEvents.RegistrationPhase.STEP_7_THRESHOLD_KEY_ENROLLMENT,
|
|
372
|
+
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
373
|
+
message: input.message,
|
|
374
|
+
thresholdKeyReady: input.thresholdKeyReady,
|
|
375
|
+
thresholdPublicKey: thresholdPublicKey || void 0,
|
|
376
|
+
relayerKeyId: relayerKeyId || void 0,
|
|
377
|
+
deviceNumber: 1,
|
|
378
|
+
warning: input.warning
|
|
379
|
+
});
|
|
380
|
+
};
|
|
381
|
+
const missingEnrollmentDetails = [];
|
|
382
|
+
if (!thresholdPublicKey) missingEnrollmentDetails.push("thresholdPublicKey");
|
|
383
|
+
if (!relayerKeyId) missingEnrollmentDetails.push("relayerKeyId");
|
|
384
|
+
if (!clientVerifyingShareB64u) missingEnrollmentDetails.push("clientVerifyingShareB64u");
|
|
385
|
+
if (!relayerVerifyingShareB64u) missingEnrollmentDetails.push("relayerVerifyingShareB64u");
|
|
386
|
+
if (missingEnrollmentDetails.length) {
|
|
369
387
|
console.warn("[Registration] threshold-signer requested but threshold enrollment details are missing; continuing with local-signer only");
|
|
388
|
+
emitThresholdKeyEnrollmentResult({
|
|
389
|
+
thresholdKeyReady: false,
|
|
390
|
+
message: "Threshold key not ready; continuing with local-signer only",
|
|
391
|
+
warning: `Missing threshold enrollment details: ${missingEnrollmentDetails.join(", ")}`
|
|
392
|
+
});
|
|
370
393
|
return;
|
|
371
394
|
}
|
|
372
395
|
try {
|
|
@@ -424,8 +447,18 @@ async function activateThresholdEnrollmentPostRegistration(opts) {
|
|
|
424
447
|
status: require_sdkSentEvents.RegistrationStatus.SUCCESS,
|
|
425
448
|
message: "Threshold access key activated on-chain"
|
|
426
449
|
});
|
|
450
|
+
emitThresholdKeyEnrollmentResult({
|
|
451
|
+
thresholdKeyReady: true,
|
|
452
|
+
message: "Threshold key ready"
|
|
453
|
+
});
|
|
427
454
|
} catch (e) {
|
|
455
|
+
const warning = e && typeof e === "object" && "message" in e ? String(e.message || "") : String(e || "");
|
|
428
456
|
console.warn("[Registration] threshold enrollment activation failed; continuing with local-signer only:", e);
|
|
457
|
+
emitThresholdKeyEnrollmentResult({
|
|
458
|
+
thresholdKeyReady: false,
|
|
459
|
+
message: "Threshold key not ready; continuing with local-signer only",
|
|
460
|
+
warning
|
|
461
|
+
});
|
|
429
462
|
}
|
|
430
463
|
}
|
|
431
464
|
async function verifyAccountAccessKeysPresent(nearClient, nearAccountId, expectedPublicKeys, opts) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"registration.js","names":["RegistrationPhase","RegistrationStatus","confirmationConfig: Partial<ConfirmationConfig>","mergeSignerMode","thresholdClientVerifyingShareB64u: string | null","createAccountAndRegisterWithRelayServer","expectedAccessKeys: string[]","error: unknown","getUserFriendlyErrorMessage","validateNearAccountId","rollbackError: unknown","DEFAULT_WAIT_STATUS","IndexedDBManager","buildThresholdEd25519Participants2pV1","ensureEd25519Prefix"],"sources":["../../../../../../src/core/TatchiPasskey/registration.ts"],"sourcesContent":["import type { NearClient } from '../NearClient';\nimport { ensureEd25519Prefix, validateNearAccountId } from '../../utils/validation';\nimport type {\n RegistrationHooksOptions,\n RegistrationSSEEvent,\n} from '../types/sdkSentEvents';\nimport type { RegistrationResult, TatchiConfigs } from '../types/tatchi';\nimport type { AuthenticatorOptions } from '../types/authenticatorOptions';\nimport { RegistrationPhase, RegistrationStatus } from '../types/sdkSentEvents';\nimport {\n createAccountAndRegisterWithRelayServer\n} from './faucets/createAccountRelayServer';\nimport { PasskeyManagerContext } from './index';\nimport { WebAuthnManager } from '../WebAuthnManager';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport { VRFChallenge } from '../types/vrf-worker';\nimport { type ConfirmationConfig, type SignerMode, mergeSignerMode } from '../types/signer-worker';\nimport type { WebAuthnRegistrationCredential } from '../types/webauthn';\nimport type { AccountId } from '../types/accountIds';\nimport { getUserFriendlyErrorMessage } from '../../utils/errors';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { DEFAULT_WAIT_STATUS } from '../types/rpc';\nimport { buildThresholdEd25519Participants2pV1 } from '../../threshold/participants';\n// Registration forces a visible, clickable confirmation for cross‑origin safety\n\n/**\n * Core registration function that handles passkey registration\n *\n * VRF Registration Flow (Single VRF Keypair):\n * 1. Generate VRF keypair (ed25519) using crypto.randomUUID() + persist in worker memory\n * 2. Generate VRF proof + output using the VRF keypair\n * - VRF input with domain separator + NEAR block height + hash\n * 3. Use VRF output as WebAuthn challenge in registration ceremony\n * 4. Derive AES key from WebAuthn PRF output and encrypt the SAME VRF keypair\n * 5. Store encrypted VRF keypair in IndexedDB\n * 6. Call contract verify_registration_response with VRF proof + WebAuthn registration payload\n * 7. Contract verifies VRF proof and WebAuthn registration (challenges match!)\n * 8. Contract stores VRF pubkey + authenticator credentials on-chain for\n * future stateless authentication\n */\nexport async function registerPasskeyInternal(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options: RegistrationHooksOptions,\n authenticatorOptions: AuthenticatorOptions,\n confirmationConfigOverride?: Partial<ConfirmationConfig>\n): Promise<RegistrationResult> {\n\n const { onEvent, onError, afterCall } = options;\n const { webAuthnManager, configs } = context;\n\n // Track registration progress for rollback\n const registrationState = {\n accountCreated: false,\n contractRegistered: false,\n databaseStored: false,\n contractTransactionId: null as string | null,\n };\n\n console.log('⚡ Registration: Passkey registration with VRF WebAuthn');\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: `Starting registration for ${nearAccountId}`\n } as RegistrationSSEEvent);\n\n try {\n\n await validateRegistrationInputs(context, nearAccountId, onEvent, onError);\n\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Generating passkey credential...'\n });\n\n const confirmationConfig: Partial<ConfirmationConfig> = {\n uiMode: 'modal',\n behavior: 'requireClick', // cross‑origin safari requirement: must requireClick\n theme: (context.configs?.walletTheme === 'light') ? 'light' : 'dark',\n ...(confirmationConfigOverride ?? options?.confirmationConfig ?? {}),\n };\n\n const registrationSession = await context.webAuthnManager.requestRegistrationCredentialConfirmation({\n nearAccountId: String(nearAccountId),\n deviceNumber: 1,\n confirmerText: options?.confirmerText,\n confirmationConfigOverride: confirmationConfig,\n });\n\n const credential = registrationSession.credential;\n const vrfChallenge = registrationSession.vrfChallenge;\n const transactionContext = registrationSession.transactionContext;\n\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'WebAuthn ceremony successful'\n });\n\n // 1) Ensure VRF keypair is derived and loaded in-memory\n // before deriving WrapKeySeed for NEAR key encryption\n const deterministicVrfKeyResult = await webAuthnManager.deriveVrfKeypair({\n credential,\n nearAccountId,\n saveInMemory: true,\n });\n if (!deterministicVrfKeyResult.success || !deterministicVrfKeyResult.vrfPublicKey) {\n throw new Error('Failed to derive deterministic VRF keypair from PRF');\n }\n\n const baseSignerMode = webAuthnManager.getUserPreferences().getSignerMode();\n const requestedSignerMode = mergeSignerMode(baseSignerMode, options?.signerMode);\n const requestedSignerModeStr = requestedSignerMode.mode;\n\n // 2) Derive/enroll the local NEAR key after VRF keypair exists.\n const nearKeyResult = await webAuthnManager.deriveNearKeypairAndEncryptFromSerialized({\n credential,\n nearAccountId,\n options: { deviceNumber: 1 },\n });\n if (!nearKeyResult.success || !nearKeyResult.publicKey) {\n const reason = nearKeyResult?.error || 'Failed to generate NEAR keypair with PRF';\n throw new Error(reason);\n }\n const nearPublicKey = nearKeyResult.publicKey;\n const wrapKeySalt = String(nearKeyResult.wrapKeySalt || '').trim();\n if (!wrapKeySalt) {\n throw new Error('Missing wrapKeySalt after local key derivation');\n }\n\n // Optional: threshold-signer enrollment during registration.\n // Derive client verifying share (public) and let the relay compute threshold group public key\n // and include it in the on-chain AddKey set during create_account_and_register_user.\n let thresholdClientVerifyingShareB64u: string | null = null;\n if (requestedSignerModeStr === 'threshold-signer') {\n const derived = await webAuthnManager.deriveThresholdEd25519ClientVerifyingShareFromCredential({\n credential,\n nearAccountId,\n wrapKeySalt,\n });\n if (!derived.success || !derived.clientVerifyingShareB64u) {\n throw new Error(derived.error || 'Failed to derive threshold client verifying share');\n }\n thresholdClientVerifyingShareB64u = derived.clientVerifyingShareB64u;\n }\n\n // Step 4-5: Create account and register with contract using the relay (atomic)\n onEvent?.({\n step: 2,\n phase: RegistrationPhase.STEP_2_KEY_GENERATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Wallet derived successfully from Passkey',\n verified: true,\n nearAccountId: nearAccountId,\n nearPublicKey: nearPublicKey,\n vrfPublicKey: vrfChallenge.vrfPublicKey,\n });\n\n let accountAndRegistrationResult;\n accountAndRegistrationResult = await createAccountAndRegisterWithRelayServer(\n context,\n nearAccountId,\n nearPublicKey,\n credential,\n vrfChallenge,\n deterministicVrfKeyResult.vrfPublicKey,\n authenticatorOptions,\n onEvent,\n {\n thresholdEd25519: thresholdClientVerifyingShareB64u\n ? { clientVerifyingShareB64u: thresholdClientVerifyingShareB64u }\n : undefined,\n },\n );\n\n if (!accountAndRegistrationResult.success) {\n throw new Error(accountAndRegistrationResult.error || 'Account creation and registration failed');\n }\n\n // Update registration state based on results\n registrationState.accountCreated = true;\n registrationState.contractRegistered = true;\n registrationState.contractTransactionId = accountAndRegistrationResult.transactionId || null;\n\n // Step 6: Post-commit verification: ensure on-chain access key matches expected public key\n onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Verifying on-chain access key matches expected public key...'\n });\n\n const expectedAccessKeys: string[] = [nearPublicKey];\n const thresholdPublicKey = String(accountAndRegistrationResult?.thresholdEd25519?.publicKey || '').trim();\n const relayerKeyId = String(accountAndRegistrationResult?.thresholdEd25519?.relayerKeyId || '').trim();\n\n const accessKeyVerified = await verifyAccountAccessKeysPresent(\n context.nearClient,\n nearAccountId,\n expectedAccessKeys,\n { attempts: 3, delayMs: 200, finality: 'optimistic' },\n );\n\n if (!accessKeyVerified) {\n console.warn('[Registration] Access key not yet visible after atomic registration; continuing optimistically');\n onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Access key verification pending (optimistic); continuing...'\n });\n } else {\n onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Access key verified on-chain'\n });\n }\n\n // Threshold enrollment can take an extra on-chain tx + key propagation; don't block registration on it.\n activateThresholdEnrollmentPostRegistration({\n requestedSignerMode: requestedSignerModeStr,\n nearAccountId,\n nearPublicKey,\n thresholdPublicKey,\n relayerKeyId,\n clientParticipantId: accountAndRegistrationResult?.thresholdEd25519?.clientParticipantId,\n relayerParticipantId: accountAndRegistrationResult?.thresholdEd25519?.relayerParticipantId,\n thresholdClientVerifyingShareB64u,\n relayerVerifyingShareB64u: String(\n accountAndRegistrationResult?.thresholdEd25519?.relayerVerifyingShareB64u || '',\n ).trim(),\n credential,\n wrapKeySalt,\n webAuthnManager: context.webAuthnManager,\n nearClient: context.nearClient,\n onEvent,\n }).catch(() => {});\n\n // Step 7: Store user data with VRF credentials atomically\n onEvent?.({\n step: 7,\n phase: RegistrationPhase.STEP_7_DATABASE_STORAGE,\n status: RegistrationStatus.PROGRESS,\n message: 'Storing passkey wallet metadata...'\n });\n\n await webAuthnManager.atomicStoreRegistrationData({\n nearAccountId,\n credential,\n publicKey: nearPublicKey,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n vrfPublicKey: deterministicVrfKeyResult.vrfPublicKey,\n serverEncryptedVrfKeypair: deterministicVrfKeyResult.serverEncryptedVrfKeypair,\n });\n\n // Mark database as stored for rollback tracking\n registrationState.databaseStored = true;\n\n onEvent?.({\n step: 7,\n phase: RegistrationPhase.STEP_7_DATABASE_STORAGE,\n status: RegistrationStatus.SUCCESS,\n message: 'Registration metadata stored successfully'\n });\n\n // Step 7: Ensure VRF session is active for auto-login\n // If VRF keypair is already in-memory (saved earlier), skip an extra Touch ID prompt.\n let vrfStatus = await webAuthnManager.checkVrfStatus().catch(() => ({ active: false }));\n if (!vrfStatus?.active) {\n // Prefer a no-prompt unlock using the existing registration credential (PRF already available).\n // Fallback to an explicit authentication ceremony only if needed.\n const unlockNoPrompt = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n credential,\n }).catch((unlockError: unknown) => {\n const message = (unlockError && typeof unlockError === 'object' && 'message' in unlockError)\n ? String((unlockError as { message?: unknown }).message || '')\n : String(unlockError || '');\n return { success: false, error: message };\n });\n\n if (!unlockNoPrompt.success) {\n // Obtain an authentication credential for VRF unlock (separate from registration credential)\n // IMPORTANT: Immediately after account creation, the new access key may not be queryable yet on some RPC nodes.\n // We only need fresh block info for the VRF challenge here, so fetch the block directly to avoid AK lookup failures.\n let txBlockHash = String(transactionContext?.txBlockHash || '').trim();\n let txBlockHeight = String(transactionContext?.txBlockHeight || '').trim();\n if (!txBlockHash || !txBlockHeight) {\n const blockInfo = await context.nearClient.viewBlock({ finality: 'final' });\n txBlockHash = String(blockInfo?.header?.hash || '').trim();\n txBlockHeight = String(blockInfo?.header?.height ?? '').trim();\n }\n const vrfChallenge2 = await webAuthnManager.generateVrfChallengeOnce({\n userId: nearAccountId,\n rpId: webAuthnManager.getRpId(),\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n });\n const allowCredentialIds = String(credential?.rawId || '').trim()\n ? [String(credential.rawId)]\n : (await webAuthnManager.getAuthenticatorsByUser(nearAccountId)).map((a) => a.credentialId);\n const authCredential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId,\n challenge: vrfChallenge2,\n credentialIds: allowCredentialIds,\n });\n const unlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n credential: authCredential,\n }).catch((unlockError: unknown) => {\n const message = (unlockError && typeof unlockError === 'object' && 'message' in unlockError)\n ? String((unlockError as { message?: unknown }).message || '')\n : String(unlockError || '');\n return { success: false, error: message };\n });\n\n if (!unlockResult.success) {\n console.warn('VRF keypair unlock failed:', unlockResult.error);\n throw new Error(unlockResult.error);\n }\n } else {\n console.debug('Registration: VRF unlocked using registration credential; skipping extra Touch ID unlock');\n }\n } else {\n console.debug('Registration: VRF session already active; skipping extra Touch ID unlock');\n }\n\n // Initialize current user only after a successful unlock\n try {\n await webAuthnManager.initializeCurrentUser(nearAccountId);\n webAuthnManager.getNonceManager().prefetchBlockheight(context.nearClient).catch(() => {});\n } catch (initErr) {\n console.warn('Failed to initialize current user after registration:', initErr);\n }\n\n onEvent?.({\n step: 8,\n phase: RegistrationPhase.STEP_8_REGISTRATION_COMPLETE,\n status: RegistrationStatus.SUCCESS,\n message: 'Registration completed!'\n });\n\n const successResult = {\n success: true,\n nearAccountId: nearAccountId,\n clientNearPublicKey: nearPublicKey,\n transactionId: registrationState.contractTransactionId,\n vrfRegistration: {\n success: true,\n vrfPublicKey: vrfChallenge.vrfPublicKey,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n contractVerified: accountAndRegistrationResult.success,\n }\n };\n\n afterCall?.(true, successResult);\n return successResult;\n\n } catch (error: unknown) {\n const message = (error && typeof error === 'object' && 'message' in error)\n ? String((error as { message?: unknown }).message || '')\n : String(error || '');\n const stack = (error && typeof error === 'object' && 'stack' in error)\n ? String((error as { stack?: unknown }).stack || '')\n : '';\n console.error('Registration failed:', message, stack);\n\n // Perform rollback based on registration state\n await performRegistrationRollback(\n registrationState,\n nearAccountId,\n webAuthnManager,\n onEvent\n );\n\n // Use centralized error handling\n const errorMessage = getUserFriendlyErrorMessage(error, 'registration', nearAccountId);\n\n const errorObject = new Error(errorMessage);\n onError?.(errorObject);\n\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: errorMessage,\n error: errorMessage\n } as RegistrationSSEEvent);\n\n const result = { success: false, error: errorMessage };\n afterCall?.(false);\n return result;\n }\n}\n\n// Backward-compatible wrapper without explicit confirmationConfig override\nexport async function registerPasskey(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options: RegistrationHooksOptions,\n authenticatorOptions: AuthenticatorOptions\n): Promise<RegistrationResult> {\n return registerPasskeyInternal(context, nearAccountId, options, authenticatorOptions, undefined);\n}\n\n//////////////////////////////////////\n// HELPER FUNCTIONS\n//////////////////////////////////////\n\n/**\n * Generate a VRF keypair + challenge in VRF wasm worker for WebAuthn registration ceremony bootstrapping\n *\n * ARCHITECTURE: This function solves the chicken-and-egg problem with a single VRF keypair:\n * 1. Generate VRF keypair + challenge (no PRF needed)\n * 2. Persist VRF keypair in worker memory (NOT encrypted yet)\n * 3. Use VRF challenge for WebAuthn ceremony → get PRF output\n * 4. Encrypt the SAME VRF keypair (still in memory) with PRF\n *\n * @param webAuthnManager - WebAuthn manager instance\n * @param nearAccountId - NEAR account ID for VRF input\n * @param blockHeight - Current NEAR block height for freshness\n * @param blockHashBytes - Current NEAR block hash bytes for entropy\n * @returns VRF challenge data (VRF keypair persisted in worker memory)\n */\nexport async function generateBootstrapVrfChallenge(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n): Promise<VRFChallenge> {\n\n const { webAuthnManager, nearClient } = context;\n\n const blockInfo = await nearClient.viewBlock({ finality: 'final' });\n\n // Generate VRF keypair and persist in worker memory\n const vrfResult = await webAuthnManager.generateVrfKeypairBootstrap({\n vrfInputData: {\n userId: nearAccountId,\n // Keep VRF rpId consistent with WebAuthn rpId selection logic.\n rpId: webAuthnManager.getRpId(),\n blockHeight: String(blockInfo.header.height),\n blockHash: blockInfo.header.hash,\n },\n saveInMemory: true,\n // VRF keypair persists in worker memory until PRF encryption\n });\n\n if (!vrfResult.vrfChallenge) {\n throw new Error('Registration VRF keypair generation failed');\n }\n return vrfResult.vrfChallenge;\n}\n\n/**\n * Validates registration inputs and throws errors if invalid\n * @param nearAccountId - NEAR account ID to validate\n * @param onEvent - Optional callback for registration progress events\n * @param onError - Optional callback for error handling\n */\nconst validateRegistrationInputs = async (\n context: {\n configs: TatchiConfigs,\n webAuthnManager: WebAuthnManager,\n nearClient: NearClient,\n },\n nearAccountId: AccountId,\n onEvent?: (event: RegistrationSSEEvent) => void,\n onError?: (error: Error) => void,\n) => {\n\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Validating registration inputs...'\n } as RegistrationSSEEvent);\n\n // Validation\n if (!nearAccountId) {\n const error = new Error('NEAR account ID is required for registration.');\n onError?.(error);\n throw error;\n }\n // Validate the account ID format\n const validation = validateNearAccountId(nearAccountId);\n if (!validation.valid) {\n const error = new Error(`Invalid NEAR account ID: ${validation.error}`);\n onError?.(error);\n throw error;\n }\n if (!window.isSecureContext) {\n const error = new Error('Passkey operations require a secure context (HTTPS or localhost).');\n onError?.(error);\n throw error;\n }\n\n // On-chain account existence / contract validation is performed by the relay + contract\n // during the atomic create_account_and_register_user call.\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: `Account format validated, preparing confirmation`\n } as RegistrationSSEEvent);\n return;\n}\n\n/**\n * Rollback registration data in case of errors\n */\nasync function performRegistrationRollback(\n registrationState: {\n accountCreated: boolean;\n contractRegistered: boolean;\n databaseStored: boolean;\n contractTransactionId: string | null;\n },\n nearAccountId: AccountId,\n webAuthnManager: WebAuthnManager,\n onEvent?: (event: RegistrationSSEEvent) => void\n): Promise<void> {\n console.debug('Starting registration rollback...', registrationState);\n\n // Rollback in reverse order\n try {\n // 1. Always clear any in-memory VRF session established during bootstrap\n await webAuthnManager.clearVrfSession();\n\n // 2. Rollback database storage\n if (registrationState.databaseStored) {\n console.debug('Rolling back database storage...');\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: 'Rolling back database storage...',\n error: 'Registration failed - rolling back database storage'\n } as RegistrationSSEEvent);\n\n await webAuthnManager.rollbackUserRegistration(nearAccountId);\n console.debug('Database rollback completed');\n }\n\n // 3. Contract rollback on the Web3Authn contract\n // NOT NEEDED - account creation and contract registration are atomic in the relay server flow\n if (registrationState.contractRegistered) {\n console.debug('Contract registration cannot be rolled back (immutable blockchain state)');\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: `Contract registration (tx: ${registrationState.contractTransactionId}) cannot be rolled back`,\n error: 'Registration failed - contract state is immutable'\n } as RegistrationSSEEvent);\n }\n console.debug('Registration rollback completed');\n\n } catch (rollbackError: unknown) {\n console.error('Rollback failed:', rollbackError);\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: `Rollback failed: ${\n (rollbackError && typeof rollbackError === 'object' && 'message' in rollbackError)\n ? String((rollbackError as { message?: unknown }).message || '')\n : String(rollbackError || '')\n }`,\n error: 'Both registration and rollback failed'\n } as RegistrationSSEEvent);\n }\n}\n\nasync function activateThresholdEnrollmentPostRegistration(opts: {\n requestedSignerMode: SignerMode['mode'];\n nearAccountId: AccountId;\n nearPublicKey: string;\n thresholdPublicKey: string;\n relayerKeyId: string;\n clientParticipantId?: number;\n relayerParticipantId?: number;\n thresholdClientVerifyingShareB64u: string | null;\n relayerVerifyingShareB64u: string;\n credential: WebAuthnRegistrationCredential;\n wrapKeySalt: string;\n webAuthnManager: WebAuthnManager;\n nearClient: NearClient;\n onEvent?: (event: RegistrationSSEEvent) => void;\n}): Promise<void> {\n // Optional: activate threshold enrollment post-registration by having the client\n // submit AddKey(thresholdPublicKey) signed with the local key.\n if (opts.requestedSignerMode !== 'threshold-signer') return;\n\n const thresholdPublicKey = String(opts.thresholdPublicKey || '').trim();\n const relayerKeyId = String(opts.relayerKeyId || '').trim();\n const clientVerifyingShareB64u = String(opts.thresholdClientVerifyingShareB64u || '').trim();\n const relayerVerifyingShareB64u = String(opts.relayerVerifyingShareB64u || '').trim();\n\n if (!thresholdPublicKey || !relayerKeyId || !clientVerifyingShareB64u || !relayerVerifyingShareB64u) {\n console.warn('[Registration] threshold-signer requested but threshold enrollment details are missing; continuing with local-signer only');\n return;\n }\n\n try {\n opts.onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Activating threshold access key onchain...'\n });\n\n // Prepare a single AddKey transaction signed with the local key (no extra TouchID prompt).\n try {\n opts.webAuthnManager.getNonceManager().initializeUser(opts.nearAccountId, opts.nearPublicKey);\n } catch {}\n const txContext = await opts.webAuthnManager.getNonceManager().getNonceBlockHashAndHeight(\n opts.nearClient,\n { force: true },\n );\n\n const signed = await opts.webAuthnManager.signAddKeyThresholdPublicKeyNoPrompt({\n nearAccountId: opts.nearAccountId,\n credential: opts.credential,\n wrapKeySalt: opts.wrapKeySalt,\n transactionContext: txContext,\n thresholdPublicKey,\n relayerVerifyingShareB64u,\n clientParticipantId: opts.clientParticipantId,\n relayerParticipantId: opts.relayerParticipantId,\n });\n\n const signedTx = signed?.signedTransaction;\n if (!signedTx) throw new Error('Failed to sign AddKey(thresholdPublicKey) transaction');\n\n await opts.nearClient.sendTransaction(signedTx, DEFAULT_WAIT_STATUS.thresholdAddKey);\n\n const thresholdKeyVerified = await verifyAccountAccessKeysPresent(\n opts.nearClient,\n opts.nearAccountId,\n [opts.nearPublicKey, thresholdPublicKey],\n { attempts: 6, delayMs: 250, finality: 'optimistic' },\n );\n if (!thresholdKeyVerified) {\n throw new Error('Threshold access key not found on-chain after AddKey');\n }\n\n await IndexedDBManager.nearKeysDB.storeKeyMaterial({\n kind: 'threshold_ed25519_2p_v1',\n nearAccountId: opts.nearAccountId,\n deviceNumber: 1,\n publicKey: thresholdPublicKey,\n wrapKeySalt: opts.wrapKeySalt,\n relayerKeyId,\n clientShareDerivation: 'prf_first_v1',\n participants: buildThresholdEd25519Participants2pV1({\n clientParticipantId: opts.clientParticipantId,\n relayerParticipantId: opts.relayerParticipantId,\n relayerKeyId,\n relayerUrl: opts.webAuthnManager.tatchiPasskeyConfigs?.relayer?.url,\n clientVerifyingShareB64u,\n relayerVerifyingShareB64u,\n clientShareDerivation: 'prf_first_v1',\n }),\n timestamp: Date.now(),\n });\n\n opts.onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Threshold access key activated on-chain'\n });\n } catch (e) {\n console.warn('[Registration] threshold enrollment activation failed; continuing with local-signer only:', e);\n }\n}\n\nasync function verifyAccountAccessKeysPresent(\n nearClient: NearClient,\n nearAccountId: string,\n expectedPublicKeys: string[],\n opts?: { attempts?: number; delayMs?: number; finality?: 'optimistic' | 'final' },\n): Promise<boolean> {\n const unique = Array.from(\n new Set(expectedPublicKeys.map((k) => ensureEd25519Prefix(k)).filter(Boolean)),\n );\n if (!unique.length) return false;\n\n const attempts = Math.max(1, Math.floor(opts?.attempts ?? 6));\n const delayMs = Math.max(50, Math.floor(opts?.delayMs ?? 750));\n const finality = opts?.finality ?? 'optimistic';\n\n for (let i = 0; i < attempts; i++) {\n try {\n const accessKeyList = await nearClient.viewAccessKeyList(\n nearAccountId,\n { finality } as any,\n );\n const keys = accessKeyList.keys.map((k) => ensureEd25519Prefix(k.public_key)).filter(Boolean);\n const allPresent = unique.every((expected) => keys.includes(expected));\n if (allPresent) return true;\n } catch {\n // tolerate transient view errors during propagation; retry\n }\n if (i < attempts - 1) {\n await new Promise((res) => setTimeout(res, delayMs));\n }\n }\n return false;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,eAAsB,wBACpB,SACA,eACA,SACA,sBACA,4BAC6B;CAE7B,MAAM,EAAE,SAAS,SAAS,cAAc;CACxC,MAAM,EAAE,iBAAiB,YAAY;CAGrC,MAAM,oBAAoB;EACxB,gBAAgB;EAChB,oBAAoB;EACpB,gBAAgB;EAChB,uBAAuB;;AAGzB,SAAQ,IAAI;AACZ,WAAU;EACR,MAAM;EACN,OAAOA,wCAAkB;EACzB,QAAQC,yCAAmB;EAC3B,SAAS,6BAA6B;;AAGxC,KAAI;AAEF,QAAM,2BAA2B,SAAS,eAAe,SAAS;AAElE,YAAU;GACR,MAAM;GACN,OAAOD,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;EAGX,MAAMC,qBAAkD;GACtD,QAAQ;GACR,UAAU;GACV,OAAQ,QAAQ,SAAS,gBAAgB,UAAW,UAAU;GAC9D,GAAI,8BAA8B,SAAS,sBAAsB;;EAGnE,MAAM,sBAAsB,MAAM,QAAQ,gBAAgB,0CAA0C;GAClG,eAAe,OAAO;GACtB,cAAc;GACd,eAAe,SAAS;GACxB,4BAA4B;;EAG9B,MAAM,aAAa,oBAAoB;EACvC,MAAM,eAAe,oBAAoB;EACzC,MAAM,qBAAqB,oBAAoB;AAE/C,YAAU;GACR,MAAM;GACN,OAAOF,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;EAKX,MAAM,4BAA4B,MAAM,gBAAgB,iBAAiB;GACvE;GACA;GACA,cAAc;;AAEhB,MAAI,CAAC,0BAA0B,WAAW,CAAC,0BAA0B,aACnE,OAAM,IAAI,MAAM;EAGlB,MAAM,iBAAiB,gBAAgB,qBAAqB;EAC5D,MAAM,sBAAsBE,sCAAgB,gBAAgB,SAAS;EACrE,MAAM,yBAAyB,oBAAoB;EAGnD,MAAM,gBAAgB,MAAM,gBAAgB,0CAA0C;GACpF;GACA;GACA,SAAS,EAAE,cAAc;;AAE3B,MAAI,CAAC,cAAc,WAAW,CAAC,cAAc,WAAW;GACtD,MAAM,SAAS,eAAe,SAAS;AACvC,SAAM,IAAI,MAAM;;EAElB,MAAM,gBAAgB,cAAc;EACpC,MAAM,cAAc,OAAO,cAAc,eAAe,IAAI;AAC5D,MAAI,CAAC,YACH,OAAM,IAAI,MAAM;EAMlB,IAAIC,oCAAmD;AACvD,MAAI,2BAA2B,oBAAoB;GACjD,MAAM,UAAU,MAAM,gBAAgB,yDAAyD;IAC7F;IACA;IACA;;AAEF,OAAI,CAAC,QAAQ,WAAW,CAAC,QAAQ,yBAC/B,OAAM,IAAI,MAAM,QAAQ,SAAS;AAEnC,uCAAoC,QAAQ;;AAI9C,YAAU;GACR,MAAM;GACN,OAAOJ,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;GACT,UAAU;GACK;GACA;GACf,cAAc,aAAa;;EAG7B,IAAI;AACJ,iCAA+B,MAAMI,yEACnC,SACA,eACA,eACA,YACA,cACA,0BAA0B,cAC1B,sBACA,SACA,EACE,kBAAkB,oCACd,EAAE,0BAA0B,sCAC5B;AAIR,MAAI,CAAC,6BAA6B,QAChC,OAAM,IAAI,MAAM,6BAA6B,SAAS;AAIxD,oBAAkB,iBAAiB;AACnC,oBAAkB,qBAAqB;AACvC,oBAAkB,wBAAwB,6BAA6B,iBAAiB;AAGxF,YAAU;GACR,MAAM;GACN,OAAOL,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;EAGX,MAAMK,qBAA+B,CAAC;EACtC,MAAM,qBAAqB,OAAO,8BAA8B,kBAAkB,aAAa,IAAI;EACnG,MAAM,eAAe,OAAO,8BAA8B,kBAAkB,gBAAgB,IAAI;EAEhG,MAAM,oBAAoB,MAAM,+BAC9B,QAAQ,YACR,eACA,oBACA;GAAE,UAAU;GAAG,SAAS;GAAK,UAAU;;AAGzC,MAAI,CAAC,mBAAmB;AACtB,WAAQ,KAAK;AACb,aAAU;IACR,MAAM;IACN,OAAON,wCAAkB;IACzB,QAAQC,yCAAmB;IAC3B,SAAS;;QAGX,WAAU;GACR,MAAM;GACN,OAAOD,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;AAKb,8CAA4C;GAC1C,qBAAqB;GACrB;GACA;GACA;GACA;GACA,qBAAqB,8BAA8B,kBAAkB;GACrE,sBAAsB,8BAA8B,kBAAkB;GACtE;GACA,2BAA2B,OACzB,8BAA8B,kBAAkB,6BAA6B,IAC7E;GACF;GACA;GACA,iBAAiB,QAAQ;GACzB,YAAY,QAAQ;GACpB;KACC,YAAY;AAGf,YAAU;GACR,MAAM;GACN,OAAOD,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;AAGX,QAAM,gBAAgB,4BAA4B;GAChD;GACA;GACA,WAAW;GACX,qBAAqB,0BAA0B;GAC/C,cAAc,0BAA0B;GACxC,2BAA2B,0BAA0B;;AAIvD,oBAAkB,iBAAiB;AAEnC,YAAU;GACR,MAAM;GACN,OAAOD,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;EAKX,IAAI,YAAY,MAAM,gBAAgB,iBAAiB,aAAa,EAAE,QAAQ;AAC9E,MAAI,CAAC,WAAW,QAAQ;GAGtB,MAAM,iBAAiB,MAAM,gBAAgB,iBAAiB;IAC7C;IACf,qBAAqB,0BAA0B;IAC/C;MACC,OAAO,gBAAyB;IACjC,MAAM,UAAW,eAAe,OAAO,gBAAgB,YAAY,aAAa,cAC5E,OAAQ,YAAsC,WAAW,MACzD,OAAO,eAAe;AAC1B,WAAO;KAAE,SAAS;KAAO,OAAO;;;AAGlC,OAAI,CAAC,eAAe,SAAS;IAI3B,IAAI,cAAc,OAAO,oBAAoB,eAAe,IAAI;IAChE,IAAI,gBAAgB,OAAO,oBAAoB,iBAAiB,IAAI;AACpE,QAAI,CAAC,eAAe,CAAC,eAAe;KAClC,MAAM,YAAY,MAAM,QAAQ,WAAW,UAAU,EAAE,UAAU;AACjE,mBAAc,OAAO,WAAW,QAAQ,QAAQ,IAAI;AACpD,qBAAgB,OAAO,WAAW,QAAQ,UAAU,IAAI;;IAE1D,MAAM,gBAAgB,MAAM,gBAAgB,yBAAyB;KACnE,QAAQ;KACR,MAAM,gBAAgB;KACtB,WAAW;KACX,aAAa;;IAEf,MAAM,qBAAqB,OAAO,YAAY,SAAS,IAAI,SACvD,CAAC,OAAO,WAAW,WAClB,MAAM,gBAAgB,wBAAwB,gBAAgB,KAAK,MAAM,EAAE;IAChF,MAAM,iBAAiB,MAAM,gBAAgB,8CAA8C;KACzF;KACA,WAAW;KACX,eAAe;;IAEjB,MAAM,eAAe,MAAM,gBAAgB,iBAAiB;KAC3C;KACf,qBAAqB,0BAA0B;KAC/C,YAAY;OACX,OAAO,gBAAyB;KACjC,MAAM,UAAW,eAAe,OAAO,gBAAgB,YAAY,aAAa,cAC5E,OAAQ,YAAsC,WAAW,MACzD,OAAO,eAAe;AAC1B,YAAO;MAAE,SAAS;MAAO,OAAO;;;AAGlC,QAAI,CAAC,aAAa,SAAS;AACzB,aAAQ,KAAK,8BAA8B,aAAa;AACxD,WAAM,IAAI,MAAM,aAAa;;SAG/B,SAAQ,MAAM;QAGhB,SAAQ,MAAM;AAIhB,MAAI;AACF,SAAM,gBAAgB,sBAAsB;AAC5C,mBAAgB,kBAAkB,oBAAoB,QAAQ,YAAY,YAAY;WAC/E,SAAS;AAChB,WAAQ,KAAK,yDAAyD;;AAGxE,YAAU;GACR,MAAM;GACN,OAAOD,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;EAGX,MAAM,gBAAgB;GACpB,SAAS;GACM;GACf,qBAAqB;GACrB,eAAe,kBAAkB;GACjC,iBAAiB;IACf,SAAS;IACT,cAAc,aAAa;IAC3B,qBAAqB,0BAA0B;IAC/C,kBAAkB,6BAA6B;;;AAInD,cAAY,MAAM;AAClB,SAAO;UAEAM,OAAgB;EACvB,MAAM,UAAW,SAAS,OAAO,UAAU,YAAY,aAAa,QAChE,OAAQ,MAAgC,WAAW,MACnD,OAAO,SAAS;EACpB,MAAM,QAAS,SAAS,OAAO,UAAU,YAAY,WAAW,QAC5D,OAAQ,MAA8B,SAAS,MAC/C;AACJ,UAAQ,MAAM,wBAAwB,SAAS;AAG/C,QAAM,4BACJ,mBACA,eACA,iBACA;EAIF,MAAM,eAAeC,2CAA4B,OAAO,gBAAgB;EAExE,MAAM,cAAc,IAAI,MAAM;AAC9B,YAAU;AAEV,YAAU;GACR,MAAM;GACN,OAAOR,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;GACT,OAAO;;EAGT,MAAM,SAAS;GAAE,SAAS;GAAO,OAAO;;AACxC,cAAY;AACZ,SAAO;;;AAKX,eAAsB,gBACpB,SACA,eACA,SACA,sBAC6B;AAC7B,QAAO,wBAAwB,SAAS,eAAe,SAAS,sBAAsB;;;;;;;;AAwDxF,MAAM,6BAA6B,OACjC,SAKA,eACA,SACA,YACG;AAEH,WAAU;EACR,MAAM;EACN,OAAOD,wCAAkB;EACzB,QAAQC,yCAAmB;EAC3B,SAAS;;AAIX,KAAI,CAAC,eAAe;EAClB,MAAM,wBAAQ,IAAI,MAAM;AACxB,YAAU;AACV,QAAM;;CAGR,MAAM,aAAaQ,yCAAsB;AACzC,KAAI,CAAC,WAAW,OAAO;EACrB,MAAM,wBAAQ,IAAI,MAAM,4BAA4B,WAAW;AAC/D,YAAU;AACV,QAAM;;AAER,KAAI,CAAC,OAAO,iBAAiB;EAC3B,MAAM,wBAAQ,IAAI,MAAM;AACxB,YAAU;AACV,QAAM;;AAKR,WAAU;EACR,MAAM;EACN,OAAOT,wCAAkB;EACzB,QAAQC,yCAAmB;EAC3B,SAAS;;;;;;AAQb,eAAe,4BACb,mBAMA,eACA,iBACA,SACe;AACf,SAAQ,MAAM,qCAAqC;AAGnD,KAAI;AAEF,QAAM,gBAAgB;AAGtB,MAAI,kBAAkB,gBAAgB;AACpC,WAAQ,MAAM;AACd,aAAU;IACR,MAAM;IACN,OAAOD,wCAAkB;IACzB,QAAQC,yCAAmB;IAC3B,SAAS;IACT,OAAO;;AAGT,SAAM,gBAAgB,yBAAyB;AAC/C,WAAQ,MAAM;;AAKhB,MAAI,kBAAkB,oBAAoB;AACxC,WAAQ,MAAM;AACd,aAAU;IACR,MAAM;IACN,OAAOD,wCAAkB;IACzB,QAAQC,yCAAmB;IAC3B,SAAS,8BAA8B,kBAAkB,sBAAsB;IAC/E,OAAO;;;AAGX,UAAQ,MAAM;UAEPS,eAAwB;AAC/B,UAAQ,MAAM,oBAAoB;AAClC,YAAU;GACR,MAAM;GACN,OAAOV,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS,oBACN,iBAAiB,OAAO,kBAAkB,YAAY,aAAa,gBAChE,OAAQ,cAAwC,WAAW,MAC3D,OAAO,iBAAiB;GAE9B,OAAO;;;;AAKb,eAAe,4CAA4C,MAezC;AAGhB,KAAI,KAAK,wBAAwB,mBAAoB;CAErD,MAAM,qBAAqB,OAAO,KAAK,sBAAsB,IAAI;CACjE,MAAM,eAAe,OAAO,KAAK,gBAAgB,IAAI;CACrD,MAAM,2BAA2B,OAAO,KAAK,qCAAqC,IAAI;CACtF,MAAM,4BAA4B,OAAO,KAAK,6BAA6B,IAAI;AAE/E,KAAI,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,4BAA4B,CAAC,2BAA2B;AACnG,UAAQ,KAAK;AACb;;AAGF,KAAI;AACF,OAAK,UAAU;GACb,MAAM;GACN,OAAOD,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;AAIX,MAAI;AACF,QAAK,gBAAgB,kBAAkB,eAAe,KAAK,eAAe,KAAK;UACzE;EACR,MAAM,YAAY,MAAM,KAAK,gBAAgB,kBAAkB,2BAC7D,KAAK,YACL,EAAE,OAAO;EAGX,MAAM,SAAS,MAAM,KAAK,gBAAgB,qCAAqC;GAC7E,eAAe,KAAK;GACpB,YAAY,KAAK;GACjB,aAAa,KAAK;GAClB,oBAAoB;GACpB;GACA;GACA,qBAAqB,KAAK;GAC1B,sBAAsB,KAAK;;EAG7B,MAAM,WAAW,QAAQ;AACzB,MAAI,CAAC,SAAU,OAAM,IAAI,MAAM;AAE/B,QAAM,KAAK,WAAW,gBAAgB,UAAUU,gCAAoB;EAEpE,MAAM,uBAAuB,MAAM,+BACjC,KAAK,YACL,KAAK,eACL,CAAC,KAAK,eAAe,qBACrB;GAAE,UAAU;GAAG,SAAS;GAAK,UAAU;;AAEzC,MAAI,CAAC,qBACH,OAAM,IAAI,MAAM;AAGlB,QAAMC,+BAAiB,WAAW,iBAAiB;GACjD,MAAM;GACN,eAAe,KAAK;GACpB,cAAc;GACd,WAAW;GACX,aAAa,KAAK;GAClB;GACA,uBAAuB;GACvB,cAAcC,2DAAsC;IAClD,qBAAqB,KAAK;IAC1B,sBAAsB,KAAK;IAC3B;IACA,YAAY,KAAK,gBAAgB,sBAAsB,SAAS;IAChE;IACA;IACA,uBAAuB;;GAEzB,WAAW,KAAK;;AAGlB,OAAK,UAAU;GACb,MAAM;GACN,OAAOb,wCAAkB;GACzB,QAAQC,yCAAmB;GAC3B,SAAS;;UAEJ,GAAG;AACV,UAAQ,KAAK,6FAA6F;;;AAI9G,eAAe,+BACb,YACA,eACA,oBACA,MACkB;CAClB,MAAM,SAAS,MAAM,KACnB,IAAI,IAAI,mBAAmB,KAAK,MAAMa,uCAAoB,IAAI,OAAO;AAEvE,KAAI,CAAC,OAAO,OAAQ,QAAO;CAE3B,MAAM,WAAW,KAAK,IAAI,GAAG,KAAK,MAAM,MAAM,YAAY;CAC1D,MAAM,UAAU,KAAK,IAAI,IAAI,KAAK,MAAM,MAAM,WAAW;CACzD,MAAM,WAAW,MAAM,YAAY;AAEnC,MAAK,IAAI,IAAI,GAAG,IAAI,UAAU,KAAK;AACjC,MAAI;GACF,MAAM,gBAAgB,MAAM,WAAW,kBACrC,eACA,EAAE;GAEJ,MAAM,OAAO,cAAc,KAAK,KAAK,MAAMA,uCAAoB,EAAE,aAAa,OAAO;GACrF,MAAM,aAAa,OAAO,OAAO,aAAa,KAAK,SAAS;AAC5D,OAAI,WAAY,QAAO;UACjB;AAGR,MAAI,IAAI,WAAW,EACjB,OAAM,IAAI,SAAS,QAAQ,WAAW,KAAK;;AAG/C,QAAO"}
|
|
1
|
+
{"version":3,"file":"registration.js","names":["RegistrationPhase","RegistrationStatus","confirmationConfig: Partial<ConfirmationConfig>","mergeSignerMode","thresholdClientVerifyingShareB64u: string | null","createAccountAndRegisterWithRelayServer","expectedAccessKeys: string[]","error: unknown","getUserFriendlyErrorMessage","validateNearAccountId","rollbackError: unknown","missingEnrollmentDetails: string[]","DEFAULT_WAIT_STATUS","IndexedDBManager","buildThresholdEd25519Participants2pV1","ensureEd25519Prefix"],"sources":["../../../../../../src/core/TatchiPasskey/registration.ts"],"sourcesContent":["import type { NearClient } from '../NearClient';\nimport { ensureEd25519Prefix, validateNearAccountId } from '../../utils/validation';\nimport type {\n RegistrationHooksOptions,\n RegistrationSSEEvent,\n} from '../types/sdkSentEvents';\nimport type { RegistrationResult, TatchiConfigs } from '../types/tatchi';\nimport type { AuthenticatorOptions } from '../types/authenticatorOptions';\nimport { RegistrationPhase, RegistrationStatus } from '../types/sdkSentEvents';\nimport {\n createAccountAndRegisterWithRelayServer\n} from './faucets/createAccountRelayServer';\nimport { PasskeyManagerContext } from './index';\nimport { WebAuthnManager } from '../WebAuthnManager';\nimport { IndexedDBManager } from '../IndexedDBManager';\nimport { VRFChallenge } from '../types/vrf-worker';\nimport { type ConfirmationConfig, type SignerMode, mergeSignerMode } from '../types/signer-worker';\nimport type { WebAuthnRegistrationCredential } from '../types/webauthn';\nimport type { AccountId } from '../types/accountIds';\nimport { getUserFriendlyErrorMessage } from '../../utils/errors';\nimport { authenticatorsToAllowCredentials } from '../WebAuthnManager/touchIdPrompt';\nimport { DEFAULT_WAIT_STATUS } from '../types/rpc';\nimport { buildThresholdEd25519Participants2pV1 } from '../../threshold/participants';\n// Registration forces a visible, clickable confirmation for cross‑origin safety\n\n/**\n * Core registration function that handles passkey registration\n *\n * VRF Registration Flow (Single VRF Keypair):\n * 1. Generate VRF keypair (ed25519) using crypto.randomUUID() + persist in worker memory\n * 2. Generate VRF proof + output using the VRF keypair\n * - VRF input with domain separator + NEAR block height + hash\n * 3. Use VRF output as WebAuthn challenge in registration ceremony\n * 4. Derive AES key from WebAuthn PRF output and encrypt the SAME VRF keypair\n * 5. Store encrypted VRF keypair in IndexedDB\n * 6. Call contract verify_registration_response with VRF proof + WebAuthn registration payload\n * 7. Contract verifies VRF proof and WebAuthn registration (challenges match!)\n * 8. Contract stores VRF pubkey + authenticator credentials on-chain for\n * future stateless authentication\n */\nexport async function registerPasskeyInternal(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options: RegistrationHooksOptions,\n authenticatorOptions: AuthenticatorOptions,\n confirmationConfigOverride?: Partial<ConfirmationConfig>\n): Promise<RegistrationResult> {\n\n const { onEvent, onError, afterCall } = options;\n const { webAuthnManager, configs } = context;\n\n // Track registration progress for rollback\n const registrationState = {\n accountCreated: false,\n contractRegistered: false,\n databaseStored: false,\n contractTransactionId: null as string | null,\n };\n\n console.log('⚡ Registration: Passkey registration with VRF WebAuthn');\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: `Starting registration for ${nearAccountId}`\n } as RegistrationSSEEvent);\n\n try {\n\n await validateRegistrationInputs(context, nearAccountId, onEvent, onError);\n\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Generating passkey credential...'\n });\n\n const confirmationConfig: Partial<ConfirmationConfig> = {\n uiMode: 'modal',\n behavior: 'requireClick', // cross‑origin safari requirement: must requireClick\n theme: (context.configs?.walletTheme === 'light') ? 'light' : 'dark',\n ...(confirmationConfigOverride ?? options?.confirmationConfig ?? {}),\n };\n\n const registrationSession = await context.webAuthnManager.requestRegistrationCredentialConfirmation({\n nearAccountId: String(nearAccountId),\n deviceNumber: 1,\n confirmerText: options?.confirmerText,\n confirmationConfigOverride: confirmationConfig,\n });\n\n const credential = registrationSession.credential;\n const vrfChallenge = registrationSession.vrfChallenge;\n const transactionContext = registrationSession.transactionContext;\n\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'WebAuthn ceremony successful'\n });\n\n // 1) Ensure VRF keypair is derived and loaded in-memory\n // before deriving WrapKeySeed for NEAR key encryption\n const deterministicVrfKeyResult = await webAuthnManager.deriveVrfKeypair({\n credential,\n nearAccountId,\n saveInMemory: true,\n });\n if (!deterministicVrfKeyResult.success || !deterministicVrfKeyResult.vrfPublicKey) {\n throw new Error('Failed to derive deterministic VRF keypair from PRF');\n }\n\n const baseSignerMode = webAuthnManager.getUserPreferences().getSignerMode();\n const requestedSignerMode = mergeSignerMode(baseSignerMode, options?.signerMode);\n const requestedSignerModeStr = requestedSignerMode.mode;\n\n // 2) Derive/enroll the local NEAR key after VRF keypair exists.\n const nearKeyResult = await webAuthnManager.deriveNearKeypairAndEncryptFromSerialized({\n credential,\n nearAccountId,\n options: { deviceNumber: 1 },\n });\n if (!nearKeyResult.success || !nearKeyResult.publicKey) {\n const reason = nearKeyResult?.error || 'Failed to generate NEAR keypair with PRF';\n throw new Error(reason);\n }\n const nearPublicKey = nearKeyResult.publicKey;\n const wrapKeySalt = String(nearKeyResult.wrapKeySalt || '').trim();\n if (!wrapKeySalt) {\n throw new Error('Missing wrapKeySalt after local key derivation');\n }\n\n // Optional: threshold-signer enrollment during registration.\n // Derive client verifying share (public) and let the relay compute threshold group public key\n // and include it in the on-chain AddKey set during create_account_and_register_user.\n let thresholdClientVerifyingShareB64u: string | null = null;\n if (requestedSignerModeStr === 'threshold-signer') {\n const derived = await webAuthnManager.deriveThresholdEd25519ClientVerifyingShareFromCredential({\n credential,\n nearAccountId,\n wrapKeySalt,\n });\n if (!derived.success || !derived.clientVerifyingShareB64u) {\n throw new Error(derived.error || 'Failed to derive threshold client verifying share');\n }\n thresholdClientVerifyingShareB64u = derived.clientVerifyingShareB64u;\n }\n\n // Step 4-5: Create account and register with contract using the relay (atomic)\n onEvent?.({\n step: 2,\n phase: RegistrationPhase.STEP_2_KEY_GENERATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Wallet derived successfully from Passkey',\n verified: true,\n nearAccountId: nearAccountId,\n nearPublicKey: nearPublicKey,\n vrfPublicKey: vrfChallenge.vrfPublicKey,\n });\n\n let accountAndRegistrationResult;\n accountAndRegistrationResult = await createAccountAndRegisterWithRelayServer(\n context,\n nearAccountId,\n nearPublicKey,\n credential,\n vrfChallenge,\n deterministicVrfKeyResult.vrfPublicKey,\n authenticatorOptions,\n onEvent,\n {\n thresholdEd25519: thresholdClientVerifyingShareB64u\n ? { clientVerifyingShareB64u: thresholdClientVerifyingShareB64u }\n : undefined,\n },\n );\n\n if (!accountAndRegistrationResult.success) {\n throw new Error(accountAndRegistrationResult.error || 'Account creation and registration failed');\n }\n\n // Update registration state based on results\n registrationState.accountCreated = true;\n registrationState.contractRegistered = true;\n registrationState.contractTransactionId = accountAndRegistrationResult.transactionId || null;\n\n // Step 6: Post-commit verification: ensure on-chain access key matches expected public key\n onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Verifying on-chain access key matches expected public key...'\n });\n\n const expectedAccessKeys: string[] = [nearPublicKey];\n const thresholdPublicKey = String(accountAndRegistrationResult?.thresholdEd25519?.publicKey || '').trim();\n const relayerKeyId = String(accountAndRegistrationResult?.thresholdEd25519?.relayerKeyId || '').trim();\n\n const accessKeyVerified = await verifyAccountAccessKeysPresent(\n context.nearClient,\n nearAccountId,\n expectedAccessKeys,\n { attempts: 3, delayMs: 200, finality: 'optimistic' },\n );\n\n if (!accessKeyVerified) {\n console.warn('[Registration] Access key not yet visible after atomic registration; continuing optimistically');\n onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Access key verification pending (optimistic); continuing...'\n });\n } else {\n onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Access key verified on-chain'\n });\n }\n\n // Threshold enrollment can take an extra on-chain tx + key propagation; don't block registration on it.\n activateThresholdEnrollmentPostRegistration({\n requestedSignerMode: requestedSignerModeStr,\n nearAccountId,\n nearPublicKey,\n thresholdPublicKey,\n relayerKeyId,\n clientParticipantId: accountAndRegistrationResult?.thresholdEd25519?.clientParticipantId,\n relayerParticipantId: accountAndRegistrationResult?.thresholdEd25519?.relayerParticipantId,\n thresholdClientVerifyingShareB64u,\n relayerVerifyingShareB64u: String(\n accountAndRegistrationResult?.thresholdEd25519?.relayerVerifyingShareB64u || '',\n ).trim(),\n credential,\n wrapKeySalt,\n webAuthnManager: context.webAuthnManager,\n nearClient: context.nearClient,\n onEvent,\n }).catch(() => {});\n\n // Step 8: Store user data with VRF credentials atomically\n onEvent?.({\n step: 8,\n phase: RegistrationPhase.STEP_8_DATABASE_STORAGE,\n status: RegistrationStatus.PROGRESS,\n message: 'Storing passkey wallet metadata...'\n });\n\n await webAuthnManager.atomicStoreRegistrationData({\n nearAccountId,\n credential,\n publicKey: nearPublicKey,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n vrfPublicKey: deterministicVrfKeyResult.vrfPublicKey,\n serverEncryptedVrfKeypair: deterministicVrfKeyResult.serverEncryptedVrfKeypair,\n });\n\n // Mark database as stored for rollback tracking\n registrationState.databaseStored = true;\n\n onEvent?.({\n step: 8,\n phase: RegistrationPhase.STEP_8_DATABASE_STORAGE,\n status: RegistrationStatus.SUCCESS,\n message: 'Registration metadata stored successfully'\n });\n\n // Step 8: Ensure VRF session is active for auto-login\n // If VRF keypair is already in-memory (saved earlier), skip an extra Touch ID prompt.\n let vrfStatus = await webAuthnManager.checkVrfStatus().catch(() => ({ active: false }));\n if (!vrfStatus?.active) {\n // Prefer a no-prompt unlock using the existing registration credential (PRF already available).\n // Fallback to an explicit authentication ceremony only if needed.\n const unlockNoPrompt = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n credential,\n }).catch((unlockError: unknown) => {\n const message = (unlockError && typeof unlockError === 'object' && 'message' in unlockError)\n ? String((unlockError as { message?: unknown }).message || '')\n : String(unlockError || '');\n return { success: false, error: message };\n });\n\n if (!unlockNoPrompt.success) {\n // Obtain an authentication credential for VRF unlock (separate from registration credential)\n // IMPORTANT: Immediately after account creation, the new access key may not be queryable yet on some RPC nodes.\n // We only need fresh block info for the VRF challenge here, so fetch the block directly to avoid AK lookup failures.\n let txBlockHash = String(transactionContext?.txBlockHash || '').trim();\n let txBlockHeight = String(transactionContext?.txBlockHeight || '').trim();\n if (!txBlockHash || !txBlockHeight) {\n const blockInfo = await context.nearClient.viewBlock({ finality: 'final' });\n txBlockHash = String(blockInfo?.header?.hash || '').trim();\n txBlockHeight = String(blockInfo?.header?.height ?? '').trim();\n }\n const vrfChallenge2 = await webAuthnManager.generateVrfChallengeOnce({\n userId: nearAccountId,\n rpId: webAuthnManager.getRpId(),\n blockHash: txBlockHash,\n blockHeight: txBlockHeight,\n });\n const allowCredentialIds = String(credential?.rawId || '').trim()\n ? [String(credential.rawId)]\n : (await webAuthnManager.getAuthenticatorsByUser(nearAccountId)).map((a) => a.credentialId);\n const authCredential = await webAuthnManager.getAuthenticationCredentialsSerializedDualPrf({\n nearAccountId,\n challenge: vrfChallenge2,\n credentialIds: allowCredentialIds,\n });\n const unlockResult = await webAuthnManager.unlockVRFKeypair({\n nearAccountId: nearAccountId,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n credential: authCredential,\n }).catch((unlockError: unknown) => {\n const message = (unlockError && typeof unlockError === 'object' && 'message' in unlockError)\n ? String((unlockError as { message?: unknown }).message || '')\n : String(unlockError || '');\n return { success: false, error: message };\n });\n\n if (!unlockResult.success) {\n console.warn('VRF keypair unlock failed:', unlockResult.error);\n throw new Error(unlockResult.error);\n }\n } else {\n console.debug('Registration: VRF unlocked using registration credential; skipping extra Touch ID unlock');\n }\n } else {\n console.debug('Registration: VRF session already active; skipping extra Touch ID unlock');\n }\n\n // Initialize current user only after a successful unlock\n try {\n await webAuthnManager.initializeCurrentUser(nearAccountId);\n webAuthnManager.getNonceManager().prefetchBlockheight(context.nearClient).catch(() => {});\n } catch (initErr) {\n console.warn('Failed to initialize current user after registration:', initErr);\n }\n\n onEvent?.({\n step: 9,\n phase: RegistrationPhase.STEP_9_REGISTRATION_COMPLETE,\n status: RegistrationStatus.SUCCESS,\n message: 'Registration completed!'\n });\n\n const successResult = {\n success: true,\n nearAccountId: nearAccountId,\n clientNearPublicKey: nearPublicKey,\n transactionId: registrationState.contractTransactionId,\n vrfRegistration: {\n success: true,\n vrfPublicKey: vrfChallenge.vrfPublicKey,\n encryptedVrfKeypair: deterministicVrfKeyResult.encryptedVrfKeypair,\n contractVerified: accountAndRegistrationResult.success,\n }\n };\n\n afterCall?.(true, successResult);\n return successResult;\n\n } catch (error: unknown) {\n const message = (error && typeof error === 'object' && 'message' in error)\n ? String((error as { message?: unknown }).message || '')\n : String(error || '');\n const stack = (error && typeof error === 'object' && 'stack' in error)\n ? String((error as { stack?: unknown }).stack || '')\n : '';\n console.error('Registration failed:', message, stack);\n\n // Perform rollback based on registration state\n await performRegistrationRollback(\n registrationState,\n nearAccountId,\n webAuthnManager,\n onEvent\n );\n\n // Use centralized error handling\n const errorMessage = getUserFriendlyErrorMessage(error, 'registration', nearAccountId);\n\n const errorObject = new Error(errorMessage);\n onError?.(errorObject);\n\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: errorMessage,\n error: errorMessage\n } as RegistrationSSEEvent);\n\n const result = { success: false, error: errorMessage };\n afterCall?.(false);\n return result;\n }\n}\n\n// Backward-compatible wrapper without explicit confirmationConfig override\nexport async function registerPasskey(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n options: RegistrationHooksOptions,\n authenticatorOptions: AuthenticatorOptions\n): Promise<RegistrationResult> {\n return registerPasskeyInternal(context, nearAccountId, options, authenticatorOptions, undefined);\n}\n\n//////////////////////////////////////\n// HELPER FUNCTIONS\n//////////////////////////////////////\n\n/**\n * Generate a VRF keypair + challenge in VRF wasm worker for WebAuthn registration ceremony bootstrapping\n *\n * ARCHITECTURE: This function solves the chicken-and-egg problem with a single VRF keypair:\n * 1. Generate VRF keypair + challenge (no PRF needed)\n * 2. Persist VRF keypair in worker memory (NOT encrypted yet)\n * 3. Use VRF challenge for WebAuthn ceremony → get PRF output\n * 4. Encrypt the SAME VRF keypair (still in memory) with PRF\n *\n * @param webAuthnManager - WebAuthn manager instance\n * @param nearAccountId - NEAR account ID for VRF input\n * @param blockHeight - Current NEAR block height for freshness\n * @param blockHashBytes - Current NEAR block hash bytes for entropy\n * @returns VRF challenge data (VRF keypair persisted in worker memory)\n */\nexport async function generateBootstrapVrfChallenge(\n context: PasskeyManagerContext,\n nearAccountId: AccountId,\n): Promise<VRFChallenge> {\n\n const { webAuthnManager, nearClient } = context;\n\n const blockInfo = await nearClient.viewBlock({ finality: 'final' });\n\n // Generate VRF keypair and persist in worker memory\n const vrfResult = await webAuthnManager.generateVrfKeypairBootstrap({\n vrfInputData: {\n userId: nearAccountId,\n // Keep VRF rpId consistent with WebAuthn rpId selection logic.\n rpId: webAuthnManager.getRpId(),\n blockHeight: String(blockInfo.header.height),\n blockHash: blockInfo.header.hash,\n },\n saveInMemory: true,\n // VRF keypair persists in worker memory until PRF encryption\n });\n\n if (!vrfResult.vrfChallenge) {\n throw new Error('Registration VRF keypair generation failed');\n }\n return vrfResult.vrfChallenge;\n}\n\n/**\n * Validates registration inputs and throws errors if invalid\n * @param nearAccountId - NEAR account ID to validate\n * @param onEvent - Optional callback for registration progress events\n * @param onError - Optional callback for error handling\n */\nconst validateRegistrationInputs = async (\n context: {\n configs: TatchiConfigs,\n webAuthnManager: WebAuthnManager,\n nearClient: NearClient,\n },\n nearAccountId: AccountId,\n onEvent?: (event: RegistrationSSEEvent) => void,\n onError?: (error: Error) => void,\n) => {\n\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Validating registration inputs...'\n } as RegistrationSSEEvent);\n\n // Validation\n if (!nearAccountId) {\n const error = new Error('NEAR account ID is required for registration.');\n onError?.(error);\n throw error;\n }\n // Validate the account ID format\n const validation = validateNearAccountId(nearAccountId);\n if (!validation.valid) {\n const error = new Error(`Invalid NEAR account ID: ${validation.error}`);\n onError?.(error);\n throw error;\n }\n if (!window.isSecureContext) {\n const error = new Error('Passkey operations require a secure context (HTTPS or localhost).');\n onError?.(error);\n throw error;\n }\n\n // On-chain account existence / contract validation is performed by the relay + contract\n // during the atomic create_account_and_register_user call.\n onEvent?.({\n step: 1,\n phase: RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: `Account format validated, preparing confirmation`\n } as RegistrationSSEEvent);\n return;\n}\n\n/**\n * Rollback registration data in case of errors\n */\nasync function performRegistrationRollback(\n registrationState: {\n accountCreated: boolean;\n contractRegistered: boolean;\n databaseStored: boolean;\n contractTransactionId: string | null;\n },\n nearAccountId: AccountId,\n webAuthnManager: WebAuthnManager,\n onEvent?: (event: RegistrationSSEEvent) => void\n): Promise<void> {\n console.debug('Starting registration rollback...', registrationState);\n\n // Rollback in reverse order\n try {\n // 1. Always clear any in-memory VRF session established during bootstrap\n await webAuthnManager.clearVrfSession();\n\n // 2. Rollback database storage\n if (registrationState.databaseStored) {\n console.debug('Rolling back database storage...');\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: 'Rolling back database storage...',\n error: 'Registration failed - rolling back database storage'\n } as RegistrationSSEEvent);\n\n await webAuthnManager.rollbackUserRegistration(nearAccountId);\n console.debug('Database rollback completed');\n }\n\n // 3. Contract rollback on the Web3Authn contract\n // NOT NEEDED - account creation and contract registration are atomic in the relay server flow\n if (registrationState.contractRegistered) {\n console.debug('Contract registration cannot be rolled back (immutable blockchain state)');\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: `Contract registration (tx: ${registrationState.contractTransactionId}) cannot be rolled back`,\n error: 'Registration failed - contract state is immutable'\n } as RegistrationSSEEvent);\n }\n console.debug('Registration rollback completed');\n\n } catch (rollbackError: unknown) {\n console.error('Rollback failed:', rollbackError);\n onEvent?.({\n step: 0,\n phase: RegistrationPhase.REGISTRATION_ERROR,\n status: RegistrationStatus.ERROR,\n message: `Rollback failed: ${\n (rollbackError && typeof rollbackError === 'object' && 'message' in rollbackError)\n ? String((rollbackError as { message?: unknown }).message || '')\n : String(rollbackError || '')\n }`,\n error: 'Both registration and rollback failed'\n } as RegistrationSSEEvent);\n }\n}\n\nasync function activateThresholdEnrollmentPostRegistration(opts: {\n requestedSignerMode: SignerMode['mode'];\n nearAccountId: AccountId;\n nearPublicKey: string;\n thresholdPublicKey: string;\n relayerKeyId: string;\n clientParticipantId?: number;\n relayerParticipantId?: number;\n thresholdClientVerifyingShareB64u: string | null;\n relayerVerifyingShareB64u: string;\n credential: WebAuthnRegistrationCredential;\n wrapKeySalt: string;\n webAuthnManager: WebAuthnManager;\n nearClient: NearClient;\n onEvent?: (event: RegistrationSSEEvent) => void;\n}): Promise<void> {\n // Optional: activate threshold enrollment post-registration by having the client\n // submit AddKey(thresholdPublicKey) signed with the local key.\n if (opts.requestedSignerMode !== 'threshold-signer') return;\n\n const thresholdPublicKey = String(opts.thresholdPublicKey || '').trim();\n const relayerKeyId = String(opts.relayerKeyId || '').trim();\n const clientVerifyingShareB64u = String(opts.thresholdClientVerifyingShareB64u || '').trim();\n const relayerVerifyingShareB64u = String(opts.relayerVerifyingShareB64u || '').trim();\n\n const emitThresholdKeyEnrollmentResult = (input: {\n thresholdKeyReady: boolean;\n message: string;\n warning?: string;\n }) => {\n opts.onEvent?.({\n step: 7,\n phase: RegistrationPhase.STEP_7_THRESHOLD_KEY_ENROLLMENT,\n status: RegistrationStatus.SUCCESS,\n message: input.message,\n thresholdKeyReady: input.thresholdKeyReady,\n thresholdPublicKey: thresholdPublicKey || undefined,\n relayerKeyId: relayerKeyId || undefined,\n deviceNumber: 1,\n warning: input.warning,\n });\n };\n\n const missingEnrollmentDetails: string[] = [];\n if (!thresholdPublicKey) missingEnrollmentDetails.push('thresholdPublicKey');\n if (!relayerKeyId) missingEnrollmentDetails.push('relayerKeyId');\n if (!clientVerifyingShareB64u) missingEnrollmentDetails.push('clientVerifyingShareB64u');\n if (!relayerVerifyingShareB64u) missingEnrollmentDetails.push('relayerVerifyingShareB64u');\n\n if (missingEnrollmentDetails.length) {\n console.warn('[Registration] threshold-signer requested but threshold enrollment details are missing; continuing with local-signer only');\n emitThresholdKeyEnrollmentResult({\n thresholdKeyReady: false,\n message: 'Threshold key not ready; continuing with local-signer only',\n warning: `Missing threshold enrollment details: ${missingEnrollmentDetails.join(', ')}`,\n });\n return;\n }\n\n try {\n opts.onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.PROGRESS,\n message: 'Activating threshold access key onchain...'\n });\n\n // Prepare a single AddKey transaction signed with the local key (no extra TouchID prompt).\n try {\n opts.webAuthnManager.getNonceManager().initializeUser(opts.nearAccountId, opts.nearPublicKey);\n } catch {}\n const txContext = await opts.webAuthnManager.getNonceManager().getNonceBlockHashAndHeight(\n opts.nearClient,\n { force: true },\n );\n\n const signed = await opts.webAuthnManager.signAddKeyThresholdPublicKeyNoPrompt({\n nearAccountId: opts.nearAccountId,\n credential: opts.credential,\n wrapKeySalt: opts.wrapKeySalt,\n transactionContext: txContext,\n thresholdPublicKey,\n relayerVerifyingShareB64u,\n clientParticipantId: opts.clientParticipantId,\n relayerParticipantId: opts.relayerParticipantId,\n });\n\n const signedTx = signed?.signedTransaction;\n if (!signedTx) throw new Error('Failed to sign AddKey(thresholdPublicKey) transaction');\n\n await opts.nearClient.sendTransaction(signedTx, DEFAULT_WAIT_STATUS.thresholdAddKey);\n\n const thresholdKeyVerified = await verifyAccountAccessKeysPresent(\n opts.nearClient,\n opts.nearAccountId,\n [opts.nearPublicKey, thresholdPublicKey],\n { attempts: 6, delayMs: 250, finality: 'optimistic' },\n );\n if (!thresholdKeyVerified) {\n throw new Error('Threshold access key not found on-chain after AddKey');\n }\n\n await IndexedDBManager.nearKeysDB.storeKeyMaterial({\n kind: 'threshold_ed25519_2p_v1',\n nearAccountId: opts.nearAccountId,\n deviceNumber: 1,\n publicKey: thresholdPublicKey,\n wrapKeySalt: opts.wrapKeySalt,\n relayerKeyId,\n clientShareDerivation: 'prf_first_v1',\n participants: buildThresholdEd25519Participants2pV1({\n clientParticipantId: opts.clientParticipantId,\n relayerParticipantId: opts.relayerParticipantId,\n relayerKeyId,\n relayerUrl: opts.webAuthnManager.tatchiPasskeyConfigs?.relayer?.url,\n clientVerifyingShareB64u,\n relayerVerifyingShareB64u,\n clientShareDerivation: 'prf_first_v1',\n }),\n timestamp: Date.now(),\n });\n\n opts.onEvent?.({\n step: 6,\n phase: RegistrationPhase.STEP_6_ACCOUNT_VERIFICATION,\n status: RegistrationStatus.SUCCESS,\n message: 'Threshold access key activated on-chain'\n });\n\n emitThresholdKeyEnrollmentResult({\n thresholdKeyReady: true,\n message: 'Threshold key ready',\n });\n } catch (e) {\n const warning = (e && typeof e === 'object' && 'message' in e)\n ? String((e as { message?: unknown }).message || '')\n : String(e || '');\n console.warn('[Registration] threshold enrollment activation failed; continuing with local-signer only:', e);\n emitThresholdKeyEnrollmentResult({\n thresholdKeyReady: false,\n message: 'Threshold key not ready; continuing with local-signer only',\n warning,\n });\n }\n}\n\nasync function verifyAccountAccessKeysPresent(\n nearClient: NearClient,\n nearAccountId: string,\n expectedPublicKeys: string[],\n opts?: { attempts?: number; delayMs?: number; finality?: 'optimistic' | 'final' },\n): Promise<boolean> {\n const unique = Array.from(\n new Set(expectedPublicKeys.map((k) => ensureEd25519Prefix(k)).filter(Boolean)),\n );\n if (!unique.length) return false;\n\n const attempts = Math.max(1, Math.floor(opts?.attempts ?? 6));\n const delayMs = Math.max(50, Math.floor(opts?.delayMs ?? 750));\n const finality = opts?.finality ?? 'optimistic';\n\n for (let i = 0; i < attempts; i++) {\n try {\n const accessKeyList = await nearClient.viewAccessKeyList(\n nearAccountId,\n { finality } as any,\n );\n const keys = accessKeyList.keys.map((k) => ensureEd25519Prefix(k.public_key)).filter(Boolean);\n const allPresent = unique.every((expected) => keys.includes(expected));\n if (allPresent) return true;\n } catch {\n // tolerate transient view errors during propagation; retry\n }\n if (i < attempts - 1) {\n await new Promise((res) => setTimeout(res, delayMs));\n }\n }\n return false;\n}\n"],"mappings":"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"}
|
|
@@ -24,7 +24,7 @@ const HIDE_PHASES = new Set([
|
|
|
24
24
|
require_sdkSentEvents.DeviceLinkingPhase.LOGIN_ERROR,
|
|
25
25
|
require_sdkSentEvents.DeviceLinkingPhase.DEVICE_LINKING_ERROR,
|
|
26
26
|
require_sdkSentEvents.RegistrationPhase.STEP_5_CONTRACT_REGISTRATION,
|
|
27
|
-
require_sdkSentEvents.RegistrationPhase.
|
|
27
|
+
require_sdkSentEvents.RegistrationPhase.STEP_9_REGISTRATION_COMPLETE,
|
|
28
28
|
require_sdkSentEvents.RegistrationPhase.REGISTRATION_ERROR,
|
|
29
29
|
require_sdkSentEvents.LoginPhase.STEP_3_VRF_UNLOCK,
|
|
30
30
|
require_sdkSentEvents.LoginPhase.STEP_4_LOGIN_COMPLETE,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"on-events-progress-bus.js","names":["ActionPhase","DelegateActionPhase","RegistrationPhase","EmailRecoveryPhase","DeviceLinkingPhase","SyncAccountPhase","LoginPhase","defaultPhaseHeuristics: PhaseHeuristics"],"sources":["../../../../../../../src/core/WalletIframe/client/on-events-progress-bus.ts"],"sourcesContent":["/**\n * OnEventsProgressBus - Client-Side Communication Layer\n *\n * Manages progress event routing and overlay visibility *intents* for the wallet\n * iframe. It never manipulates the iframe directly; instead it calls the\n * injected OverlayController interface (show/hide), and WalletIframeRouter\n * owns the concrete OverlayController that knows how to display the iframe.\n *\n * Key Responsibilities:\n * - Progress Routing: Dispatches typed progress payloads to per-request subscribers\n * - Overlay Intents: Applies SHOW/HIDE based on phase heuristics, leaving actual\n * DOM/CSS work to WalletIframeRouter + OverlayController\n * - Concurrent Aggregation: Tracks overlay demand per requestId and only hides\n * when no request still requires SHOW (multi-request safe)\n * - Sticky Subscriptions: Supports long-running subscriptions that persist after completion\n * - Phase Heuristics: Pluggable logic to map phases → 'show' | 'hide' | 'none'\n * - Event Statistics: Tracks counts/timestamps for debugging\n */\n\nimport type { ProgressPayload as MessageProgressPayload } from '../shared/messages';\nimport {\n ActionPhase,\n DeviceLinkingPhase,\n SyncAccountPhase,\n RegistrationPhase,\n LoginPhase,\n EmailRecoveryPhase,\n DelegateActionPhase,\n} from '../../types/sdkSentEvents';\n\n// Phases that should temporarily SHOW the overlay (to capture activation)\n// IMPORTANT: STEP_2_USER_CONFIRMATION must remain in this list. Without it,\n// modal confirmation with behavior: 'requireClick' will never be visible in\n// iframe mode, because the wallet iframe is still 0×0 when the modal mounts.\nconst SHOW_PHASES = new Set<string>([\n // Gate overlay to moments of imminent activation only.\n // Show early during user confirmation so the modal inside the wallet iframe is visible\n // and can capture the required click when behavior === 'requireClick'.\n ActionPhase.STEP_2_USER_CONFIRMATION,\n DelegateActionPhase.STEP_2_USER_CONFIRMATION,\n ActionPhase.STEP_3_WEBAUTHN_AUTHENTICATION,\n DelegateActionPhase.STEP_3_WEBAUTHN_AUTHENTICATION,\n // Registration requires a WebAuthn create() ceremony at step 1\n RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n // Email recovery: TouchID registration uses WebAuthn create()\n EmailRecoveryPhase.STEP_2_TOUCH_ID_REGISTRATION,\n // Device1: TouchID authorization (host needs overlay to capture activation)\n DeviceLinkingPhase.STEP_3_AUTHORIZATION,\n // Device2: Registration inside wallet host (collects passkey via ModalTxConfirmer)\n // Show overlay so the wallet iframe is visible and focused for WebAuthn\n DeviceLinkingPhase.STEP_6_REGISTRATION,\n SyncAccountPhase.STEP_2_WEBAUTHN_AUTHENTICATION,\n LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n]);\n\n// Phases that should HIDE the overlay asap (post-activation, non-interactive)\nconst HIDE_PHASES = new Set<string>([\n ActionPhase.STEP_4_AUTHENTICATION_COMPLETE,\n ActionPhase.STEP_5_TRANSACTION_SIGNING_PROGRESS,\n ActionPhase.STEP_6_TRANSACTION_SIGNING_COMPLETE,\n ActionPhase.STEP_7_BROADCASTING,\n ActionPhase.STEP_8_ACTION_COMPLETE,\n // Device linking: hide when the flow has finished or errored\n DeviceLinkingPhase.STEP_7_LINKING_COMPLETE,\n DeviceLinkingPhase.REGISTRATION_ERROR,\n DeviceLinkingPhase.LOGIN_ERROR,\n DeviceLinkingPhase.DEVICE_LINKING_ERROR,\n // Registration: hide once contract work starts or flow completes/errors\n RegistrationPhase.STEP_5_CONTRACT_REGISTRATION,\n RegistrationPhase.STEP_8_REGISTRATION_COMPLETE,\n RegistrationPhase.REGISTRATION_ERROR,\n // Login: hide after assertion leads to VRF unlock or completion/errors\n LoginPhase.STEP_3_VRF_UNLOCK,\n LoginPhase.STEP_4_LOGIN_COMPLETE,\n LoginPhase.LOGIN_ERROR,\n // Account sync: hide after authentication completes or on completion/errors\n SyncAccountPhase.STEP_4_AUTHENTICATOR_SAVED,\n SyncAccountPhase.STEP_5_SYNC_ACCOUNT_COMPLETE,\n SyncAccountPhase.ERROR,\n // Email recovery: hide after finalization/complete or on error\n EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n EmailRecoveryPhase.STEP_6_COMPLETE,\n EmailRecoveryPhase.ERROR,\n]);\n\nexport type ProgressPayload = MessageProgressPayload;\n\n// Minimal overlay control interface used by ProgressBus.\n// Implemented by WalletIframeRouter via an adapter object that calls\n// into the concrete OverlayToggler (fullscreen/anchored) as needed.\nexport interface OverlayToggler {\n show: () => void;\n hide: () => void;\n}\n\nexport type PhaseHeuristics = (payload: ProgressPayload) => 'show' | 'hide' | 'none';\n\nexport interface ProgressSubscriber {\n onProgress?: (payload: ProgressPayload) => void;\n sticky: boolean;\n stats: { count: number; lastPhase: string | null; lastAt: number | null };\n}\n\nexport class OnEventsProgressBus {\n private subs = new Map<string, ProgressSubscriber>();\n private logger?: (msg: string, data?: Record<string, unknown>) => void;\n private overlay: OverlayToggler;\n private heuristic: PhaseHeuristics;\n // Track the most recent overlay intent per requestId so that we can\n // aggregate visibility across concurrent requests. If any request's\n // latest intent is 'show', we keep the overlay visible.\n private overlayDemands = new Map<string, 'show' | 'hide' | 'none'>();\n\n constructor(overlay: OverlayToggler, heuristic: PhaseHeuristics, logger?: (msg: string, data?: Record<string, unknown>) => void) {\n this.overlay = overlay;\n this.heuristic = heuristic;\n this.logger = logger;\n }\n\n /**\n * Register a subscriber for a requestId.\n * Initializes demand tracking to 'none' (neutral) until phases arrive.\n */\n register({ requestId, onProgress, sticky = false }: {\n requestId: string,\n sticky: boolean,\n onProgress?: (p: ProgressPayload) => void,\n }): void {\n this.subs.set(requestId, {\n onProgress,\n sticky,\n stats: { count: 0, lastPhase: null, lastAt: null }\n });\n // Initialize demand tracking for this request as neutral\n this.overlayDemands.set(requestId, 'none');\n this.log('register', { requestId, sticky });\n }\n\n /**\n * Unregister a subscriber and clear its overlay demand.\n * If no remaining requests demand 'show', the overlay is hidden.\n */\n unregister(requestId: string): void {\n if (this.subs.delete(requestId)) this.log('unregister', { requestId });\n // Remove any overlay demand for this request\n this.overlayDemands.delete(requestId);\n // If no remaining requests demand 'show', we can safely hide\n if (!this.wantsVisible()) {\n try { this.overlay.hide(); } catch {}\n }\n }\n\n /**\n * Remove all subscribers and demands; overlay demand set is cleared.\n */\n clearAll(): void {\n this.subs.clear();\n this.overlayDemands.clear();\n this.log('clearAll');\n }\n\n isSticky(requestId: string): boolean {\n const sub = this.subs.get(requestId);\n return !!sub?.sticky;\n }\n\n /**\n * Dispatch a progress payload to a request's subscriber and update\n * the aggregate overlay demand based on the phase heuristic.\n */\n dispatch({ requestId, payload }: {\n requestId: string,\n payload: ProgressPayload\n }): boolean {\n\n const phase = String((payload || {}).phase || '');\n const action = this.heuristic(payload);\n\n // Update the latest demand for this request\n this.overlayDemands.set(requestId, action);\n\n // Apply aggregated overlay visibility:\n // - If any request currently demands 'show', ensure overlay is visible\n // - Only hide when no outstanding 'show' demands remain\n if (action === 'show') {\n try { this.overlay.show(); } catch {}\n } else if (action === 'hide') {\n if (!this.wantsVisible()) {\n try { this.overlay.hide(); } catch {}\n }\n }\n\n const sub = this.subs.get(requestId);\n if (sub) {\n this.bumpStats(sub, phase);\n try { sub.onProgress?.(payload); } catch {}\n this.log('dispatch', { requestId, phase, sticky: sub.sticky });\n return true;\n }\n\n // Deliver to sticky-only subscriber if present (e.g., flow finished but status updates continue)\n const sticky = this.findSticky(requestId);\n if (sticky) {\n this.bumpStats(sticky, phase);\n try { sticky.onProgress?.(payload); } catch {}\n this.log('dispatch-sticky', { requestId, phase });\n return true;\n }\n this.log('dispatch-miss', { requestId, phase });\n return false;\n }\n\n getStats(requestId: string): {\n count: number;\n lastPhase: string | null;\n lastAt: number | null\n } | null {\n const sub = this.subs.get(requestId);\n return sub ? sub.stats : null;\n }\n\n /**\n * Returns true if any tracked request currently demands the overlay be visible.\n * Useful for higher layers (router) to avoid premature hides on completion/timeout.\n */\n wantsVisible(): boolean {\n for (const v of this.overlayDemands.values()) {\n if (v === 'show') return true;\n }\n return false;\n }\n\n private findSticky(requestId: string): ProgressSubscriber | null {\n const sub = this.subs.get(requestId);\n if (sub && sub.sticky) return sub;\n // sticky subscribers are keyed by the same requestId in this design\n return null;\n }\n\n private bumpStats(sub: ProgressSubscriber, phase: string) {\n sub.stats.count += 1;\n sub.stats.lastPhase = phase || null;\n sub.stats.lastAt = Date.now();\n }\n\n private log(msg: string, data?: Record<string, unknown>) {\n try { this.logger?.(msg, data); } catch {}\n }\n}\n\n// Default phase heuristic used by the client\n/**\n * defaultPhaseHeuristics\n *\n * Decides when to expand or contract the invisible wallet iframe overlay\n * based on incoming progress events (phases). Returning:\n * - 'show' → expands the iframe to a full-screen, invisible layer that captures\n * user activation (e.g., TouchID / WebAuthn prompts) and pointer events.\n * - 'hide' → immediately contracts the iframe back to 0×0 so it no longer blocks clicks.\n * - 'none' → no change.\n *\n * Important UX constraint: the overlay covers the entire viewport and is\n * intentionally invisible. While expanded, it will intercept clicks and can\n * block interactions with the app. Therefore, we must minimize the time it is\n * expanded and only show it during the brief windows where user activation is\n * required (e.g., when the TouchID prompt is about to appear or the modal is\n * mounting and needs focus/activation in the iframe context). As soon as\n * activation completes (e.g., authentication-complete), we hide it again.\n *\n * If new phases are introduced that require user activation, add them to\n * SHOW_PHASES; if phases become non-interactive post-activation, add them to\n * HIDE_PHASES. The goal is to keep the overlay up for the minimum possible time.\n */\nexport const defaultPhaseHeuristics: PhaseHeuristics = (payload: ProgressPayload) => {\n try {\n const phase = String((payload || {}).phase || '');\n if (!phase) return 'none';\n\n // Step 1: Check if this phase requires showing the overlay for user activation\n if (SHOW_PHASES.has(phase)) return 'show';\n\n // Step 2: Check if this phase indicates we should hide the overlay (post-activation)\n if (HIDE_PHASES.has(phase)) return 'hide';\n\n // Step 3: Handle legacy/custom completion markers\n const raw = phase.toLowerCase();\n if (raw === 'user-confirmation-complete') return 'hide';\n\n // Step 4: Extra hardening - hide overlay on explicit cancellation\n if (raw === 'cancelled') return 'hide';\n\n // Step 5: Default to no change for unknown phases\n return 'none';\n } catch { return 'none'; }\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"on-events-progress-bus.js","names":["ActionPhase","DelegateActionPhase","RegistrationPhase","EmailRecoveryPhase","DeviceLinkingPhase","SyncAccountPhase","LoginPhase","defaultPhaseHeuristics: PhaseHeuristics"],"sources":["../../../../../../../src/core/WalletIframe/client/on-events-progress-bus.ts"],"sourcesContent":["/**\n * OnEventsProgressBus - Client-Side Communication Layer\n *\n * Manages progress event routing and overlay visibility *intents* for the wallet\n * iframe. It never manipulates the iframe directly; instead it calls the\n * injected OverlayController interface (show/hide), and WalletIframeRouter\n * owns the concrete OverlayController that knows how to display the iframe.\n *\n * Key Responsibilities:\n * - Progress Routing: Dispatches typed progress payloads to per-request subscribers\n * - Overlay Intents: Applies SHOW/HIDE based on phase heuristics, leaving actual\n * DOM/CSS work to WalletIframeRouter + OverlayController\n * - Concurrent Aggregation: Tracks overlay demand per requestId and only hides\n * when no request still requires SHOW (multi-request safe)\n * - Sticky Subscriptions: Supports long-running subscriptions that persist after completion\n * - Phase Heuristics: Pluggable logic to map phases → 'show' | 'hide' | 'none'\n * - Event Statistics: Tracks counts/timestamps for debugging\n */\n\nimport type { ProgressPayload as MessageProgressPayload } from '../shared/messages';\nimport {\n ActionPhase,\n DeviceLinkingPhase,\n SyncAccountPhase,\n RegistrationPhase,\n LoginPhase,\n EmailRecoveryPhase,\n DelegateActionPhase,\n} from '../../types/sdkSentEvents';\n\n// Phases that should temporarily SHOW the overlay (to capture activation)\n// IMPORTANT: STEP_2_USER_CONFIRMATION must remain in this list. Without it,\n// modal confirmation with behavior: 'requireClick' will never be visible in\n// iframe mode, because the wallet iframe is still 0×0 when the modal mounts.\nconst SHOW_PHASES = new Set<string>([\n // Gate overlay to moments of imminent activation only.\n // Show early during user confirmation so the modal inside the wallet iframe is visible\n // and can capture the required click when behavior === 'requireClick'.\n ActionPhase.STEP_2_USER_CONFIRMATION,\n DelegateActionPhase.STEP_2_USER_CONFIRMATION,\n ActionPhase.STEP_3_WEBAUTHN_AUTHENTICATION,\n DelegateActionPhase.STEP_3_WEBAUTHN_AUTHENTICATION,\n // Registration requires a WebAuthn create() ceremony at step 1\n RegistrationPhase.STEP_1_WEBAUTHN_VERIFICATION,\n // Email recovery: TouchID registration uses WebAuthn create()\n EmailRecoveryPhase.STEP_2_TOUCH_ID_REGISTRATION,\n // Device1: TouchID authorization (host needs overlay to capture activation)\n DeviceLinkingPhase.STEP_3_AUTHORIZATION,\n // Device2: Registration inside wallet host (collects passkey via ModalTxConfirmer)\n // Show overlay so the wallet iframe is visible and focused for WebAuthn\n DeviceLinkingPhase.STEP_6_REGISTRATION,\n SyncAccountPhase.STEP_2_WEBAUTHN_AUTHENTICATION,\n LoginPhase.STEP_2_WEBAUTHN_ASSERTION,\n]);\n\n// Phases that should HIDE the overlay asap (post-activation, non-interactive)\nconst HIDE_PHASES = new Set<string>([\n ActionPhase.STEP_4_AUTHENTICATION_COMPLETE,\n ActionPhase.STEP_5_TRANSACTION_SIGNING_PROGRESS,\n ActionPhase.STEP_6_TRANSACTION_SIGNING_COMPLETE,\n ActionPhase.STEP_7_BROADCASTING,\n ActionPhase.STEP_8_ACTION_COMPLETE,\n // Device linking: hide when the flow has finished or errored\n DeviceLinkingPhase.STEP_7_LINKING_COMPLETE,\n DeviceLinkingPhase.REGISTRATION_ERROR,\n DeviceLinkingPhase.LOGIN_ERROR,\n DeviceLinkingPhase.DEVICE_LINKING_ERROR,\n // Registration: hide once contract work starts or flow completes/errors\n RegistrationPhase.STEP_5_CONTRACT_REGISTRATION,\n RegistrationPhase.STEP_9_REGISTRATION_COMPLETE,\n RegistrationPhase.REGISTRATION_ERROR,\n // Login: hide after assertion leads to VRF unlock or completion/errors\n LoginPhase.STEP_3_VRF_UNLOCK,\n LoginPhase.STEP_4_LOGIN_COMPLETE,\n LoginPhase.LOGIN_ERROR,\n // Account sync: hide after authentication completes or on completion/errors\n SyncAccountPhase.STEP_4_AUTHENTICATOR_SAVED,\n SyncAccountPhase.STEP_5_SYNC_ACCOUNT_COMPLETE,\n SyncAccountPhase.ERROR,\n // Email recovery: hide after finalization/complete or on error\n EmailRecoveryPhase.STEP_5_FINALIZING_REGISTRATION,\n EmailRecoveryPhase.STEP_6_COMPLETE,\n EmailRecoveryPhase.ERROR,\n]);\n\nexport type ProgressPayload = MessageProgressPayload;\n\n// Minimal overlay control interface used by ProgressBus.\n// Implemented by WalletIframeRouter via an adapter object that calls\n// into the concrete OverlayToggler (fullscreen/anchored) as needed.\nexport interface OverlayToggler {\n show: () => void;\n hide: () => void;\n}\n\nexport type PhaseHeuristics = (payload: ProgressPayload) => 'show' | 'hide' | 'none';\n\nexport interface ProgressSubscriber {\n onProgress?: (payload: ProgressPayload) => void;\n sticky: boolean;\n stats: { count: number; lastPhase: string | null; lastAt: number | null };\n}\n\nexport class OnEventsProgressBus {\n private subs = new Map<string, ProgressSubscriber>();\n private logger?: (msg: string, data?: Record<string, unknown>) => void;\n private overlay: OverlayToggler;\n private heuristic: PhaseHeuristics;\n // Track the most recent overlay intent per requestId so that we can\n // aggregate visibility across concurrent requests. If any request's\n // latest intent is 'show', we keep the overlay visible.\n private overlayDemands = new Map<string, 'show' | 'hide' | 'none'>();\n\n constructor(overlay: OverlayToggler, heuristic: PhaseHeuristics, logger?: (msg: string, data?: Record<string, unknown>) => void) {\n this.overlay = overlay;\n this.heuristic = heuristic;\n this.logger = logger;\n }\n\n /**\n * Register a subscriber for a requestId.\n * Initializes demand tracking to 'none' (neutral) until phases arrive.\n */\n register({ requestId, onProgress, sticky = false }: {\n requestId: string,\n sticky: boolean,\n onProgress?: (p: ProgressPayload) => void,\n }): void {\n this.subs.set(requestId, {\n onProgress,\n sticky,\n stats: { count: 0, lastPhase: null, lastAt: null }\n });\n // Initialize demand tracking for this request as neutral\n this.overlayDemands.set(requestId, 'none');\n this.log('register', { requestId, sticky });\n }\n\n /**\n * Unregister a subscriber and clear its overlay demand.\n * If no remaining requests demand 'show', the overlay is hidden.\n */\n unregister(requestId: string): void {\n if (this.subs.delete(requestId)) this.log('unregister', { requestId });\n // Remove any overlay demand for this request\n this.overlayDemands.delete(requestId);\n // If no remaining requests demand 'show', we can safely hide\n if (!this.wantsVisible()) {\n try { this.overlay.hide(); } catch {}\n }\n }\n\n /**\n * Remove all subscribers and demands; overlay demand set is cleared.\n */\n clearAll(): void {\n this.subs.clear();\n this.overlayDemands.clear();\n this.log('clearAll');\n }\n\n isSticky(requestId: string): boolean {\n const sub = this.subs.get(requestId);\n return !!sub?.sticky;\n }\n\n /**\n * Dispatch a progress payload to a request's subscriber and update\n * the aggregate overlay demand based on the phase heuristic.\n */\n dispatch({ requestId, payload }: {\n requestId: string,\n payload: ProgressPayload\n }): boolean {\n\n const phase = String((payload || {}).phase || '');\n const action = this.heuristic(payload);\n\n // Update the latest demand for this request\n this.overlayDemands.set(requestId, action);\n\n // Apply aggregated overlay visibility:\n // - If any request currently demands 'show', ensure overlay is visible\n // - Only hide when no outstanding 'show' demands remain\n if (action === 'show') {\n try { this.overlay.show(); } catch {}\n } else if (action === 'hide') {\n if (!this.wantsVisible()) {\n try { this.overlay.hide(); } catch {}\n }\n }\n\n const sub = this.subs.get(requestId);\n if (sub) {\n this.bumpStats(sub, phase);\n try { sub.onProgress?.(payload); } catch {}\n this.log('dispatch', { requestId, phase, sticky: sub.sticky });\n return true;\n }\n\n // Deliver to sticky-only subscriber if present (e.g., flow finished but status updates continue)\n const sticky = this.findSticky(requestId);\n if (sticky) {\n this.bumpStats(sticky, phase);\n try { sticky.onProgress?.(payload); } catch {}\n this.log('dispatch-sticky', { requestId, phase });\n return true;\n }\n this.log('dispatch-miss', { requestId, phase });\n return false;\n }\n\n getStats(requestId: string): {\n count: number;\n lastPhase: string | null;\n lastAt: number | null\n } | null {\n const sub = this.subs.get(requestId);\n return sub ? sub.stats : null;\n }\n\n /**\n * Returns true if any tracked request currently demands the overlay be visible.\n * Useful for higher layers (router) to avoid premature hides on completion/timeout.\n */\n wantsVisible(): boolean {\n for (const v of this.overlayDemands.values()) {\n if (v === 'show') return true;\n }\n return false;\n }\n\n private findSticky(requestId: string): ProgressSubscriber | null {\n const sub = this.subs.get(requestId);\n if (sub && sub.sticky) return sub;\n // sticky subscribers are keyed by the same requestId in this design\n return null;\n }\n\n private bumpStats(sub: ProgressSubscriber, phase: string) {\n sub.stats.count += 1;\n sub.stats.lastPhase = phase || null;\n sub.stats.lastAt = Date.now();\n }\n\n private log(msg: string, data?: Record<string, unknown>) {\n try { this.logger?.(msg, data); } catch {}\n }\n}\n\n// Default phase heuristic used by the client\n/**\n * defaultPhaseHeuristics\n *\n * Decides when to expand or contract the invisible wallet iframe overlay\n * based on incoming progress events (phases). Returning:\n * - 'show' → expands the iframe to a full-screen, invisible layer that captures\n * user activation (e.g., TouchID / WebAuthn prompts) and pointer events.\n * - 'hide' → immediately contracts the iframe back to 0×0 so it no longer blocks clicks.\n * - 'none' → no change.\n *\n * Important UX constraint: the overlay covers the entire viewport and is\n * intentionally invisible. While expanded, it will intercept clicks and can\n * block interactions with the app. Therefore, we must minimize the time it is\n * expanded and only show it during the brief windows where user activation is\n * required (e.g., when the TouchID prompt is about to appear or the modal is\n * mounting and needs focus/activation in the iframe context). As soon as\n * activation completes (e.g., authentication-complete), we hide it again.\n *\n * If new phases are introduced that require user activation, add them to\n * SHOW_PHASES; if phases become non-interactive post-activation, add them to\n * HIDE_PHASES. The goal is to keep the overlay up for the minimum possible time.\n */\nexport const defaultPhaseHeuristics: PhaseHeuristics = (payload: ProgressPayload) => {\n try {\n const phase = String((payload || {}).phase || '');\n if (!phase) return 'none';\n\n // Step 1: Check if this phase requires showing the overlay for user activation\n if (SHOW_PHASES.has(phase)) return 'show';\n\n // Step 2: Check if this phase indicates we should hide the overlay (post-activation)\n if (HIDE_PHASES.has(phase)) return 'hide';\n\n // Step 3: Handle legacy/custom completion markers\n const raw = phase.toLowerCase();\n if (raw === 'user-confirmation-complete') return 'hide';\n\n // Step 4: Extra hardening - hide overlay on explicit cancellation\n if (raw === 'cancelled') return 'hide';\n\n // Step 5: Default to no change for unknown phases\n return 'none';\n } catch { return 'none'; }\n};\n"],"mappings":"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"}
|
|
@@ -7,8 +7,9 @@ let RegistrationPhase = /* @__PURE__ */ function(RegistrationPhase$1) {
|
|
|
7
7
|
RegistrationPhase$1["STEP_4_ACCESS_KEY_ADDITION"] = "access-key-addition";
|
|
8
8
|
RegistrationPhase$1["STEP_5_CONTRACT_REGISTRATION"] = "contract-registration";
|
|
9
9
|
RegistrationPhase$1["STEP_6_ACCOUNT_VERIFICATION"] = "account-verification";
|
|
10
|
-
RegistrationPhase$1["
|
|
11
|
-
RegistrationPhase$1["
|
|
10
|
+
RegistrationPhase$1["STEP_7_THRESHOLD_KEY_ENROLLMENT"] = "threshold-key-enrollment";
|
|
11
|
+
RegistrationPhase$1["STEP_8_DATABASE_STORAGE"] = "database-storage";
|
|
12
|
+
RegistrationPhase$1["STEP_9_REGISTRATION_COMPLETE"] = "registration-complete";
|
|
12
13
|
RegistrationPhase$1["REGISTRATION_ERROR"] = "error";
|
|
13
14
|
return RegistrationPhase$1;
|
|
14
15
|
}({});
|