UNPKG

@btc-vision/walletconnect

Version:

The OP_NET Wallet Connect library helps your dApp connect to any compatible wallet.

179 lines (178 loc) 6.97 kB
import { jsx as _jsx } from "react/jsx-runtime"; import { createContext, useCallback, useContext, useEffect, useRef, useState, } from 'react'; import WalletConnection, { SupportedWallets } from './WalletConnection'; const WalletContext = createContext(undefined); const MAX_RETRIES = 10; const RETRY_DELAY_MS = 2000; function useDocumentComplete(fn) { const fired = useRef(false); useEffect(() => { if (fired.current) return; const run = () => { if (fired.current) return; fired.current = true; fn(); }; if (document.readyState === 'complete') { run(); return; } const handler = () => { if (document.readyState === 'complete') { document.removeEventListener('readystatechange', handler); run(); } }; document.addEventListener('readystatechange', handler); return () => document.removeEventListener('readystatechange', handler); }, [fn]); } export const WalletProvider = ({ children }) => { const [walletConnection] = useState(() => new WalletConnection()); const [walletType, setWalletType] = useState(null); const [walletWindowInstance, setWalletWindowInstance] = useState(null); const [account, setAccount] = useState(null); const listeners = useRef({}); const disconnect = useCallback(() => { const inst = walletWindowInstance; if (inst) { if (listeners.current.disconnect) { inst.removeListener?.('disconnect', listeners.current.disconnect); listeners.current.disconnect = undefined; } if (listeners.current.accountsChanged) { inst.removeListener?.('accountsChanged', listeners.current.accountsChanged); listeners.current.accountsChanged = undefined; } } walletConnection.disconnect(); setWalletType(null); setWalletWindowInstance(null); localStorage.removeItem('walletType'); setAccount(null); }, [walletConnection, walletWindowInstance]); const connect = useCallback(async (type, signal) => { let attempt = 0; const throwIfAborted = () => { if (signal?.aborted) throw new DOMException('Aborted', 'AbortError'); }; while (attempt < MAX_RETRIES) { throwIfAborted(); try { await walletConnection.connect(type); if ((walletConnection.walletType !== SupportedWallets.OP_WALLET && !walletConnection.signer) || !walletConnection.walletWindowInstance) { throw new Error('Wallet not fully loaded yet'); } break; } catch (err) { attempt += 1; if (attempt >= MAX_RETRIES) { console.warn(`Failed to connect after ${MAX_RETRIES} attempts.`, err); disconnect(); return; } console.warn(`Connection attempt ${attempt} failed:`, err.message); try { await new Promise((res, rej) => { const t = setTimeout(res, RETRY_DELAY_MS); signal?.addEventListener('abort', () => { clearTimeout(t); rej(new DOMException('Aborted', 'AbortError')); }); }); } catch { console.debug('Connection aborted during retry delay.'); return; } } } throwIfAborted(); setWalletType(type); setWalletWindowInstance(walletConnection.walletWindowInstance); localStorage.setItem('walletType', type); try { const [signer, address, addressTyped, network, provider] = await Promise.all([ walletConnection.signer, walletConnection.getAddress(), walletConnection.getAddressTyped(), walletConnection.getNetwork(), walletConnection.getProvider(), ]); setAccount({ isConnected: true, signer, address, addressTyped, network, provider, }); const instance = walletConnection.walletWindowInstance; if (instance) { const onDisconnect = () => disconnect(); const onAccountsChanged = async () => { try { const [updatedAddr, updatedAddrTyped, updatedNet, updatedProv] = await Promise.all([ walletConnection.getAddress(), walletConnection.getAddressTyped(), walletConnection.getNetwork(), walletConnection.getProvider(), ]); setAccount((prev) => prev ? { ...prev, address: updatedAddr, addressTyped: updatedAddrTyped, network: updatedNet, provider: updatedProv, } : prev); } catch { disconnect(); } }; listeners.current.disconnect = onDisconnect; listeners.current.accountsChanged = onAccountsChanged; instance.on('disconnect', onDisconnect); instance.on('accountsChanged', onAccountsChanged); } } catch (err) { console.warn('Unable to finalize wallet connection:', err); disconnect(); } }, [walletConnection, disconnect]); useDocumentComplete(() => { const stored = localStorage.getItem('walletType'); if (!stored) return; const controller = new AbortController(); connect(stored, controller.signal).catch((err) => { if (err.name !== 'AbortError') { console.warn('Failed to reconnect to wallet:', err); } }); return () => controller.abort(); }); const ctx = { connect, disconnect, walletType, walletWindowInstance, account, }; return _jsx(WalletContext.Provider, { value: ctx, children: children }); }; export const useWallet = () => { const ctx = useContext(WalletContext); if (!ctx) throw new Error('useWallet must be used within a WalletProvider'); return ctx; };