mirror of
https://github.com/AustinKelsay/plebdevs.git
synced 2025-06-05 00:32:03 +00:00
update Bitcoin Connect integration to use client-based approach and direct SDK for NWC operations
This commit is contained in:
parent
5dd71c3de0
commit
3ec3f69ae6
@ -5,14 +5,16 @@ const Button = dynamic(() => import('@getalby/bitcoin-connect-react').then(mod =
|
|||||||
ssr: false,
|
ssr: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Module-level state
|
||||||
let initialized = false;
|
let initialized = false;
|
||||||
let bitcoinConnectClient = null;
|
let bitcoinConnectClient = null;
|
||||||
|
|
||||||
export async function initializeBitcoinConnect() {
|
export async function initializeBitcoinConnect() {
|
||||||
if (!initialized) {
|
if (!initialized) {
|
||||||
try {
|
try {
|
||||||
// Import the full module
|
// Import the required modules
|
||||||
const bc = await import('@getalby/bitcoin-connect-react');
|
const bc = await import('@getalby/bitcoin-connect-react');
|
||||||
|
const sdkModule = await import('@getalby/sdk');
|
||||||
|
|
||||||
// Initialize with the config options
|
// Initialize with the config options
|
||||||
bc.init({
|
bc.init({
|
||||||
@ -23,6 +25,17 @@ export async function initializeBitcoinConnect() {
|
|||||||
|
|
||||||
// Store the client for use in components
|
// Store the client for use in components
|
||||||
bitcoinConnectClient = bc.client;
|
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;
|
initialized = true;
|
||||||
console.log('Bitcoin Connect initialized successfully, client:', bitcoinConnectClient);
|
console.log('Bitcoin Connect initialized successfully, client:', bitcoinConnectClient);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -35,9 +48,15 @@ export async function initializeBitcoinConnect() {
|
|||||||
} else {
|
} else {
|
||||||
console.log('Bitcoin Connect already initialized');
|
console.log('Bitcoin Connect already initialized');
|
||||||
}
|
}
|
||||||
|
|
||||||
return bitcoinConnectClient;
|
return bitcoinConnectClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Export the SDK for direct usage
|
||||||
|
export const getSDK = async () => {
|
||||||
|
return import('@getalby/sdk');
|
||||||
|
};
|
||||||
|
|
||||||
const BitcoinConnectButton = () => {
|
const BitcoinConnectButton = () => {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeBitcoinConnect();
|
initializeBitcoinConnect();
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { track } from '@vercel/analytics';
|
import { track } from '@vercel/analytics';
|
||||||
import { initializeBitcoinConnect } from './BitcoinConnect';
|
import { initializeBitcoinConnect, getSDK } from './BitcoinConnect';
|
||||||
import { LightningAddress } from '@getalby/lightning-tools';
|
import { LightningAddress } from '@getalby/lightning-tools';
|
||||||
import { useToast } from '@/hooks/useToast';
|
import { useToast } from '@/hooks/useToast';
|
||||||
import { useSession } from 'next-auth/react';
|
import { useSession } from 'next-auth/react';
|
||||||
import { webln } from '@getalby/sdk';
|
|
||||||
import { useRouter } from 'next/router';
|
import { useRouter } from 'next/router';
|
||||||
import { Divider } from 'primereact/divider';
|
import { Divider } from 'primereact/divider';
|
||||||
import dynamic from 'next/dynamic';
|
import dynamic from 'next/dynamic';
|
||||||
@ -30,7 +29,6 @@ const SubscriptionPaymentButtons = ({
|
|||||||
const [invoice, setInvoice] = useState(null);
|
const [invoice, setInvoice] = useState(null);
|
||||||
const [showRecurringOptions, setShowRecurringOptions] = useState(false);
|
const [showRecurringOptions, setShowRecurringOptions] = useState(false);
|
||||||
const [nwcInput, setNwcInput] = useState('');
|
const [nwcInput, setNwcInput] = useState('');
|
||||||
const [bitcoinConnectClient, setBitcoinConnectClient] = useState(null);
|
|
||||||
const { showToast } = useToast();
|
const { showToast } = useToast();
|
||||||
const { data: session, status } = useSession();
|
const { data: session, status } = useSession();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@ -40,33 +38,27 @@ const SubscriptionPaymentButtons = ({
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Initialize Bitcoin Connect as early as possible
|
// Initialize Bitcoin Connect as early as possible
|
||||||
const initBC = async () => {
|
initializeBitcoinConnect().catch(err => {
|
||||||
try {
|
console.error("Error initializing Bitcoin Connect:", err);
|
||||||
const client = await initializeBitcoinConnect();
|
});
|
||||||
console.log("Client in SubscriptionPaymentButton:", client);
|
|
||||||
setBitcoinConnectClient(client);
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error initializing Bitcoin Connect in SubscriptionPaymentButton:", err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
initBC();
|
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let intervalId;
|
let intervalId;
|
||||||
if (invoice) {
|
if (invoice) {
|
||||||
intervalId = setInterval(async () => {
|
intervalId = setInterval(async () => {
|
||||||
const paid = await invoice.verifyPayment();
|
try {
|
||||||
|
const paid = await invoice.verifyPayment();
|
||||||
if (paid && invoice.preimage) {
|
if (paid && invoice.preimage) {
|
||||||
|
clearInterval(intervalId);
|
||||||
|
// handle success
|
||||||
|
onSuccess();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error verifying payment:", error);
|
||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
// handle success
|
|
||||||
onSuccess();
|
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
} else {
|
|
||||||
console.error('no invoice');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
@ -74,7 +66,7 @@ const SubscriptionPaymentButtons = ({
|
|||||||
clearInterval(intervalId);
|
clearInterval(intervalId);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}, [invoice]);
|
}, [invoice, onSuccess]);
|
||||||
|
|
||||||
const fetchInvoice = async () => {
|
const fetchInvoice = async () => {
|
||||||
try {
|
try {
|
||||||
@ -84,6 +76,7 @@ const SubscriptionPaymentButtons = ({
|
|||||||
satoshi: amount,
|
satoshi: amount,
|
||||||
comment: `Subscription Purchase. User: ${session?.user?.id}`,
|
comment: `Subscription Purchase. User: ${session?.user?.id}`,
|
||||||
});
|
});
|
||||||
|
console.log("Invoice fetched successfully:", newInvoice);
|
||||||
return newInvoice;
|
return newInvoice;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching invoice:', error);
|
console.error('Error fetching invoice:', error);
|
||||||
@ -106,32 +99,18 @@ const SubscriptionPaymentButtons = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleRecurringSubscription = async () => {
|
const handleRecurringSubscription = async () => {
|
||||||
setIsProcessing(true);
|
if (!setIsProcessing) {
|
||||||
|
console.warn("setIsProcessing is not defined");
|
||||||
// Re-initialize if not already initialized
|
} else {
|
||||||
if (!bitcoinConnectClient) {
|
setIsProcessing(true);
|
||||||
try {
|
|
||||||
console.log("Client not found, reinitializing");
|
|
||||||
const client = await initializeBitcoinConnect();
|
|
||||||
setBitcoinConnectClient(client);
|
|
||||||
|
|
||||||
if (!client) {
|
|
||||||
showToast('error', 'Connection Error', 'Failed to initialize Bitcoin Connect client');
|
|
||||||
setIsProcessing(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error reinitializing Bitcoin Connect:", err);
|
|
||||||
showToast('error', 'Connection Error', 'Failed to initialize Bitcoin Connect client');
|
|
||||||
setIsProcessing(false);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Import the SDK directly to avoid client issues
|
// Get SDK directly to avoid client issues
|
||||||
const { nwc } = await import('@getalby/sdk');
|
const sdk = await getSDK();
|
||||||
const newNwc = nwc.NWCClient.withNewSecret();
|
|
||||||
|
// Create NWC client
|
||||||
|
const newNwc = sdk.nwc.NWCClient.withNewSecret();
|
||||||
|
|
||||||
const yearFromNow = new Date();
|
const yearFromNow = new Date();
|
||||||
yearFromNow.setFullYear(yearFromNow.getFullYear() + 1);
|
yearFromNow.setFullYear(yearFromNow.getFullYear() + 1);
|
||||||
@ -145,36 +124,44 @@ const SubscriptionPaymentButtons = ({
|
|||||||
expiresAt: yearFromNow,
|
expiresAt: yearFromNow,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Initialize NWC directly with the SDK
|
console.log("Initializing NWC with options:", initNwcOptions);
|
||||||
|
|
||||||
|
// Initialize NWC
|
||||||
await newNwc.initNWC(initNwcOptions);
|
await newNwc.initNWC(initNwcOptions);
|
||||||
showToast('info', 'Alby', 'Alby connection window opened.');
|
showToast('info', 'Alby', 'Alby connection window opened.');
|
||||||
|
|
||||||
// Get NWC URL directly
|
// Get NWC URL
|
||||||
const newNWCUrl = newNwc.getNostrWalletConnectUrl();
|
const newNWCUrl = newNwc.getNostrWalletConnectUrl();
|
||||||
|
console.log("NWC URL generated:", !!newNWCUrl);
|
||||||
|
|
||||||
if (newNWCUrl) {
|
if (newNWCUrl) {
|
||||||
const nwcProvider = new webln.NostrWebLNProvider({
|
const nwcProvider = new sdk.webln.NostrWebLNProvider({
|
||||||
nostrWalletConnectUrl: newNWCUrl,
|
nostrWalletConnectUrl: newNWCUrl,
|
||||||
});
|
});
|
||||||
|
|
||||||
await nwcProvider.enable();
|
await nwcProvider.enable();
|
||||||
|
console.log("NWC provider enabled");
|
||||||
|
|
||||||
const invoice = await fetchInvoice();
|
const invoice = await fetchInvoice();
|
||||||
|
console.log("Invoice fetched for recurring payment:", !!invoice);
|
||||||
|
|
||||||
if (!invoice || !invoice.paymentRequest) {
|
if (!invoice || !invoice.paymentRequest) {
|
||||||
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
||||||
setIsProcessing(false);
|
if (setIsProcessing) setIsProcessing(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Sending payment with NWC provider");
|
||||||
const paymentResponse = await nwcProvider.sendPayment(invoice.paymentRequest);
|
const paymentResponse = await nwcProvider.sendPayment(invoice.paymentRequest);
|
||||||
|
console.log("Payment response:", paymentResponse);
|
||||||
|
|
||||||
if (!paymentResponse || !paymentResponse?.preimage) {
|
if (!paymentResponse || !paymentResponse?.preimage) {
|
||||||
showToast('error', 'NWC', 'Payment failed');
|
showToast('error', 'NWC', 'Payment failed');
|
||||||
setIsProcessing(false);
|
if (setIsProcessing) setIsProcessing(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Updating subscription in API");
|
||||||
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
isSubscribed: true,
|
isSubscribed: true,
|
||||||
@ -196,7 +183,7 @@ const SubscriptionPaymentButtons = ({
|
|||||||
showToast('error', 'Subscription Setup Failed', `Error: ${error.message}`);
|
showToast('error', 'Subscription Setup Failed', `Error: ${error.message}`);
|
||||||
if (onError) onError(error);
|
if (onError) onError(error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
if (setIsProcessing) setIsProcessing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -206,29 +193,40 @@ const SubscriptionPaymentButtons = ({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsProcessing(true);
|
if (setIsProcessing) setIsProcessing(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const nwc = new webln.NostrWebLNProvider({
|
const sdk = await getSDK();
|
||||||
|
const nwc = new sdk.webln.NostrWebLNProvider({
|
||||||
nostrWalletConnectUrl: nwcInput,
|
nostrWalletConnectUrl: nwcInput,
|
||||||
});
|
});
|
||||||
|
|
||||||
await nwc.enable();
|
await nwc.enable();
|
||||||
|
console.log("Manual NWC provider enabled");
|
||||||
|
|
||||||
const invoice = await fetchInvoice();
|
const invoice = await fetchInvoice();
|
||||||
|
console.log("Invoice fetched for manual NWC:", !!invoice);
|
||||||
|
|
||||||
if (!invoice || !invoice.paymentRequest) {
|
if (!invoice || !invoice.paymentRequest) {
|
||||||
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
showToast('error', 'NWC', `Failed to fetch invoice from ${lnAddress}`);
|
||||||
|
if (setIsProcessing) setIsProcessing(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log("Sending payment with manual NWC");
|
||||||
const payResponse = await nwc.sendPayment(invoice.paymentRequest);
|
const payResponse = await nwc.sendPayment(invoice.paymentRequest);
|
||||||
|
console.log("Payment response:", payResponse);
|
||||||
|
|
||||||
if (!payResponse || !payResponse.preimage) {
|
if (!payResponse || !payResponse.preimage) {
|
||||||
showToast('error', 'NWC', 'Payment failed');
|
showToast('error', 'NWC', 'Payment failed');
|
||||||
|
if (setIsProcessing) setIsProcessing(false);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showToast('success', 'NWC', 'Payment successful!');
|
showToast('success', 'NWC', 'Payment successful!');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
console.log("Updating subscription in API (manual)");
|
||||||
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
const subscriptionResponse = await axios.put('/api/users/subscription', {
|
||||||
userId: session.user.id,
|
userId: session.user.id,
|
||||||
isSubscribed: true,
|
isSubscribed: true,
|
||||||
@ -252,7 +250,7 @@ const SubscriptionPaymentButtons = ({
|
|||||||
showToast('error', 'NWC', `An error occurred: ${error.message}`);
|
showToast('error', 'NWC', `An error occurred: ${error.message}`);
|
||||||
if (onError) onError(error);
|
if (onError) onError(error);
|
||||||
} finally {
|
} finally {
|
||||||
setIsProcessing(false);
|
if (setIsProcessing) setIsProcessing(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -127,7 +127,7 @@ export default function CourseDetails({
|
|||||||
if (session?.user && session.user?.role?.subscribed && decryptionPerformed) {
|
if (session?.user && session.user?.role?.subscribed && decryptionPerformed) {
|
||||||
return (
|
return (
|
||||||
<GenericButton
|
<GenericButton
|
||||||
tooltipOptions={{ position: 'top' }}
|
tooltipOptions={{ position: 'right' }}
|
||||||
tooltip={`You are subscribed so you can access all paid content`}
|
tooltip={`You are subscribed so you can access all paid content`}
|
||||||
icon="pi pi-check"
|
icon="pi pi-check"
|
||||||
label="Subscribed"
|
label="Subscribed"
|
||||||
@ -154,7 +154,7 @@ export default function CourseDetails({
|
|||||||
outlined
|
outlined
|
||||||
size="small"
|
size="small"
|
||||||
tooltip={`You paid ${processedEvent.price} sats to access this course (or potentially less if a discount was applied)`}
|
tooltip={`You paid ${processedEvent.price} sats to access this course (or potentially less if a discount was applied)`}
|
||||||
tooltipOptions={{ position: 'top' }}
|
tooltipOptions={{ position: 'right' }}
|
||||||
className="cursor-default hover:opacity-100 hover:bg-transparent focus:ring-0"
|
className="cursor-default hover:opacity-100 hover:bg-transparent focus:ring-0"
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -163,7 +163,7 @@ export default function CourseDetails({
|
|||||||
if (paidCourse && author && processedEvent?.pubkey === session?.user?.pubkey) {
|
if (paidCourse && author && processedEvent?.pubkey === session?.user?.pubkey) {
|
||||||
return (
|
return (
|
||||||
<GenericButton
|
<GenericButton
|
||||||
tooltipOptions={{ position: 'top' }}
|
tooltipOptions={{ position: 'right' }}
|
||||||
tooltip={`You created this paid course, users must pay ${processedEvent.price} sats to access it`}
|
tooltip={`You created this paid course, users must pay ${processedEvent.price} sats to access it`}
|
||||||
icon="pi pi-check"
|
icon="pi pi-check"
|
||||||
label={`Price ${processedEvent.price} sats`}
|
label={`Price ${processedEvent.price} sats`}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user