UNPKG

accounts

Version:

Tempo Accounts SDK

223 lines 7.28 kB
import { useLogin, usePrivy, useWallets } from '@privy-io/react-auth'; import { useEffect, useRef, useSyncExternalStore } from 'react'; import { privy as core_privy } from '../core/adapters/privy.js'; /** * Creates a Privy adapter wired to `@privy-io/react-auth`. * * Mount {@link PrivyAccountsBridge} under Privy's `PrivyProvider` in the same * React tree. The bridge owns Privy's login modal, hook state, and embedded * wallet selection for this adapter. */ export function privy(options = {}) { return core_privy({ ...options, client, loadAccounts: async () => await requestPrivyAccounts(), }); } /** Mounts the React boundary needed by {@link privy}. */ export function PrivyAccountsBridge() { const privy = usePrivy(); const wallets = useWallets(); const request = usePrivyAccountsRequest(); const loading = useRef(false); const login_started = useRef(false); const login = useLogin({ onError(error) { login_started.current = false; rejectPrivyAccounts(new Error(String(error))); }, }); useEffect(() => { bridge_mounts += 1; return () => { bridge_mounts = Math.max(bridge_mounts - 1, 0); if (bridge_mounts === 0) rejectPrivyAccounts(new Error('PrivyAccountsBridge unmounted before Privy returned accounts.')); }; }, []); useEffect(() => { setPrivyReactState({ authenticated: privy.authenticated, getAccessToken: privy.getAccessToken, logout: privy.logout, ready: privy.ready, userId: privy.user?.id, wallets: wallets.wallets.filter(isEmbeddedEthereumWallet).map((wallet) => ({ address: wallet.address, getEthereumProvider: wallet.getEthereumProvider, walletIndex: wallet.walletIndex, })), walletsReady: wallets.ready, }); }, [ privy.authenticated, privy.getAccessToken, privy.logout, privy.ready, privy.user, wallets.ready, wallets.wallets, ]); useEffect(() => { if (!request || loading.current || !privy.ready) return; if (!privy.authenticated) { if (!login_started.current) { login_started.current = true; login.login(); } return; } login_started.current = false; const embedded = wallets.wallets.filter(isEmbeddedEthereumWallet); if (!wallets.ready && embedded.length === 0) return; loading.current = true; void (async () => { try { if (embedded.length === 0) throw new Error('Privy React returned no embedded Ethereum wallet.'); resolvePrivyAccounts(embedded.map((wallet) => wallet.address)); } catch (error) { rejectPrivyAccounts(error instanceof Error ? error : new Error(String(error))); } finally { loading.current = false; } })(); }, [login, privy.authenticated, privy.ready, privy.user, request, wallets.ready, wallets.wallets]); return null; } let request; let state = { authenticated: false, ready: false, wallets: [], walletsReady: false, }; let bridge_mounts = 0; const listeners = new Set(); const ready_listeners = new Set(); const wallets_ready_listeners = new Set(); const client = { auth: { async logout() { await state.logout?.(); }, }, embeddedWallet: { async getEthereumProvider(parameters) { await waitForWallets(); const address = parameters.wallet.address; if (!address) throw new Error('Privy React wallet address is required.'); const wallet = state.wallets.find((wallet) => sameAddress(wallet.address, address)); if (!wallet) throw new Error(`Privy React wallet "${address}" is no longer connected.`); return await wallet.getEthereumProvider(); }, }, async getAccessToken() { await waitForReady(); if (!state.authenticated) return null; return (await state.getAccessToken?.()) ?? null; }, initialize() { }, user: { async get() { await waitForWallets(); if (!state.authenticated) throw new Error('Privy React user is not authenticated.'); return { user: { id: state.userId ?? 'privy-react-user', linked_accounts: state.wallets.map((wallet, index) => ({ address: wallet.address, chain_type: 'ethereum', connector_type: 'embedded', type: 'wallet', wallet_client_type: 'privy', wallet_index: wallet.walletIndex ?? index, })), }, }; }, }, }; function setPrivyReactState(next) { state = next; if (next.ready) emitReady(); if (next.ready && (next.walletsReady || next.wallets.length > 0)) emitWalletsReady(); } function usePrivyAccountsRequest() { return useSyncExternalStore(subscribe, getSnapshot, getSnapshot); } function requestPrivyAccounts() { if (bridge_mounts === 0) throw createMissingBridgeError(); if (request) request.reject(new Error('Another Privy React request is already active.')); return new Promise((resolve, reject) => { request = { reject, resolve }; emit(); }); } function resolvePrivyAccounts(accounts) { request?.resolve(accounts); request = undefined; emit(); } function rejectPrivyAccounts(error) { request?.reject(error); request = undefined; emit(); } function emit() { for (const listener of listeners) listener(); } function emitReady() { for (const listener of ready_listeners) listener(); ready_listeners.clear(); } function emitWalletsReady() { for (const listener of wallets_ready_listeners) listener(); wallets_ready_listeners.clear(); } function createMissingBridgeError() { return new Error('PrivyAccountsBridge must be mounted under PrivyProvider to use privy().'); } function getSnapshot() { return request; } function isEmbeddedEthereumWallet(wallet) { return (wallet.type === 'ethereum' && wallet.walletClientType === 'privy' && wallet.connectorType === 'embedded'); } function sameAddress(a, b) { return a.toLowerCase() === b.toLowerCase(); } function subscribe(listener) { listeners.add(listener); return () => listeners.delete(listener); } function waitForReady() { if (state.ready) return Promise.resolve(); return new Promise((resolve) => ready_listeners.add(resolve)); } async function waitForWallets() { await waitForReady(); if (state.walletsReady || state.wallets.length > 0) return; return new Promise((resolve) => wallets_ready_listeners.add(resolve)); } //# sourceMappingURL=privy.js.map