@turnkey/sdk-react-native
Version:
React Native SDK
702 lines (698 loc) • 35.2 kB
JavaScript
'use strict';
var jsxRuntime = require('react/jsx-runtime');
var react = require('react');
var crypto = require('@turnkey/crypto');
var encoding = require('@turnkey/encoding');
var errors = require('../errors.js');
var storage = require('../storage.js');
var turnkeyHelpers = require('../turnkey-helpers.js');
var constants = require('../constants.js');
var reactNativeInappbrowserReborn = require('react-native-inappbrowser-reborn');
const TurnkeyContext = react.createContext(undefined);
const TurnkeyProvider = ({ children, config }) => {
const [session, setSession] = react.useState(undefined);
const [client, setClient] = react.useState(undefined);
// map to track expiration timers for each session by session key
const expiryTimeoutsRef = react.useRef({});
/**
* Effect hook that initializes stored sessions on mount.
*
* This hook runs once when the component mounts. It retrieves all stored session keys,
* validates their expiration status, removes expired sessions, and schedules expiration
* timers for active ones. Additionally, it loads the last selected session if it is still valid,
* otherwise it clears the session and triggers the session expiration callback.
*
* When the component unmounts, it clears all scheduled expiration timeouts.
*/
react.useEffect(() => {
const initializeSessions = async () => {
const sessionKeys = await storage.getSessionKeys();
await Promise.all(sessionKeys.map(async (sessionKey) => {
const session = await storage.getSession(sessionKey);
// if session is invalid, we remove it
if (!turnkeyHelpers.isValidSession(session)) {
await clearSession({ sessionKey });
await storage.removeSessionKey(sessionKey);
return;
}
scheduleSessionExpiration(sessionKey, session.expiry);
}));
// load the selected session if it's still valid
const selectedSessionKey = await storage.getSelectedSessionKey();
if (selectedSessionKey) {
const selectedSession = await storage.getSession(selectedSessionKey);
if (turnkeyHelpers.isValidSession(selectedSession)) {
const clientInstance = turnkeyHelpers.createClient(selectedSession.publicKey, selectedSession.privateKey, config.apiBaseUrl);
setSession(selectedSession);
setClient(clientInstance);
config.onSessionSelected?.(selectedSession);
}
else {
await clearSession({ sessionKey: selectedSessionKey });
config.onSessionExpired?.(selectedSession ?? { key: selectedSessionKey });
}
}
else {
config.onSessionEmpty?.();
}
};
initializeSessions();
config.onInitialized?.();
return () => {
clearTimeouts();
};
}, []);
/**
* Clears all scheduled session expiration timeouts.
*
* - Iterates over the currently tracked expiration timers and clears each one.
* - Resets the `expiryTimeoutsRef` object to an empty state.
*/
const clearTimeouts = () => {
Object.values(expiryTimeoutsRef.current).forEach((timer) => clearTimeout(timer));
expiryTimeoutsRef.current = {};
};
/**
* Schedules the expiration and pre-expiration warning of a session.
*
* - Clears any existing expiration and warning timeouts for the session.
* - Computes the remaining time until the session expires.
* - If the session is already expired, triggers expiration immediately.
* - If the remaining time is less than or equal to the warning threshold (30 seconds),
* triggers the warning callback immediately; otherwise, schedules the warning to fire 30 seconds before expiration.
* - Always schedules a timeout to expire the session at the appropriate time.
* - Upon expiration, invokes clearSession and the onSessionExpired callback.
*
* @param sessionKey - The key identifying the session for which to schedule expiration.
* @param expiryTime - The expiration timestamp (in milliseconds) of the session.
*/
const scheduleSessionExpiration = async (sessionKey, expiryTime) => {
// clear existing timeout if it exists
if (expiryTimeoutsRef.current[sessionKey]) {
clearTimeout(expiryTimeoutsRef.current[sessionKey]);
}
// clear existing warning timeouts if it exists
if (expiryTimeoutsRef.current[`${sessionKey}-warning`]) {
clearTimeout(expiryTimeoutsRef.current[`${sessionKey}-warning`]);
}
const timeUntilExpiry = expiryTime - Date.now();
const warningThreshold = constants.SESSION_WARNING_THRESHOLD_SECONDS * 1000;
const warnBeforeExpiry = async () => {
const session = await storage.getSession(sessionKey);
if (!session)
return;
config.onSessionExpiryWarning?.(session);
delete expiryTimeoutsRef.current[`${sessionKey}-warning`];
};
const expireSession = async () => {
const expiredSession = await storage.getSession(sessionKey);
if (!expiredSession)
return;
await clearSession({ sessionKey });
config.onSessionExpired?.(expiredSession);
delete expiryTimeoutsRef.current[sessionKey];
delete expiryTimeoutsRef.current[`${sessionKey}-warning`];
};
if (timeUntilExpiry <= 0) {
await expireSession();
return;
}
// if it is less than warning threshold, we warn immediately
if (timeUntilExpiry <= warningThreshold) {
warnBeforeExpiry();
}
else {
// schedule warning
expiryTimeoutsRef.current[`${sessionKey}-warning`] = setTimeout(warnBeforeExpiry, timeUntilExpiry - warningThreshold);
}
// schedule expiration
expiryTimeoutsRef.current[sessionKey] = setTimeout(expireSession, timeUntilExpiry);
};
/**
* Sets the selected session and updates the client instance.
*
* - Retrieves the session associated with the given `sessionKey`.
* - If the session is valid, initializes a new `TurnkeyClient` and updates the state.
* - Saves the selected session key and triggers `onSessionSelected` if provided.
* - If the session is expired or invalid, clears the session and triggers `onSessionExpired`.
*
* @param sessionKey - The key of the session to set as selected.
* @returns The selected session if valid, otherwise `undefined`.
*/
const setSelectedSession = react.useCallback(async ({ sessionKey }) => {
const session = await storage.getSession(sessionKey);
if (turnkeyHelpers.isValidSession(session)) {
const clientInstance = turnkeyHelpers.createClient(session.publicKey, session.privateKey, config.apiBaseUrl);
setClient(clientInstance);
setSession(session);
await storage.saveSelectedSessionKey(sessionKey);
config.onSessionSelected?.(session);
return session;
}
else {
await clearSession({ sessionKey });
config.onSessionExpired?.(session ?? { key: sessionKey });
return undefined;
}
}, [turnkeyHelpers.createClient, config]);
/**
* Refreshes the current user data.
*
* - Fetches the latest user details from the API using the current session's client.
* - If the user data is successfully retrieved, updates the session with the new user details.
* - Saves the updated session and updates the state.
*
* @throws If the session or client is not initialized.
*/
const refreshUser = react.useCallback(async () => {
if (session && client) {
const updatedUser = await turnkeyHelpers.fetchUser(client, config.organizationId);
if (updatedUser) {
const updatedSession = { ...session, user: updatedUser };
await storage.saveSession(updatedSession, updatedSession.key);
setSession(updatedSession);
}
}
}, [session, client, config.organizationId]);
/**
* Updates the current user's information.
*
* - Sends a request to update the user's email and/or phone number.
* - If the update is successful, refreshes the user data to reflect changes.
*
* @param email - (Optional) The new email address.
* @param phone - (Optional) The new phone number.
* @returns The update user activity result.
* @throws If the client or session is not initialized.
*/
const updateUser = react.useCallback(async ({ email, phone }) => {
if (client == null || session?.user == null) {
throw new errors.TurnkeyReactNativeError("Client or user not initialized");
}
const parameters = {
userId: session.user.id,
userTagIds: [],
...(phone?.trim() && { userPhoneNumber: phone }),
...(email?.trim() && { userEmail: email }),
};
const result = await client.updateUser({
type: "ACTIVITY_TYPE_UPDATE_USER",
timestampMs: Date.now().toString(),
organizationId: session.user.organizationId,
parameters,
});
const activity = result.activity;
if (activity.result.updateUserResult?.userId) {
await refreshUser();
}
return activity;
}, [client, session, refreshUser]);
/**
* Generates a new embedded key pair and securely stores the private key in secure storage.
*
* - By default, returns the uncompressed public key.
* - If `isCompressed` is set to `true`, returns the compressed public key instead.
*
* @param storageKey - (Optional) The storage key under which to store the private key (defaults to `@turnkey/embedded-key`).
* @param isCompressed - (Optional) Whether to return the compressed public key (defaults to `false`).
* @returns The public key (compressed or uncompressed) corresponding to the generated embedded key pair.
* @throws {TurnkeyReactNativeError} If saving the private key fails.
*/
const createEmbeddedKey = react.useCallback(async ({ storageKey = constants.StorageKeys.EmbeddedKey, isCompressed = false, } = {}) => {
const key = crypto.generateP256KeyPair();
const embeddedPrivateKey = key.privateKey;
const publicKey = key.publicKey;
const publicKeyUncompressed = key.publicKeyUncompressed;
await storage.saveEmbeddedKey(embeddedPrivateKey, storageKey);
return isCompressed ? publicKey : publicKeyUncompressed;
}, []);
/**
* Creates a new session and securely stores it.
*
* - Enforces a maximum limit of `MAX_SESSIONS` to prevent excessive resource usage.
* - Retrieves the embedded private key from secure storage.
* - Decrypts the provided session bundle using the embedded key.
* - Extracts the public key from the decrypted private key.
* - Creates a new Turnkey API client using the derived credentials.
* - Fetches user information associated with the session.
* - Constructs and saves the session in secure storage.
* - Schedules session expiration handling.
* - If this is the first session created, it is automatically set as the selected session.
* - Calls `onSessionCreated` callback if provided.
*
* @param bundle - The encrypted credential bundle.
* @param expirationSeconds - (Optional) The expiration time in seconds (defaults to `OTP_AUTH_DEFAULT_EXPIRATION_SECONDS`).
* @param embeddedStorageKey - (Optional) The service key where the embedded key is stored (defaults to `@turnkey/embedded-key`).
* @param sessionKey - (Optional) The session identifier (defaults to `TURNKEY_DEFAULT_SESSION_STORAGE`).
* @returns The created session.
* @throws {TurnkeyReactNativeError} If the embedded key or user data cannot be retrieved,
* if the sessionKey already exists, or if the maximum session limit is reached.
*/
const createSession = react.useCallback(async ({ bundle, expirationSeconds = constants.OTP_AUTH_DEFAULT_EXPIRATION_SECONDS, embeddedStorageKey = constants.StorageKeys.EmbeddedKey, sessionKey = constants.StorageKeys.DefaultSession, }) => {
// we throw an error if a session with this sessionKey already exists
const existingSessionKeys = await storage.getSessionKeys();
if (existingSessionKeys.length >= constants.MAX_SESSIONS) {
throw new errors.TurnkeyReactNativeError(`Maximum session limit of ${constants.MAX_SESSIONS} reached. Please clear an existing session before creating a new one.`);
}
if (existingSessionKeys.includes(sessionKey)) {
throw new errors.TurnkeyReactNativeError(`session key "${sessionKey}" already exists. Please choose a unique session key or clear the existing session.`);
}
const embeddedKey = await storage.getEmbeddedKey(true, embeddedStorageKey);
if (!embeddedKey) {
throw new errors.TurnkeyReactNativeError("Embedded key not found.");
}
const privateKey = crypto.decryptCredentialBundle(bundle, embeddedKey);
const publicKey = encoding.uint8ArrayToHexString(crypto.getPublicKey(privateKey));
const expiry = Date.now() + expirationSeconds * 1000;
const clientInstance = turnkeyHelpers.createClient(publicKey, privateKey, config.apiBaseUrl);
const user = await turnkeyHelpers.fetchUser(clientInstance, config.organizationId);
if (!user) {
throw new errors.TurnkeyReactNativeError("User not found.");
}
const newSession = {
key: sessionKey,
publicKey,
privateKey,
expiry,
user,
};
await storage.saveSession(newSession, sessionKey);
await storage.addSessionKey(sessionKey);
scheduleSessionExpiration(sessionKey, newSession.expiry);
// if this is the first session created, set it as the selected session
const isFirstSession = existingSessionKeys.length === 0;
if (isFirstSession) {
await setSelectedSession({ sessionKey });
}
config.onSessionCreated?.(newSession);
return newSession;
}, [config, setSelectedSession]);
/**
* Creates a new session using an embedded private key and securely stores it.
*
* - Enforces a maximum limit of `MAX_SESSIONS` to prevent excessive resource usage.
* - Retrieves the embedded private key from secure storage or uses the provided one.
* - Extracts the public key from the private key.
* - Creates a new Turnkey API client using the derived credentials.
* - Fetches user information associated with the session.
* - Constructs and saves the session in secure storage.
* - Schedules session expiration handling.
* - If this is the first session created, it is automatically set as the selected session.
* - Calls `onSessionCreated` callback if provided.
*
* @param subOrganizationId - The sub-organization ID used to fetch user information.
* @param embeddedKey - (Optional) A private key to use instead of fetching from secure storage.
* @param embeddedStorageKey - (Optional) The storage key where the embedded key is stored. Only used if `embeddedKey` is not provided (defaults to `@turnkey/embedded-key`).
* @param expirationSeconds - (Optional) The expiration time in seconds (defaults to `OTP_AUTH_DEFAULT_EXPIRATION_SECONDS`).
* @param sessionKey - (Optional) The session identifier (defaults to `TURNKEY_DEFAULT_SESSION_STORAGE`).
* @returns The created session.
* @throws {TurnkeyReactNativeError} If the embedded key or user data cannot be retrieved,
* if the sessionKey already exists, or if the maximum session limit is reached.
*/
const createSessionFromEmbeddedKey = react.useCallback(async ({ subOrganizationId, embeddedKey, expirationSeconds = constants.OTP_AUTH_DEFAULT_EXPIRATION_SECONDS, embeddedStorageKey = constants.StorageKeys.EmbeddedKey, sessionKey = constants.StorageKeys.DefaultSession, }) => {
// we throw an error if a session with this sessionKey already exists
const existingSessionKeys = await storage.getSessionKeys();
if (existingSessionKeys.length >= constants.MAX_SESSIONS) {
throw new errors.TurnkeyReactNativeError(`Maximum session limit of ${constants.MAX_SESSIONS} reached. Please clear an existing session before creating a new one.`);
}
if (existingSessionKeys.includes(sessionKey)) {
throw new errors.TurnkeyReactNativeError(`session key "${sessionKey}" already exists. Please choose a unique session key or clear the existing session.`);
}
const privateKey = embeddedKey ?? (await storage.getEmbeddedKey(true, embeddedStorageKey));
if (!privateKey) {
throw new errors.TurnkeyReactNativeError("Embedded key not found.");
}
const publicKey = encoding.uint8ArrayToHexString(crypto.getPublicKey(privateKey));
const expiry = Date.now() + expirationSeconds * 1000;
const clientInstance = turnkeyHelpers.createClient(publicKey, privateKey, config.apiBaseUrl);
const user = await turnkeyHelpers.fetchUser(clientInstance, subOrganizationId);
if (!user) {
throw new errors.TurnkeyReactNativeError("User not found.");
}
const newSession = {
key: sessionKey,
publicKey,
privateKey,
expiry,
user,
};
await storage.saveSession(newSession, sessionKey);
await storage.addSessionKey(sessionKey);
scheduleSessionExpiration(sessionKey, newSession.expiry);
// if this is the first session created, set it as the selected session
const isFirstSession = existingSessionKeys.length === 0;
if (isFirstSession) {
await setSelectedSession({ sessionKey });
}
config.onSessionCreated?.(newSession);
return newSession;
}, [config, setSelectedSession]);
/**
* Refreshes an existing session by creating a new read/write session.
*
* This function refreshes an existing session by:
* - Retrieving the session using the provided or selected session key.
* - Verifying that the session is still valid.
* - Generating a new embedded key for refreshing.
* - Creating a new read/write session via the current session client.
* - Decrypting the returned credential bundle to derive new keys and expiry.
* - Fetching updated user information using the new credentials.
* - Updating local state (if this is the currently selected session),
* saving the refreshed session, and scheduling its expiration.
*
* @param expirationSeconds - (Optional) The expiration time in seconds for the new session. Defaults to OTP_AUTH_DEFAULT_EXPIRATION_SECONDS.
* @param sessionKey - (Optional) The session key to refresh; if not provided, the currently selected session key is used.
* @returns The refreshed Session.
* @throws {TurnkeyReactNativeError} If the session is not found, already expired, or any step in the refresh fails.
*/
const refreshSession = react.useCallback(async ({ expirationSeconds = constants.OTP_AUTH_DEFAULT_EXPIRATION_SECONDS, sessionKey, } = {}) => {
const keyToRefresh = sessionKey ?? (await storage.getSelectedSessionKey());
if (!keyToRefresh) {
throw new errors.TurnkeyReactNativeError("Session not found when refreshing the session. Either the provided sessionKey is invalid, or no session is currently selected.");
}
const sessionToRefresh = await storage.getSession(keyToRefresh);
if (!turnkeyHelpers.isValidSession(sessionToRefresh)) {
throw new errors.TurnkeyReactNativeError(`You cannot refresh session with key "${keyToRefresh}" because it is already expired.`);
}
const { publicKeyUncompressed: targetPublicKey, privateKey: embeddedKey, } = crypto.generateP256KeyPair();
const currentClient = turnkeyHelpers.createClient(sessionToRefresh.publicKey, sessionToRefresh.privateKey, config.apiBaseUrl);
const sessionResponse = await currentClient.createReadWriteSession({
type: "ACTIVITY_TYPE_CREATE_READ_WRITE_SESSION_V2",
timestampMs: Date.now().toString(),
organizationId: config.organizationId,
parameters: {
targetPublicKey,
expirationSeconds: expirationSeconds.toString(),
},
});
const bundle = sessionResponse.activity.result.createReadWriteSessionResultV2
?.credentialBundle;
if (!bundle) {
throw new errors.TurnkeyReactNativeError("Failed to create read/write session when refreshing the session");
}
const newPrivateKey = crypto.decryptCredentialBundle(bundle, embeddedKey);
const newPublicKey = encoding.uint8ArrayToHexString(crypto.getPublicKey(newPrivateKey));
const newExpiry = Date.now() + expirationSeconds * 1000;
const newClient = turnkeyHelpers.createClient(newPublicKey, newPrivateKey, config.apiBaseUrl);
const user = await turnkeyHelpers.fetchUser(newClient, config.organizationId);
if (!user) {
throw new errors.TurnkeyReactNativeError("User not found when refreshing the session");
}
const newSession = {
key: keyToRefresh,
publicKey: newPublicKey,
privateKey: newPrivateKey,
expiry: newExpiry,
user,
};
// if selected session is being refreshed, we update the local state session and client
if (session?.key === keyToRefresh) {
setSession(newSession);
setClient(newClient);
}
await storage.saveSession(newSession, keyToRefresh);
scheduleSessionExpiration(keyToRefresh, newSession.expiry);
return newSession;
}, [config, session]);
/**
* Removes a session from secure storage.
*
* - If `sessionKey` is provided, removes the specified session.
* - If no `sessionKey` is provided, removes the currently selected session.
* - If the session being removed is the currently selected session, resets the state.
* - Deletes the session from secure storage and updates the session index.
* - Calls `onSessionCleared` callback if provided.
*
* @param sessionKey - The key of the session to clear. Defaults to the currently selected session.
* @throws {TurnkeyReactNativeError} If the provided `sessionKey` does not exist or no session is currently selected.
*/
const clearSession = react.useCallback(async ({ sessionKey, } = {}) => {
const keyToClear = sessionKey ?? (await storage.getSelectedSessionKey());
if (!keyToClear) {
throw new errors.TurnkeyReactNativeError("Session not found. Either the provided sessionKey is invalid, or no session is currently selected.");
}
const clearedSession = await storage.getSession(keyToClear);
// if selected session is being cleared, clear the local state session and client
if (session?.key === keyToClear) {
setSession(undefined);
setClient(undefined);
await storage.clearSelectedSessionKey();
}
await storage.deleteSession(keyToClear);
await storage.removeSessionKey(keyToClear);
clearTimeout(expiryTimeoutsRef.current[keyToClear]);
delete expiryTimeoutsRef.current[keyToClear];
config.onSessionCleared?.(clearedSession ?? { key: keyToClear });
}, [session, config]);
/**
* Clears all sessions from secure storage.
*
* - Retrieves all stored session keys.
* - For each session key, deletes the associated session and removes the key from the session index.
* - Clears the selected session key from secure storage.
* - Resets local state (active session and client).
* - Clears all scheduled expiration timers.
*
* @throws {TurnkeyReactNativeError} If any error occurs during the clearing process.
*/
const clearAllSessions = react.useCallback(async () => {
try {
const sessionKeys = await storage.getSessionKeys();
for (const key of sessionKeys) {
await storage.deleteSession(key);
await storage.removeSessionKey(key);
}
await storage.clearSelectedSessionKey();
setSession(undefined);
setClient(undefined);
clearTimeouts();
}
catch (error) {
throw new errors.TurnkeyReactNativeError("Failed to clear all sessions", error);
}
}, [clearTimeouts]);
/**
*
* Creates a new wallet with the specified name and accounts.
*
* @param walletName - The name of the wallet.
* @param accounts - The list of accounts associated with the wallet.
* @param mnemonicLength - (Optional) The length of the mnemonic phrase (defaults to 12).
* @returns The activity response from the wallet creation.
* @throws If the client or user is not initialized.
*/
const createWallet = react.useCallback(async ({ walletName, accounts, mnemonicLength, }) => {
if (client == null || session?.user == null) {
throw new errors.TurnkeyReactNativeError("Client or user not initialized");
}
const parameters = { walletName, accounts };
if (mnemonicLength != null) {
parameters.mnemonicLength = mnemonicLength;
}
const response = await client.createWallet({
type: "ACTIVITY_TYPE_CREATE_WALLET",
timestampMs: Date.now().toString(),
organizationId: session.user.organizationId,
parameters,
});
const activity = response.activity;
if (activity.result.createWalletResult?.walletId) {
await refreshUser();
}
return activity;
}, [client, session, refreshUser]);
/**
*
* Imports a wallet using a provided mnemonic and creates accounts.
*
* @param walletName - The name of the wallet.
* @param mnemonic - The mnemonic phrase used to restore the wallet.
* @param accounts - The list of accounts associated with the wallet.
* @returns The activity response from the wallet import.
* @throws If the client or user is not initialized.
*/
const importWallet = react.useCallback(async ({ walletName, mnemonic, accounts, }) => {
if (client == null || session?.user == null) {
throw new errors.TurnkeyReactNativeError("Client or user not initialized");
}
const initResponse = await client.initImportWallet({
type: "ACTIVITY_TYPE_INIT_IMPORT_WALLET",
timestampMs: Date.now().toString(),
organizationId: session.user.organizationId,
parameters: { userId: session.user.id },
});
const importBundle = initResponse.activity.result.initImportWalletResult?.importBundle;
if (importBundle == null) {
throw new errors.TurnkeyReactNativeError("Failed to get import bundle");
}
const encryptedBundle = await crypto.encryptWalletToBundle({
mnemonic,
importBundle,
userId: session.user.id,
organizationId: session.user.organizationId,
});
const response = await client.importWallet({
type: "ACTIVITY_TYPE_IMPORT_WALLET",
timestampMs: Date.now().toString(),
organizationId: session.user.organizationId,
parameters: {
userId: session.user.id,
walletName,
encryptedBundle,
accounts,
},
});
const activity = response.activity;
if (activity.result.importWalletResult?.walletId) {
await refreshUser();
}
return activity;
}, [client, session, refreshUser]);
/**
*
* Exports an existing wallet by decrypting the stored mnemonic phrase.
*
* @param walletId - The unique identifier of the wallet to be exported.
* @returns The decrypted mnemonic phrase of the wallet.
* @throws If the client, user, or export bundle is not initialized.
*/
const exportWallet = react.useCallback(async ({ walletId }) => {
const { publicKeyUncompressed: targetPublicKey, privateKey: embeddedKey, } = crypto.generateP256KeyPair();
if (client == null || session?.user == null) {
throw new errors.TurnkeyReactNativeError("Client or user not initialized");
}
const response = await client.exportWallet({
type: "ACTIVITY_TYPE_EXPORT_WALLET",
timestampMs: Date.now().toString(),
organizationId: session.user.organizationId,
parameters: { walletId, targetPublicKey },
});
const exportBundle = response.activity.result.exportWalletResult?.exportBundle;
if (exportBundle == null || embeddedKey == null) {
throw new errors.TurnkeyReactNativeError("Export bundle or embedded key not initialized");
}
return await crypto.decryptExportBundle({
exportBundle,
embeddedKey,
organizationId: session.user.organizationId,
returnMnemonic: true,
});
}, [client, session]);
/**
*
* Signs a raw payload using the specified signing key and encoding parameters.
*
* @param signWith - The identifier of the signing key.
* @param payload - The raw payload to be signed.
* @param encoding - The encoding format of the payload.
* @param hashFunction - The hash function to be used before signing.
* @returns The result of the signing operation.
* @throws If the client or user is not initialized.
*/
const signRawPayload = react.useCallback(async ({ signWith, payload, encoding, hashFunction, }) => {
if (client == null || session?.user == null) {
throw new errors.TurnkeyReactNativeError("Client or user not initialized");
}
const response = await client.signRawPayload({
type: "ACTIVITY_TYPE_SIGN_RAW_PAYLOAD_V2",
timestampMs: Date.now().toString(),
organizationId: session.user.organizationId,
parameters: { signWith, payload, encoding, hashFunction },
});
const signRawPayloadResult = response.activity.result.signRawPayloadResult;
if (signRawPayloadResult == null) {
throw new errors.TurnkeyReactNativeError("Failed to sign raw payload");
}
return signRawPayloadResult;
}, [client, session]);
/**
* Handles the Google OAuth authentication flow.
*
* Initiates an InAppBrowser OAuth flow with the provided credentials and parameters.
* After the OAuth flow completes successfully, it extracts the oidcToken from the callback URL
* and invokes the provided onSuccess callback.
*
* @param clientId The client ID for Google OAuth.
* @param nonce A random nonce for the OAuth flow.
* @param scheme The app’s custom URL scheme (e.g., `"myapp"`).
* @param originUri (Optional) The base URI to start the OAuth flow. Defaults to `TURNKEY_OAUTH_ORIGIN_URL`.
* @param redirectUri (Optional) The redirect URI for the OAuth flow. Defaults to `TURNKEY_OAUTH_REDIRECT_URL?scheme={scheme}`.
* @param onSuccess Callback function that receives the oidcToken upon successful OAuth authentication.
* @returns A promise that resolves once the OAuth flow completes.
* @throws {TurnkeyReactNativeError} If InAppBrowser is unavailable, the OAuth flow fails, or the oidcToken is missing.
*/
const handleGoogleOAuth = react.useCallback(async ({ clientId, nonce, scheme, originUri = constants.TURNKEY_OAUTH_ORIGIN_URL, redirectUri, onSuccess, }) => {
if (!(await reactNativeInappbrowserReborn.InAppBrowser.isAvailable())) {
throw new errors.TurnkeyReactNativeError("InAppBrowser is not available");
}
const finalRedirectUri = redirectUri
? redirectUri
: `${constants.TURNKEY__OAUTH_REDIRECT_URL}?scheme=${encodeURIComponent(scheme)}`;
const oauthUrl = originUri +
`?provider=google` +
`&clientId=${encodeURIComponent(clientId)}` +
`&redirectUri=${encodeURIComponent(finalRedirectUri)}` +
`&nonce=${encodeURIComponent(nonce)}`;
const result = await reactNativeInappbrowserReborn.InAppBrowser.openAuth(oauthUrl, scheme, {
dismissButtonStyle: "cancel",
animated: true,
modalPresentationStyle: "fullScreen",
modalTransitionStyle: "coverVertical",
modalEnabled: true,
enableBarCollapsing: false,
showTitle: true,
enableUrlBarHiding: true,
enableDefaultShare: true,
});
if (!result || result.type !== "success" || !result.url) {
throw new errors.TurnkeyReactNativeError("OAuth flow did not complete successfully");
}
const resultUrl = new URL(result.url);
const oidcToken = resultUrl.searchParams.get("id_token");
if (!oidcToken) {
throw new errors.TurnkeyReactNativeError("oidcToken not found in the response");
}
onSuccess(oidcToken);
}, []);
const providerValue = react.useMemo(() => ({
session,
client,
user: session?.user,
setSelectedSession,
refreshUser,
updateUser,
createEmbeddedKey,
createSession,
createSessionFromEmbeddedKey,
refreshSession,
clearSession,
clearAllSessions,
createWallet,
importWallet,
exportWallet,
signRawPayload,
handleGoogleOAuth,
}), [
session,
client,
session?.user,
setSelectedSession,
refreshUser,
updateUser,
createEmbeddedKey,
createSession,
createSessionFromEmbeddedKey,
refreshSession,
clearSession,
clearAllSessions,
createWallet,
importWallet,
exportWallet,
signRawPayload,
handleGoogleOAuth,
]);
return (jsxRuntime.jsx(TurnkeyContext.Provider, { value: providerValue, children: children }));
};
exports.TurnkeyContext = TurnkeyContext;
exports.TurnkeyProvider = TurnkeyProvider;
//# sourceMappingURL=TurnkeyContext.js.map