UNPKG

@turnkey/react-wallet-kit

Version:

The easiest and most powerful way to integrate Turnkey's Embedded Wallets into your React applications.

1,064 lines (1,062 loc) 110 kB
'use client'; 'use strict'; var jsxRuntime = require('react/jsx-runtime'); var sha2 = require('@noble/hashes/sha2'); var utils$1 = require('@noble/hashes/utils'); var utils = require('../../utils.js'); var core = require('@turnkey/core'); var react = require('react'); var sdkTypes = require('@turnkey/sdk-types'); var Hook = require('../modal/Hook.js'); var base = require('../../types/base.js'); var index = require('../../components/auth/index.js'); var reactFontawesome = require('@fortawesome/react-fontawesome'); var freeBrandsSvgIcons = require('@fortawesome/free-brands-svg-icons'); var Action = require('../../components/auth/Action.js'); var Message = require('../../components/sign/Message.js'); var Export = require('../../components/export/Export.js'); var Import = require('../../components/import/Import.js'); var Success = require('../../components/design/Success.js'); var UpdateEmail = require('../../components/user/UpdateEmail.js'); var UpdatePhoneNumber = require('../../components/user/UpdatePhoneNumber.js'); var UpdateUserName = require('../../components/user/UpdateUserName.js'); var RemoveOAuthProvider = require('../../components/user/RemoveOAuthProvider.js'); var RemovePasskey = require('../../components/user/RemovePasskey.js'); var LinkWallet = require('../../components/user/LinkWallet.js'); var Types = require('./Types.js'); var OTP = require('../../components/auth/OTP.js'); var RemoveEmail = require('../../components/user/RemoveEmail.js'); var RemovePhoneNumber = require('../../components/user/RemovePhoneNumber.js'); /** * Provides Turnkey client authentication, session management, wallet operations, and user profile management * for the React Wallet Kit SDK. This context provider encapsulates all core authentication flows (Passkey, Wallet, OTP, OAuth), * session lifecycle (creation, expiration, refresh), wallet linking/import/export, and user profile updates (email, phone, name). * * The provider automatically initializes the Turnkey client, fetches configuration (including proxy auth config if needed), * and synchronizes session and authentication state. It exposes a comprehensive set of methods for authentication flows, * wallet management, and user profile operations, as well as UI handlers for modal-driven flows. * * Features: * - Passkey, Wallet, OTP (Email/SMS), and OAuth (Google, Apple, Facebook) authentication and sign-up flows. * - Session management: creation, expiration scheduling, refresh, and clearing. * - Wallet management: fetch, link, import, export, account management. * - User profile management: email, phone, name, OAuth provider, and passkey linking/removal. * - Modal-driven UI flows for authentication, wallet linking, and profile updates. * - Error handling and callback integration for custom error and event responses. * * Usage: * Wrap your application with `TurnkeyProvider` to enable authentication and wallet features via context. * * @param config - The Turnkey provider configuration object. * @param children - React children to be rendered within the provider. * @param callbacks - Optional callbacks for error handling and session events. * * @returns A React context provider exposing authentication, wallet, and user management methods and state. */ const ClientProvider = ({ config, children, callbacks }) => { const [client, setClient] = react.useState(undefined); const [session, setSession] = react.useState(undefined); const [masterConfig, setMasterConfig] = react.useState(undefined); const [wallets, setWallets] = react.useState([]); const [user, setUser] = react.useState(undefined); const [clientState, setClientState] = react.useState(); const [authState, setAuthState] = react.useState(base.AuthState.Unauthenticated); // we use this custom hook to only update the state if the value is different // this is so our useEffect that calls `initializeWalletProviderListeners()` only runs when it needs to const [walletProviders, setWalletProviders] = utils.useWalletProviderState(); const expiryTimeoutsRef = react.useRef({}); const proxyAuthConfigRef = react.useRef(null); const [allSessions, setAllSessions] = react.useState(undefined); const { pushPage, closeModal } = Hook.useModal(); const completeRedirectOauth = async () => { // Check for either hash or search parameters that could indicate an OAuth redirect if (window.location.hash || window.location.search) { // Handle Facebook redirect (uses search params with code) if (window.location.search && window.location.search.includes("code=") && window.location.search.includes("state=")) { const searchParams = new URLSearchParams(window.location.search.substring(1)); const code = searchParams.get("code"); const state = searchParams.get("state"); // Parse state parameter if (state && code) { const stateParams = new URLSearchParams(state); const provider = stateParams.get("provider"); const flow = stateParams.get("flow"); const publicKey = stateParams.get("publicKey"); const openModal = stateParams.get("openModal"); if (provider === "facebook" && flow === "redirect" && publicKey) { // We have all the required parameters for a Facebook PKCE flow const clientId = masterConfig?.auth?.oauthConfig?.facebookClientId; const redirectURI = masterConfig?.auth?.oauthConfig?.oauthRedirectUri; if (clientId && redirectURI) { await utils.handleFacebookPKCEFlow({ code, publicKey, openModal, clientId, redirectURI, callbacks, completeOauth, onPushPage: oidcToken => { return new Promise((resolve, reject) => { pushPage({ key: `Facebook OAuth`, content: jsxRuntime.jsx(Action.ActionPage, { title: `Authenticating with Facebook...`, action: async () => { try { await completeOauth({ oidcToken, publicKey, providerName: "facebook" }); resolve(); } catch (err) { reject(err); } }, icon: jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { size: "3x", icon: freeBrandsSvgIcons.faFacebook }) }), showTitle: false }); }); } }).catch(error => { // Handle errors if (callbacks?.onError) { callbacks.onError(error instanceof sdkTypes.TurnkeyError ? error : new sdkTypes.TurnkeyError("Facebook authentication failed", sdkTypes.TurnkeyErrorCodes.OAUTH_SIGNUP_ERROR, error)); } }); } } } } // Handle Google/Apple redirects (uses hash with id_token) else if (window.location.hash) { const hash = window.location.hash.substring(1); // Parse the hash using our helper functions const { idToken, provider, flow, publicKey, openModal } = utils.parseOAuthRedirect(hash); if (idToken && flow === "redirect" && publicKey) { if (openModal === "true") { const providerName = provider ? provider.charAt(0).toUpperCase() + provider.slice(1) : "Provider"; // Determine which icon to show based on the provider let icon; if (provider === "apple") { icon = jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { size: "3x", icon: freeBrandsSvgIcons.faApple }); } else { // Default to Google icon icon = jsxRuntime.jsx(reactFontawesome.FontAwesomeIcon, { size: "3x", icon: freeBrandsSvgIcons.faGoogle }); } // This state is set when the OAuth flow comes from the AuthComponent await new Promise((resolve, reject) => { pushPage({ key: `${providerName} OAuth`, content: jsxRuntime.jsx(Action.ActionPage, { title: `Authenticating with ${providerName}...`, action: async () => { try { await completeOauth({ oidcToken: idToken, publicKey, ...(provider ? { providerName: provider } : {}) }); resolve(null); } catch (err) { reject(err); } }, icon: icon }), showTitle: false }); }); } else if (callbacks?.onOauthRedirect) { callbacks.onOauthRedirect({ idToken, publicKey }); } else { completeOauth({ oidcToken: idToken, publicKey, ...(provider ? { providerName: provider } : {}) }); } // Clean up the URL after processing window.history.replaceState(null, document.title, window.location.pathname + window.location.search); } } } }; const buildConfig = proxyAuthConfig => { // Juggle the local overrides with the values set in the dashboard (proxyAuthConfig). const resolvedMethods = { emailOtpAuthEnabled: config.auth?.methods?.emailOtpAuthEnabled ?? proxyAuthConfig?.enabledProviders.includes("email"), smsOtpAuthEnabled: config.auth?.methods?.smsOtpAuthEnabled ?? proxyAuthConfig?.enabledProviders.includes("sms"), passkeyAuthEnabled: config.auth?.methods?.passkeyAuthEnabled ?? proxyAuthConfig?.enabledProviders.includes("passkey"), walletAuthEnabled: config.auth?.methods?.walletAuthEnabled ?? proxyAuthConfig?.enabledProviders.includes("wallet"), googleOauthEnabled: config.auth?.methods?.googleOauthEnabled ?? proxyAuthConfig?.enabledProviders.includes("google"), appleOauthEnabled: config.auth?.methods?.appleOauthEnabled ?? proxyAuthConfig?.enabledProviders.includes("apple"), facebookOauthEnabled: config.auth?.methods?.facebookOauthEnabled ?? proxyAuthConfig?.enabledProviders.includes("facebook") }; // Set a default ordering for the oAuth methods const oauthOrder = config.auth?.oauthOrder ?? ["google", "apple", "facebook"].filter(provider => resolvedMethods[`${provider}OauthEnabled`]); // Set a default ordering for the overall auth methods const methodOrder = config.auth?.methodOrder ?? [oauthOrder.length > 0 ? "socials" : null, resolvedMethods.emailOtpAuthEnabled ? "email" : null, resolvedMethods.smsOtpAuthEnabled ? "sms" : null, resolvedMethods.passkeyAuthEnabled ? "passkey" : null, resolvedMethods.walletAuthEnabled ? "wallet" : null].filter(Boolean); return { ...config, // Overrides: auth: { ...config.auth, methods: resolvedMethods, oauthConfig: { ...config.auth?.oauthConfig, openOauthInPage: config.auth?.oauthConfig?.openOauthInPage }, sessionExpirationSeconds: proxyAuthConfig?.sessionExpirationSeconds, methodOrder, oauthOrder, autoRefreshSession: config.auth?.autoRefreshSession ?? true }, walletConfig: { ...config.walletConfig, features: { ...config.walletConfig?.features, auth: // If walletAuthEnabled is not set, default to true. Wallet auth can be enabled/disabled in the dashboard or by explicitly changing the walletAuthEnabled / walletConfig auth feature. resolvedMethods.walletAuthEnabled ?? config.walletConfig?.features?.auth ?? true, connecting: config.walletConfig?.features?.connecting ?? true // Default connecting to true if not set. We don't care about auth settings here. }, chains: { ...config.walletConfig?.chains, ethereum: { ...config.walletConfig?.chains?.ethereum, // keep user's value if provided; default only when undefined native: config.walletConfig?.chains?.ethereum?.native ?? true }, solana: { ...config.walletConfig?.chains?.solana, // keep user's value if provided; default only when undefined native: config.walletConfig?.chains?.solana?.native ?? true } } }, importIframeUrl: config.importIframeUrl ?? "https://import.turnkey.com", exportIframeUrl: config.exportIframeUrl ?? "https://export.turnkey.com" }; }; /** * Initializes the Turnkey client with the provided configuration. * This function sets up the client, fetches the proxy auth config if needed, * and prepares the client for use in authentication and wallet operations. * * @internal */ const initializeClient = async () => { if (!masterConfig || client || clientState == base.ClientState.Loading) return; try { setClientState(base.ClientState.Loading); const turnkeyClient = new core.TurnkeyClient({ apiBaseUrl: masterConfig.apiBaseUrl, authProxyUrl: masterConfig.authProxyUrl, authProxyConfigId: masterConfig.authProxyConfigId, organizationId: masterConfig.organizationId, // Define passkey and wallet config here. If we don't pass it into the client, Mr. Client will assume that we don't want to use passkeys/wallets and not create the stamper! passkeyConfig: { rpId: masterConfig.passkeyConfig?.rpId, timeout: masterConfig.passkeyConfig?.timeout || 60000, // 60 seconds userVerification: masterConfig.passkeyConfig?.userVerification || "preferred", allowCredentials: masterConfig.passkeyConfig?.allowCredentials || [] }, walletConfig: { features: { ...masterConfig.walletConfig?.features }, chains: { ...masterConfig.walletConfig?.chains }, ...(masterConfig.walletConfig?.walletConnect && { walletConnect: masterConfig.walletConfig.walletConnect }) } }); await turnkeyClient.init(); setClient(turnkeyClient); // Don't set clientState to ready until we fetch the proxy auth config (See other fetchProxyAuthConfig useEffect) } catch (error) { setClientState(base.ClientState.Error); if (error instanceof sdkTypes.TurnkeyError || error instanceof sdkTypes.TurnkeyNetworkError) { callbacks?.onError?.(error); } else { callbacks?.onError?.(new sdkTypes.TurnkeyError(`Failed to initialize Turnkey client`, sdkTypes.TurnkeyErrorCodes.INITIALIZE_CLIENT_ERROR, error)); } } }; /** * Initializes the user sessions by fetching all active sessions and setting up their state. * @internal */ const initializeSessions = async () => { setSession(undefined); setAllSessions(undefined); try { const allLocalStorageSessions = await getAllSessions(); if (!allLocalStorageSessions) return; await Promise.all(Object.keys(allLocalStorageSessions).map(async sessionKey => { const session = allLocalStorageSessions?.[sessionKey]; if (!utils.isValidSession(session)) { await clearSession({ sessionKey }); if (sessionKey === (await getActiveSessionKey())) { setSession(undefined); } delete allLocalStorageSessions[sessionKey]; return; } scheduleSessionExpiration({ sessionKey, expiry: session.expiry }); })); setAllSessions(allLocalStorageSessions || undefined); const activeSessionKey = await client?.getActiveSessionKey(); if (activeSessionKey) { // If we have an active session key, set if (!allLocalStorageSessions[activeSessionKey]) { return; } setSession(allLocalStorageSessions[activeSessionKey]); await refreshUser(); await refreshWallets(); return; } } catch (error) { if (error instanceof sdkTypes.TurnkeyError || error instanceof sdkTypes.TurnkeyNetworkError) { callbacks?.onError?.(error); } else { callbacks?.onError?.(new sdkTypes.TurnkeyError(`Failed to initialize sessions`, sdkTypes.TurnkeyErrorCodes.INITIALIZE_SESSION_ERROR, error)); } } }; /** * @internal * Attach listeners for connected wallet providers so we can refresh state on changes. * * - Ethereum: listens for disconnect and chain switches to trigger a refresh. * - Solana: listens for disconnect via Wallet Standard `change` events to trigger a refresh. * - WalletConnect: listens for disconnect via our custom wrapper’s `change` event to trigger a refresh. * * Notes: * - Only providers that are connected are bound. * - WalletConnect is excluded from the “native” paths and handled via its unified `change` event. * * @param walletProviders - Discovered providers; only connected ones are bound. * @param onWalletsChanged - Invoked when a relevant provider event occurs. * @returns Cleanup function that removes all listeners registered by this call. */ async function initializeWalletProviderListeners(walletProviders, onWalletsChanged) { if (walletProviders.length === 0) return () => {}; const cleanups = []; // we only want to initialize these listeners for connected walletProviders const nativeOnly = provider => provider.interfaceType !== core.WalletInterfaceType.WalletConnect; const ethProviders = masterConfig?.walletConfig?.chains.ethereum?.native ? walletProviders.filter(provider => provider.chainInfo.namespace === core.Chain.Ethereum && nativeOnly(provider) && provider.connectedAddresses.length > 0) : []; const solProviders = masterConfig?.walletConfig?.chains.solana?.native ? walletProviders.filter(provider => provider.chainInfo.namespace === core.Chain.Solana && nativeOnly(provider) && provider.connectedAddresses.length > 0) : []; // we exclude WalletConnect from the native event wiring // this is because WC is handled separately with a custom wrapper’s // `change` event const wcProviders = walletProviders.filter(p => p.interfaceType === core.WalletInterfaceType.WalletConnect && p.connectedAddresses.length > 0); function attachEthereumListeners(provider, onWalletsChanged) { if (typeof provider.on !== "function") return; const handleChainChanged = _chainId => onWalletsChanged(); const handleAccountsChanged = accounts => { if (accounts.length === 0) onWalletsChanged(); }; const handleDisconnect = () => onWalletsChanged(); provider.on("chainChanged", handleChainChanged); provider.on("accountsChanged", handleAccountsChanged); provider.on("disconnect", handleDisconnect); return () => { provider.removeListener("chainChanged", handleChainChanged); provider.removeListener("accountsChanged", handleAccountsChanged); provider.removeListener("disconnect", handleDisconnect); }; } function attachSolanaListeners(provider, onWalletsChanged) { const cleanups = []; const walletEvents = provider?.features?.["standard:events"]; if (walletEvents?.on) { const offChange = walletEvents.on("change", _evt => { onWalletsChanged(); }); cleanups.push(offChange); } return () => cleanups.forEach(fn => fn()); } ethProviders.forEach(p => { const cleanup = attachEthereumListeners(p.provider, onWalletsChanged); if (cleanup) cleanups.push(cleanup); }); solProviders.forEach(p => { const cleanup = attachSolanaListeners(p.provider, onWalletsChanged); if (cleanup) cleanups.push(cleanup); }); wcProviders.forEach(p => { const standardEvents = p.provider?.features?.["standard:events"]; if (standardEvents?.on) { const unsubscribe = standardEvents.on("change", onWalletsChanged); cleanups.push(unsubscribe); } }); return () => { cleanups.forEach(remove => remove()); }; } /** * @internal * Schedules a session expiration and warning timeout for the given session key. * * - This function sets up two timeouts: one for warning before the session expires and another to expire the session. * - The warning timeout is set to trigger before the session expires, allowing for actions like refreshing the session. * - The expiration timeout clears the session and triggers any necessary callbacks. * * @param params.sessionKey - The key of the session to schedule expiration for. * @param params.expiry - The expiration time in seconds for the session. * @throws {TurnkeyError} If an error occurs while scheduling the session expiration. */ async function scheduleSessionExpiration(params) { const { sessionKey, expiry } = params; try { // Clear any existing timeout for this session key if (expiryTimeoutsRef.current[sessionKey]) { clearTimeout(expiryTimeoutsRef.current[sessionKey]); delete expiryTimeoutsRef.current[sessionKey]; } if (expiryTimeoutsRef.current[`${sessionKey}-warning`]) { clearTimeout(expiryTimeoutsRef.current[`${sessionKey}-warning`]); delete expiryTimeoutsRef.current[`${sessionKey}-warning`]; } const timeUntilExpiry = expiry * 1000 - Date.now(); const beforeExpiry = async () => { const activeSession = await getSession(); if (!activeSession && expiryTimeoutsRef.current[sessionKey]) { clearTimeout(expiryTimeoutsRef.current[`${sessionKey}-warning`]); expiryTimeoutsRef.current[`${sessionKey}-warning`] = setTimeout(beforeExpiry, 10000); return; } const session = await getSession({ sessionKey }); if (!session) return; callbacks?.beforeSessionExpiry?.({ sessionKey }); if (masterConfig?.auth?.autoRefreshSession) { await refreshSession({ expirationSeconds: session.expirationSeconds, sessionKey }); } }; const expireSession = async () => { const expiredSession = await getSession({ sessionKey }); if (!expiredSession) return; callbacks?.onSessionExpired?.({ sessionKey }); if ((await getActiveSessionKey()) === sessionKey) { setSession(undefined); } setAllSessions(prevSessions => { if (!prevSessions) return prevSessions; const newSessions = { ...prevSessions }; delete newSessions[sessionKey]; return newSessions; }); await clearSession({ sessionKey }); delete expiryTimeoutsRef.current[sessionKey]; delete expiryTimeoutsRef.current[`${sessionKey}-warning`]; await logout(); }; if (timeUntilExpiry <= utils.SESSION_WARNING_THRESHOLD_MS) { beforeExpiry(); } else { expiryTimeoutsRef.current[`${sessionKey}-warning`] = setTimeout(beforeExpiry, timeUntilExpiry - utils.SESSION_WARNING_THRESHOLD_MS); } expiryTimeoutsRef.current[sessionKey] = setTimeout(expireSession, timeUntilExpiry); } catch (error) { if (error instanceof sdkTypes.TurnkeyError || error instanceof sdkTypes.TurnkeyNetworkError) { callbacks?.onError?.(error); } else { callbacks?.onError?.(new sdkTypes.TurnkeyError(`Failed to schedule session expiration for ${sessionKey}`, sdkTypes.TurnkeyErrorCodes.SCHEDULE_SESSION_EXPIRY_ERROR, error)); } } } /** * Clears all scheduled session expiration and warning timeouts for the client. * * - This function removes all active session expiration and warning timeouts managed by the provider. * - It is called automatically when sessions are re-initialized or on logout to prevent memory leaks and ensure no stale timeouts remain. * - All timeouts stored in `expiryTimeoutsRef` are cleared and the reference is reset. * * @throws {TurnkeyError} If an error occurs while clearing the timeouts. */ function clearSessionTimeouts() { try { Object.values(expiryTimeoutsRef.current).forEach(timeout => { clearTimeout(timeout); }); expiryTimeoutsRef.current = {}; } catch (error) { if (error instanceof sdkTypes.TurnkeyError || error instanceof sdkTypes.TurnkeyNetworkError) { callbacks?.onError?.(error); } else { callbacks?.onError?.(new sdkTypes.TurnkeyError(`Failed to clear session timeouts`, sdkTypes.TurnkeyErrorCodes.CLEAR_SESSION_TIMEOUTS_ERROR, error)); } } } /** * @internal * Handles the post-authentication flow. * * - This function is called after a successful authentication (login or sign-up) via any supported method (Passkey, Wallet, OTP, OAuth). * - It fetches the active session and all sessions, updates the session state, and schedules session expiration and warning timeouts. * - It also refreshes the user's wallets and profile information to ensure the provider state is up to date. * - This function is used internally after all authentication flows to synchronize state and trigger any necessary callbacks. * * @returns A void promise. * @throws {TurnkeyError} If the client is not initialized or if there is an error during the process. */ const handlePostAuth = async () => { try { const sessionKey = await getActiveSessionKey(); const session = await getSession({ ...(sessionKey && { sessionKey }) }); if (session && sessionKey) await scheduleSessionExpiration({ sessionKey, expiry: session.expiry }); const allSessions = await client.getAllSessions(); setSession(session); setAllSessions(allSessions); await refreshWallets(); await refreshUser(); } catch (error) { if (error instanceof sdkTypes.TurnkeyError || error instanceof sdkTypes.TurnkeyNetworkError) { callbacks?.onError?.(error); } else { callbacks?.onError?.(new sdkTypes.TurnkeyError(`Failed to handle post-authentication`, sdkTypes.TurnkeyErrorCodes.HANDLE_POST_AUTH_ERROR, error)); } } }; /** * @internal * Handles the post-logout flow. * * - This function is called after a successful logout or session clear. * - It clears all scheduled session expiration and warning timeouts to prevent memory leaks. * - It resets the session state, removes all session and user data from memory, and clears the wallets list. * - This ensures that all sensitive information is removed from the provider state after logout. * - Called internally after logout or when all sessions are cleared. * * @returns void * @throws {TurnkeyError} If there is an error during the post-logout process. */ const handlePostLogout = () => { try { clearSessionTimeouts(); setSession(undefined); setAllSessions(undefined); setUser(undefined); setWallets([]); } catch (error) { callbacks?.onError?.(new sdkTypes.TurnkeyError(`Failed to initialize sessions`, sdkTypes.TurnkeyErrorCodes.HANDLE_POST_LOGOUT_ERROR, error)); } }; const createPasskey = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } return utils.withTurnkeyErrorHandling(() => client.createPasskey({ ...params }), callbacks, "Failed to create passkey"); }, [client, callbacks]); const logout = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } await utils.withTurnkeyErrorHandling(() => client.logout(params), callbacks, "Failed to logout"); handlePostLogout(); return; }, [client, callbacks]); const loginWithPasskey = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } const expirationSeconds = masterConfig?.auth?.sessionExpirationSeconds ?? core.DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const res = await utils.withTurnkeyErrorHandling(() => client.loginWithPasskey({ ...params, expirationSeconds }), callbacks, "Failed to login with passkey"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks]); const signUpWithPasskey = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } if (!masterConfig) { throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION); } // If createSubOrgParams is not provided, use the default from masterConfig let createSubOrgParams = params?.createSubOrgParams ?? masterConfig.auth?.createSuborgParams?.passkeyAuth; params = createSubOrgParams !== undefined ? { ...params, createSubOrgParams } : { ...params }; const expirationSeconds = masterConfig?.auth?.sessionExpirationSeconds ?? core.DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const websiteName = window.location.hostname; const timestamp = new Date().toLocaleDateString() + "-" + new Date().toLocaleTimeString([], { hour: "2-digit", minute: "2-digit" }); // We allow passkeyName to be passed in thru the provider or thru the params of this function directly. // This is because signUpWithPasskey will create a new passkey using that name. // Any extra authenticators will be created after the first one. (see core implementation) const passkeyName = params?.passkeyDisplayName ?? masterConfig.auth?.createSuborgParams?.passkeyAuth?.passkeyName ?? `${websiteName}-${timestamp}`; const res = await utils.withTurnkeyErrorHandling(() => client.signUpWithPasskey({ ...params, passkeyDisplayName: passkeyName, expirationSeconds }), callbacks, "Failed to sign up with passkey"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks]); const getWalletProviders = react.useCallback(async chain => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } const newProviders = await client.getWalletProviders(chain); // we update state with the latest providers // we keep this state so that initializeWalletProviderListeners() re-runs // whenever the list of connected providers changes // this ensures we attach disconnect listeners for each connected provider setWalletProviders(newProviders); return newProviders; }, [client, callbacks]); const connectWalletAccount = react.useCallback(async walletProvider => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } await client.connectWalletAccount(walletProvider); // this will update our walletProvider state await refreshWallets(); }, [client, callbacks]); const disconnectWalletAccount = react.useCallback(async walletProvider => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } await client.disconnectWalletAccount(walletProvider); // we only refresh the wallets if: // 1. there is an active session. This is needed because for WalletConnect // you can unlink a wallet before actually being logged in // // 2. it was a WalletConnect provider that we just disconnected. Since // native providers emit a disconnect event which will already refresh // the wallets. This event is triggered in `initializeWalletProviderListeners()` if (session && walletProvider.interfaceType === core.WalletInterfaceType.WalletConnect) { // this will update our walletProvider state await refreshWallets(); } }, [client, callbacks]); const switchWalletAccountChain = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } await client.switchWalletAccountChain({ ...params, walletProviders }); }, [client, callbacks]); const loginWithWallet = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } const expirationSeconds = masterConfig?.auth?.sessionExpirationSeconds ?? core.DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const res = await utils.withTurnkeyErrorHandling(() => client.loginWithWallet({ ...params, expirationSeconds }), callbacks, "Failed to login with wallet"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks]); const signUpWithWallet = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } if (!masterConfig) { throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION); } // If createSubOrgParams is not provided, use the default from masterConfig let createSubOrgParams = params.createSubOrgParams ?? masterConfig.auth?.createSuborgParams?.walletAuth; params = createSubOrgParams !== undefined ? { ...params, createSubOrgParams } : { ...params }; const expirationSeconds = masterConfig?.auth?.sessionExpirationSeconds ?? core.DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const res = await utils.withTurnkeyErrorHandling(() => client.signUpWithWallet({ ...params, expirationSeconds }), callbacks, "Failed to sign up with wallet"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks, masterConfig]); const loginOrSignupWithWallet = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } if (!masterConfig) { throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION); } // If createSubOrgParams is not provided, use the default from masterConfig let createSubOrgParams = params.createSubOrgParams ?? masterConfig.auth?.createSuborgParams?.walletAuth; params = createSubOrgParams !== undefined ? { ...params, createSubOrgParams } : { ...params }; const expirationSeconds = masterConfig?.auth?.sessionExpirationSeconds ?? core.DEFAULT_SESSION_EXPIRATION_IN_SECONDS; const res = await utils.withTurnkeyErrorHandling(() => client.loginOrSignupWithWallet({ ...params, expirationSeconds }), callbacks, "Failed to login or sign up with wallet"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks, masterConfig]); const initOtp = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } return utils.withTurnkeyErrorHandling(() => client.initOtp(params), callbacks, "Failed to initialize OTP"); }, [client, callbacks]); const verifyOtp = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } return utils.withTurnkeyErrorHandling(() => client.verifyOtp(params), callbacks, "Failed to verify OTP"); }, [client, callbacks]); const loginWithOtp = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } const res = await utils.withTurnkeyErrorHandling(() => client.loginWithOtp(params), callbacks, "Failed to login with OTP"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks]); const signUpWithOtp = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } if (!masterConfig) { throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION); } // If createSubOrgParams is not provided, use the default from masterConfig let createSubOrgParams = params.createSubOrgParams; if (!createSubOrgParams && masterConfig?.auth?.createSuborgParams) { if (params.otpType === core.OtpType.Email) { createSubOrgParams = masterConfig.auth.createSuborgParams.emailOtpAuth; } else if (params.otpType === core.OtpType.Sms) { createSubOrgParams = masterConfig.auth.createSuborgParams.smsOtpAuth; } } params = createSubOrgParams !== undefined ? { ...params, createSubOrgParams } : { ...params }; const res = await utils.withTurnkeyErrorHandling(() => client.signUpWithOtp(params), callbacks, "Failed to sign up with OTP"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks, masterConfig]); const completeOtp = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } if (!masterConfig) { throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION); } // If createSubOrgParams is not provided, use the default from masterConfig let createSubOrgParams = params.createSubOrgParams; if (!createSubOrgParams && masterConfig?.auth?.createSuborgParams) { if (params.otpType === core.OtpType.Email) { createSubOrgParams = masterConfig.auth.createSuborgParams.emailOtpAuth; } else if (params.otpType === core.OtpType.Sms) { createSubOrgParams = masterConfig.auth.createSuborgParams.smsOtpAuth; } } params = createSubOrgParams !== undefined ? { ...params, createSubOrgParams } : { ...params }; const res = await utils.withTurnkeyErrorHandling(() => client.completeOtp(params), callbacks, "Failed to complete OTP"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks, masterConfig]); const completeOauth = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } if (!masterConfig) { throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION); } // If createSubOrgParams is not provided, use the default from masterConfig const createSubOrgParams = params.createSubOrgParams ?? masterConfig.auth?.createSuborgParams?.oauth; params = createSubOrgParams !== undefined ? { ...params, createSubOrgParams } : { ...params }; const res = await utils.withTurnkeyErrorHandling(() => client.completeOauth(params), callbacks, "Failed to complete OAuth"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks, masterConfig]); const loginWithOauth = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } const res = await utils.withTurnkeyErrorHandling(() => client.loginWithOauth(params), callbacks, "Failed to login with OAuth"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks]); const signUpWithOauth = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } if (!masterConfig) { throw new sdkTypes.TurnkeyError("Config is not ready yet!", sdkTypes.TurnkeyErrorCodes.INVALID_CONFIGURATION); } // If createSubOrgParams is not provided, use the default from masterConfig let createSubOrgParams = params.createSubOrgParams ?? masterConfig.auth?.createSuborgParams?.oauth; params = createSubOrgParams !== undefined ? { ...params, createSubOrgParams } : { ...params }; const res = await utils.withTurnkeyErrorHandling(() => client.signUpWithOauth(params), callbacks, "Failed to sign up with OAuth"); if (res) { await handlePostAuth(); } return res; }, [client, callbacks, masterConfig]); const fetchWallets = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } return utils.withTurnkeyErrorHandling(() => client.fetchWallets(params), callbacks, "Failed to fetch wallets"); }, [client, callbacks]); const fetchWalletAccounts = react.useCallback(async params => { if (!client) { throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); } return utils.withTurnkeyErrorHandling(() => client.fetchWalletAccounts(params), callbacks, "Failed to fetch wallet accounts"); }, [client, callbacks]); const signMessage = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); return utils.withTurnkeyErrorHandling(() => client.signMessage(params), callbacks, "Failed to sign message"); }, [client, callbacks]); const handleSignMessage = react.useCallback(async params => { const { successPageDuration = 2000 } = params; if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); return new Promise((resolve, reject) => { pushPage({ key: "Sign Message", content: jsxRuntime.jsx(Message.SignMessageModal, { message: params.message, subText: params?.subText, walletAccount: params.walletAccount, stampWith: params.stampWith, successPageDuration: successPageDuration, onSuccess: result => { resolve(result); }, onError: error => { reject(error); }, ...(params?.encoding && { encoding: params.encoding }), ...(params?.hashFunction && { hashFunction: params.hashFunction }), ...(params?.addEthereumPrefix && { addEthereumPrefix: params.addEthereumPrefix }) }) }); }); }, [client, callbacks]); const signTransaction = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); return utils.withTurnkeyErrorHandling(() => client.signTransaction(params), callbacks, "Failed to sign transaction"); }, [client, callbacks]); const signAndSendTransaction = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); return utils.withTurnkeyErrorHandling(() => client.signAndSendTransaction(params), callbacks, "Failed to sign transaction"); }, [client, callbacks]); const fetchUser = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); return utils.withTurnkeyErrorHandling(() => client.fetchUser(params), callbacks, "Failed to fetch user"); }, [client, callbacks]); const updateUserEmail = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.updateUserEmail(params), callbacks, "Failed to update user email"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const removeUserEmail = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.removeUserEmail(params), callbacks, "Failed to remove user email"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const updateUserPhoneNumber = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.updateUserPhoneNumber(params), callbacks, "Failed to update user phone number"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const removeUserPhoneNumber = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.removeUserPhoneNumber(params), callbacks, "Failed to remove user phone number"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const updateUserName = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.updateUserName(params), callbacks, "Failed to update user name"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const addOauthProvider = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.addOauthProvider(params), callbacks, "Failed to add OAuth provider"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const removeOauthProviders = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.removeOauthProviders(params), callbacks, "Failed to remove OAuth providers"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const addPasskey = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.addPasskey(params), callbacks, "Failed to add passkey"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const removePasskeys = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.removePasskeys(params), callbacks, "Failed to remove passkeys"); if (res) await refreshUser({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const createWallet = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.createWallet(params), callbacks, "Failed to create wallet"); if (res) await refreshWallets({ stampWith: params?.stampWith }); return res; }, [client, callbacks]); const createWalletAccounts = react.useCallback(async params => { if (!client) throw new sdkTypes.TurnkeyError("Client is not initialized.", sdkTypes.TurnkeyErrorCodes.CLIENT_NOT_INITIALIZED); const res = await utils.withTurnkeyErrorHandling(() => client.createWalletAccounts(params), callbacks, "Failed to create wal