accounts
Version:
Tempo Accounts SDK
223 lines • 7.28 kB
JavaScript
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