import { createContext, useContext, useEffect, useState } from 'react'
import { LNbitsProvider, useLNbits } from './lnbits'
import { NWCProvider, useNWC } from './nwc'
import { useToast } from '../toast'
import { gql, useMutation } from '@apollo/client'
const WebLNContext = createContext({})
const storageKey = 'webln:providers'
const paymentMethodHook = (methods, { name, enabled }) => {
let newMethods
if (enabled) {
newMethods = methods.includes(name) ? methods : [...methods, name]
} else {
newMethods = methods.filter(m => m !== name)
}
savePaymentMethods(newMethods)
return newMethods
}
const savePaymentMethods = (methods) => {
window.localStorage.setItem(storageKey, JSON.stringify(methods))
}
function RawWebLNProvider ({ children }) {
// LNbits should only be used during development
// since it gives full wallet access on XSS
const lnbits = useLNbits()
const nwc = useNWC()
const providers = [lnbits, nwc]
// TODO: Order of payment methods depends on user preference.
// Payment method at index 0 should be default,
// if that one fails we try the remaining ones in order as fallbacks.
// We should be able to implement this via dragging of cards.
// This list should then match the order in which the (payment) cards are rendered.
// eslint-disable-next-line no-unused-vars
const [paymentMethods, setPaymentMethods] = useState([])
const loadPaymentMethods = () => {
const methods = window.localStorage.getItem(storageKey)
if (!methods) return
setPaymentMethods(JSON.parse(methods))
}
useEffect(loadPaymentMethods, [])
const toaster = useToast()
const [cancelInvoice] = useMutation(gql`
mutation cancelInvoice($hash: String!, $hmac: String!) {
cancelInvoice(hash: $hash, hmac: $hmac) {
id
}
}
`)
useEffect(() => {
setPaymentMethods(methods => paymentMethodHook(methods, nwc))
if (!nwc.enabled) nwc.setIsDefault(false)
}, [nwc.enabled])
useEffect(() => {
setPaymentMethods(methods => paymentMethodHook(methods, lnbits))
if (!lnbits.enabled) lnbits.setIsDefault(false)
}, [lnbits.enabled])
const setDefaultPaymentMethod = (provider) => {
for (const p of providers) {
if (p.name !== provider.name) {
p.setIsDefault(false)
}
}
}
useEffect(() => {
if (nwc.isDefault) setDefaultPaymentMethod(nwc)
}, [nwc.isDefault])
useEffect(() => {
if (lnbits.isDefault) setDefaultPaymentMethod(lnbits)
}, [lnbits.isDefault])
// TODO: implement numeric provider priority using paymentMethods list
// when we have more than two providers for sending
let provider = providers.filter(p => p.enabled && p.isDefault)[0]
if (!provider && providers.length > 0) {
// if no provider is the default, pick the first one and use that one as the default
provider = providers.filter(p => p.enabled)[0]
if (provider) {
provider.setIsDefault(true)
}
}
const sendPaymentWithToast = function ({ bolt11, hash, hmac }) {
let canceled = false
let removeToast = toaster.warning('payment pending', {
autohide: false,
onCancel: async () => {
try {
await cancelInvoice({ variables: { hash, hmac } })
canceled = true
toaster.warning('payment canceled')
removeToast = undefined
} catch (err) {
toaster.danger('failed to cancel payment')
}
}
})
return provider.sendPayment(bolt11)
.then(({ preimage }) => {
removeToast?.()
toaster.success('payment successful')
return { preimage }
}).catch((err) => {
if (canceled) return
removeToast?.()
const reason = err?.message?.toString().toLowerCase() || 'unknown reason'
toaster.danger(`payment failed: ${reason}`)
throw err
})
}
return (
{children}
)
}
export function WebLNProvider ({ children }) {
return (
{children}
)
}
export function useWebLN () {
return useContext(WebLNContext)
}