// Copyright (C) 2026 Whiterun LLC, // This software is licensed under the GNU Lesser General Public License (LGPL), version 3.0 or later. // A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/lgpl-3.0.html import { useState, useRef, useEffect, useCallback } from "react"; import { initiateDappRelay, type DappRelayResult, type RelayUpdatePayload, } from "@wizardconnect/core"; import { DappConnectionManager, loadSession } from "@wizardconnect/dapp"; import type { UseWizardConnectOptions, UseWizardConnectResult, WizardConnectState, } from "../types.js"; const DEFAULT_SESSION_KEY = "wizardconnect-session"; /** * React hook that encapsulates the WizardConnect relay lifecycle. * * Manages: relay initiation, DappConnectionManager, key exchange events, * session persistence, and auto-reconnect on page refresh. * * The returned `manager` can be used to build an app-specific wallet adapter. */ export function useWizardConnect( options: UseWizardConnectOptions = {}, ): UseWizardConnectResult { const { dappName, dappIcon, relayUrls, sessionKey = DEFAULT_SESSION_KEY, persistSession = true, storage, } = options; const [state, setState] = useState("idle"); const [manager, setManager] = useState(null); const [uri, setUri] = useState(null); const [qrUri, setQrUri] = useState(null); const [walletName, setWalletName] = useState(null); const [walletIcon, setWalletIcon] = useState(null); const [error, setError] = useState(null); const relayRef = useRef(null); const managerRef = useRef(null); const autoReconnectAttempted = useRef(false); const startRelay = useCallback( (existingCredentials?: { privateKey: string; secret: string; walletPublicKey: string; }): boolean => { if (state === "connecting" || state === "connected") return false; setError(null); setState("connecting"); const mgr = new DappConnectionManager(dappName, dappIcon, { session: persistSession ? { key: sessionKey, storage } : false, }); managerRef.current = mgr; setManager(mgr); mgr.on("walletready", () => { setWalletName(mgr.walletName); setWalletIcon(mgr.walletIcon); setState("connected"); }); mgr.on("reconnecting", () => { setState("reconnecting"); }); mgr.on("disconnect", () => { setState("disconnected"); setWalletName(null); setWalletIcon(null); mgr.clearStoredSession(); relayRef.current?.cleanup(); relayRef.current = null; }); try { const relay = initiateDappRelay( (payload: RelayUpdatePayload) => { mgr.updateConnection(payload.client, payload.status); }, { existingCredentials, explicitRelayUrls: relayUrls, }, ); relayRef.current = relay; // Only expose URI for new connections (QR pairing), not reconnects if (!existingCredentials) { setUri(relay.uri); setQrUri(relay.qrUri); } mgr.attachRelay(relay); return true; } catch (e) { const message = e instanceof Error ? e.message : "Failed to start relay"; setError(message); setState("idle"); return false; } }, [state, dappName, dappIcon, relayUrls, sessionKey, persistSession, storage], ); const connect = useCallback((): boolean => { return startRelay(); }, [startRelay]); const disconnect = useCallback(async (): Promise => { try { if (managerRef.current) { await managerRef.current.sendDisconnect().catch(() => {}); } } finally { relayRef.current?.cleanup(); relayRef.current = null; managerRef.current?.clearStoredSession(); managerRef.current = null; setManager(null); setUri(null); setQrUri(null); setWalletName(null); setWalletIcon(null); setState("idle"); } }, []); // Auto-reconnect on mount if a stored session exists useEffect(() => { if (autoReconnectAttempted.current) return; if (!persistSession) return; autoReconnectAttempted.current = true; // Read session before creating the manager (need credentials for startRelay) const stored = loadSession(sessionKey, storage); if (!stored || !stored.walletPublicKey) return; if (stored.walletName) { setWalletName(stored.walletName); } startRelay({ privateKey: stored.privateKey, secret: stored.secret, walletPublicKey: stored.walletPublicKey, }); }, [persistSession, sessionKey, storage, startRelay]); // Cleanup on unmount useEffect(() => { return () => { managerRef.current?.destroy(); relayRef.current?.cleanup(); }; }, []); return { state, manager, uri, qrUri, walletName, walletIcon, connect, disconnect, error, }; }