diff --git a/src/components/bitcoinConnect/BitcoinConnect.js b/src/components/bitcoinConnect/BitcoinConnect.js index d9f83e1..6e60241 100644 --- a/src/components/bitcoinConnect/BitcoinConnect.js +++ b/src/components/bitcoinConnect/BitcoinConnect.js @@ -5,31 +5,65 @@ const Button = dynamic(() => import('@getalby/bitcoin-connect-react').then(mod = ssr: false, }); +// Module-level state let initialized = false; +let initializationPromise = null; +let bitcoinConnectClient = null; export async function initializeBitcoinConnect() { - if (!initialized) { + if (initialized) return bitcoinConnectClient; // fast path + + if (initializationPromise) return initializationPromise; // someone else is already doing the work + + initializationPromise = (async () => { try { - const { init } = await import('@getalby/bitcoin-connect-react'); - // Check if custom elements are already registered - if (!customElements.get('bc-balance')) { - init({ - appName: 'PlebDevs', - filters: ['nwc'], - showBalance: false, - }); - initialized = true; + // Import the required modules + const bc = await import('@getalby/bitcoin-connect-react'); + const sdkModule = await import('@getalby/sdk'); + + // Initialize with the config options + bc.init({ + appName: 'PlebDevs', + filters: ['nwc'], + showBalance: false, + }); + + // Store the client for use in components + bitcoinConnectClient = bc.client; + + // Export NWC functionality directly + if (!bitcoinConnectClient) { + console.log('Creating backup NWC client instance'); + // Create fallback if client isn't available + bitcoinConnectClient = { + nwc: sdkModule.nwc, + webln: sdkModule.webln + }; } + + initialized = true; + console.log('Bitcoin Connect initialized successfully, client:', bitcoinConnectClient); + return bitcoinConnectClient; } catch (error) { // If the error is about custom element already being defined, we can ignore it // as it means the component is already initialized if (!error.message?.includes('has already been defined as a custom element')) { console.error('Error initializing Bitcoin Connect:', error); } + throw error; // re-throw so callers see the failure + } finally { + initializationPromise = null; // allow retry after failure } - } + })(); + + return initializationPromise; } +// Export the SDK for direct usage +export const getSDK = async () => { + return import('@getalby/sdk'); +}; + const BitcoinConnectButton = () => { useEffect(() => { initializeBitcoinConnect(); diff --git a/src/components/bitcoinConnect/SubscriptionPaymentButton.js b/src/components/bitcoinConnect/SubscriptionPaymentButton.js index 695899f..8ee2819 100644 --- a/src/components/bitcoinConnect/SubscriptionPaymentButton.js +++ b/src/components/bitcoinConnect/SubscriptionPaymentButton.js @@ -1,10 +1,9 @@ import React, { useState, useEffect } from 'react'; import { track } from '@vercel/analytics'; -import { initializeBitcoinConnect } from './BitcoinConnect'; +import { initializeBitcoinConnect, getSDK } from './BitcoinConnect'; import { LightningAddress } from '@getalby/lightning-tools'; import { useToast } from '@/hooks/useToast'; import { useSession } from 'next-auth/react'; -import { webln, nwc } from '@getalby/sdk'; import { useRouter } from 'next/router'; import { Divider } from 'primereact/divider'; import dynamic from 'next/dynamic'; @@ -38,23 +37,28 @@ const SubscriptionPaymentButtons = ({ const amount = 50000; useEffect(() => { - initializeBitcoinConnect(); + // Initialize Bitcoin Connect as early as possible + initializeBitcoinConnect().catch(err => { + console.error("Error initializing Bitcoin Connect:", err); + }); }, []); useEffect(() => { let intervalId; if (invoice) { intervalId = setInterval(async () => { - const paid = await invoice.verifyPayment(); - - if (paid && invoice.preimage) { + try { + const paid = await invoice.verifyPayment(); + if (paid) { + clearInterval(intervalId); + // handle success + onSuccess(); + } + } catch (error) { + console.error("Error verifying payment:", error); clearInterval(intervalId); - // handle success - onSuccess(); } }, 1000); - } else { - console.error('no invoice'); } return () => { @@ -62,7 +66,7 @@ const SubscriptionPaymentButtons = ({ clearInterval(intervalId); } }; - }, [invoice]); + }, [invoice, onSuccess]); const fetchInvoice = async () => { try { @@ -94,12 +98,22 @@ const SubscriptionPaymentButtons = ({ }; const handleRecurringSubscription = async () => { - setIsProcessing(true); - const newNwc = nwc.NWCClient.withNewSecret(); - const yearFromNow = new Date(); - yearFromNow.setFullYear(yearFromNow.getFullYear() + 1); - + if (!setIsProcessing) { + console.warn("setIsProcessing is not defined"); + } else { + setIsProcessing(true); + } + try { + // Get SDK directly to avoid client issues + const sdk = await getSDK(); + + // Create NWC client + const newNwc = sdk.nwc.NWCClient.withNewSecret(); + + const yearFromNow = new Date(); + yearFromNow.setFullYear(yearFromNow.getFullYear() + 1); + const initNwcOptions = { name: 'plebdevs.com', requestMethods: ['pay_invoice'], @@ -108,31 +122,44 @@ const SubscriptionPaymentButtons = ({ budgetRenewal: 'monthly', expiresAt: yearFromNow, }; + + console.log("Initializing NWC with options:", initNwcOptions); + + // Initialize NWC await newNwc.initNWC(initNwcOptions); showToast('info', 'Alby', 'Alby connection window opened.'); + + // Get NWC URL const newNWCUrl = newNwc.getNostrWalletConnectUrl(); if (newNWCUrl) { - const nwc = new webln.NostrWebLNProvider({ + const nwcProvider = new sdk.webln.NostrWebLNProvider({ nostrWalletConnectUrl: newNWCUrl, }); - await nwc.enable(); + await nwcProvider.enable(); + console.log("NWC provider enabled"); const invoice = await fetchInvoice(); + console.log("Invoice fetched for recurring payment:", !!invoice); if (!invoice || !invoice.paymentRequest) { showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`); + if (setIsProcessing) setIsProcessing(false); return; } - const paymentResponse = await nwc.sendPayment(invoice.paymentRequest); + console.log("Sending payment with NWC provider"); + const paymentResponse = await nwcProvider.sendPayment(invoice.paymentRequest); + console.log("Payment response:", paymentResponse?.preimage); if (!paymentResponse || !paymentResponse?.preimage) { showToast('error', 'NWC', 'Payment failed'); + if (setIsProcessing) setIsProcessing(false); return; } + console.log("Updating subscription in API"); const subscriptionResponse = await axios.put('/api/users/subscription', { userId: session.user.id, isSubscribed: true, @@ -154,7 +181,7 @@ const SubscriptionPaymentButtons = ({ showToast('error', 'Subscription Setup Failed', `Error: ${error.message}`); if (onError) onError(error); } finally { - setIsProcessing(false); + if (setIsProcessing) setIsProcessing(false); } }; @@ -164,29 +191,40 @@ const SubscriptionPaymentButtons = ({ return; } - setIsProcessing(true); + if (setIsProcessing) setIsProcessing(true); + try { - const nwc = new webln.NostrWebLNProvider({ + const sdk = await getSDK(); + const nwc = new sdk.webln.NostrWebLNProvider({ nostrWalletConnectUrl: nwcInput, }); await nwc.enable(); + console.log("Manual NWC provider enabled"); const invoice = await fetchInvoice(); + console.log("Invoice fetched for manual NWC:", !!invoice); + if (!invoice || !invoice.paymentRequest) { showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`); + if (setIsProcessing) setIsProcessing(false); return; } + console.log("Sending payment with manual NWC"); const payResponse = await nwc.sendPayment(invoice.paymentRequest); + console.log("Payment response:", payResponse?.preimage); + if (!payResponse || !payResponse.preimage) { showToast('error', 'NWC', 'Payment failed'); + if (setIsProcessing) setIsProcessing(false); return; } showToast('success', 'NWC', 'Payment successful!'); try { + console.log("Updating subscription in API (manual)"); const subscriptionResponse = await axios.put('/api/users/subscription', { userId: session.user.id, isSubscribed: true, @@ -210,7 +248,7 @@ const SubscriptionPaymentButtons = ({ showToast('error', 'NWC', `An error occurred: ${error.message}`); if (onError) onError(error); } finally { - setIsProcessing(false); + if (setIsProcessing) setIsProcessing(false); } }; diff --git a/src/components/content/courses/details/CourseDetails.js b/src/components/content/courses/details/CourseDetails.js index e1c3c83..53ef5e6 100644 --- a/src/components/content/courses/details/CourseDetails.js +++ b/src/components/content/courses/details/CourseDetails.js @@ -127,7 +127,7 @@ export default function CourseDetails({ if (session?.user && session.user?.role?.subscribed && decryptionPerformed) { return ( ); @@ -163,7 +163,7 @@ export default function CourseDetails({ if (paidCourse && author && processedEvent?.pubkey === session?.user?.pubkey) { return (