182 lines
5.1 KiB
TypeScript
182 lines
5.1 KiB
TypeScript
// 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<WizardConnectState>("idle");
|
|
const [manager, setManager] = useState<DappConnectionManager | null>(null);
|
|
const [uri, setUri] = useState<string | null>(null);
|
|
const [qrUri, setQrUri] = useState<string | null>(null);
|
|
const [walletName, setWalletName] = useState<string | null>(null);
|
|
const [walletIcon, setWalletIcon] = useState<string | null>(null);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
const relayRef = useRef<DappRelayResult | null>(null);
|
|
const managerRef = useRef<DappConnectionManager | null>(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<void> => {
|
|
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,
|
|
};
|
|
}
|