UNPKG

bigblocks

Version:

Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React

262 lines (261 loc) 9.65 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; /** * Yours Wallet Provider Integration * * This module provides lazy-loaded integration with Yours Wallet * using the window.yours provider API when available. */ import React from "react"; // Yours Wallet detection and connection export class YoursWalletProvider { provider = null; isInstalled = false; isConnected = false; publicKey = null; listeners = new Map(); constructor() { this.checkInstallation(); this.setupEventListeners(); } checkInstallation() { this.isInstalled = typeof window !== "undefined" && !!window.yours; if (this.isInstalled && window.yours) { this.provider = window.yours; } } setupEventListeners() { if (!this.provider) return; const handleSwitchAccount = () => { this.publicKey = null; this.isConnected = false; this.emit("disconnected"); }; const handleSignedOut = () => { this.publicKey = null; this.isConnected = false; this.emit("disconnected"); }; this.provider.on("switchAccount", handleSwitchAccount); this.provider.on("signedOut", handleSignedOut); } isWalletInstalled() { return this.isInstalled; } async connect() { if (!this.isInstalled || !this.provider) { return { success: false, error: "Yours Wallet not installed. Please install from Chrome Web Store.", }; } try { const publicKey = await this.provider.connect(); this.publicKey = publicKey; this.isConnected = true; this.emit("connected"); return { success: true, publicKey }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Failed to connect to Yours Wallet", }; } } async disconnect() { if (!this.provider) return; try { await this.provider.disconnect(); this.publicKey = null; this.isConnected = false; this.emit("disconnected"); } catch (error) { console.error("Error disconnecting from Yours Wallet:", error); } } getPublicKey() { return this.publicKey; } isWalletConnected() { return this.isConnected && !!this.publicKey; } async getBalance() { if (!this.provider || !this.isConnected) return null; try { return await this.provider.getBalance(); } catch (error) { console.error("Error getting balance:", error); return null; } } // Event management on(event, handler) { if (!this.listeners.has(event)) { this.listeners.set(event, new Set()); } this.listeners.get(event)?.add(handler); } off(event, handler) { const eventListeners = this.listeners.get(event); if (eventListeners) { eventListeners.delete(handler); } } emit(event) { const eventListeners = this.listeners.get(event); if (eventListeners) { for (const handler of eventListeners) { handler(); } } } } // React hook for Yours Wallet export function useYoursWallet() { const [provider] = React.useState(() => new YoursWalletProvider()); const [isInstalled, _setIsInstalled] = React.useState(provider.isWalletInstalled()); const [isConnected, setIsConnected] = React.useState(provider.isWalletConnected()); const [publicKey, setPublicKey] = React.useState(provider.getPublicKey()); const [balance, setBalance] = React.useState(null); React.useEffect(() => { const handleConnected = () => { setIsConnected(true); setPublicKey(provider.getPublicKey()); // Fetch balance on connection provider.getBalance().then(setBalance).catch(console.error); }; const handleDisconnected = () => { setIsConnected(false); setPublicKey(null); setBalance(null); }; provider.on("connected", handleConnected); provider.on("disconnected", handleDisconnected); return () => { provider.off("connected", handleConnected); provider.off("disconnected", handleDisconnected); }; }, [provider]); const connect = React.useCallback(async () => { return await provider.connect(); }, [provider]); const disconnect = React.useCallback(async () => { await provider.disconnect(); }, [provider]); const refreshBalance = React.useCallback(async () => { if (isConnected) { const newBalance = await provider.getBalance(); setBalance(newBalance); } }, [provider, isConnected]); return { isInstalled, isConnected, publicKey, balance, connect, disconnect, refreshBalance, provider, }; } // Yours Wallet OAuth authentication handler // Integrates with OAuth system like Google/GitHub providers export async function createYoursAuthHandler() { const yoursProvider = new YoursWalletProvider(); // Check if Yours Wallet is installed if (!yoursProvider.isWalletInstalled()) { return { success: false, error: "Yours Wallet not installed. Please install from Chrome Web Store.", }; } try { // Connect to wallet const connectResult = await yoursProvider.connect(); if (!connectResult.success) { return connectResult; } const publicKey = connectResult.publicKey; if (!publicKey) { return { success: false, error: "Failed to get public key from wallet connection", }; } // Generate challenge for signing const challenge = globalThis.crypto.getRandomValues(new Uint8Array(32)); const challengeHex = Array.from(challenge) .map((b) => b.toString(16).padStart(2, "0")) .join(""); // Note: This uses OAuth-style authentication flow // Backend handles address derivation and BAP ID lookup via verifyByAddress API // No private key signing required on frontend // Send to backend for OAuth-style authentication // Backend will: // 1. Derive address from public key // 2. Use verifyByAddress API to lookup BAP ID // 3. Create OAuth token for session management // 4. Retrieve encrypted backup if exists const response = await fetch("/api/auth/yours/oauth", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ publicKey, challenge: challengeHex, provider: "yours", // OAuth provider identifier }), }); if (!response.ok) { return { success: false, error: "OAuth authentication with backend failed", }; } const result = await response.json(); // Get balance for profile const balance = await yoursProvider.getBalance(); return { success: true, idKey: result.idKey, // Backend uses verifyByAddress API to lookup BAP ID encryptedBackup: result.encryptedBackup, oauthToken: result.oauthToken, // OAuth-style token for session management profile: { address: result.address, publicKey, balance: balance || undefined, }, }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : "Unknown error occurred", }; } finally { // Clean up connection await yoursProvider.disconnect(); } } export function YoursInstallPrompt({ onInstalled, className = "", }) { const handleInstall = () => { window.open("https://chromewebstore.google.com/detail/yours-wallet/mlbnicldlpdimbjdcncnklfempedeipj", "_blank"); }; const handleCheckInstalled = () => { // Re-check after potential installation setTimeout(() => { if (typeof window !== "undefined" && window.yours) { onInstalled?.(); } }, 1000); }; return (_jsx("div", { className: `text-center space-y-4 ${className}`, children: _jsxs("div", { className: "p-4 bg-purple-900/20 border border-purple-500/30 rounded-lg", children: [_jsx("h3", { className: "text-lg font-semibold text-purple-300 mb-2", children: "Yours Wallet Required" }), _jsx("p", { className: "text-gray-300 text-sm mb-4", children: "To use Yours Wallet authentication, please install the browser extension." }), _jsxs("div", { className: "space-y-2", children: [_jsx("button", { type: "button", onClick: handleInstall, className: "w-full px-4 py-2 bg-purple-600 hover:bg-purple-700 text-white rounded-lg transition-colors", children: "Install Yours Wallet" }), _jsx("button", { type: "button", onClick: handleCheckInstalled, className: "w-full px-4 py-2 bg-gray-600 hover:bg-gray-700 text-white rounded-lg transition-colors", children: "I've Installed It" })] })] }) })); }