bigblocks
Version:
Complete Bitcoin UI component library - authentication, social, wallet, market, inscriptions, and blockchain interactions for React
262 lines (261 loc) • 9.65 kB
JavaScript
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" })] })] }) }));
}