@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
JavaScript
'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