b3d485e8c4
* Refactor setting of default providers * fixed warning about component update while rendering another component * individual providers no longer need to know if they are the default or not * default setting is now handled by WebLNContext -- the same context that returns the provider. this makes a lot more sense and is a lot easier to read * default payment checkbox is now also disabled if there is only one enabled provider or if it is the default provider * Fix order lost on page reload On page reload, the providers were synced in the order they were loaded. This means that the default payment provider setting was lost. Fixed this by syncing order to local storage and on page reload, only syncing providers when they were initialized (else the order would have been lost again).
153 lines
4.7 KiB
JavaScript
153 lines
4.7 KiB
JavaScript
import { createContext, useCallback, 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 syncProvider = (array, provider) => {
|
|
const idx = array.findIndex(({ name }) => provider.name === name)
|
|
if (idx === -1) {
|
|
// add provider to end if enabled
|
|
return provider.enabled ? [...array, provider] : array
|
|
}
|
|
return [
|
|
...array.slice(0, idx),
|
|
// remove provider if not enabled
|
|
...provider.enabled ? [provider] : [],
|
|
...array.slice(idx + 1)
|
|
]
|
|
}
|
|
|
|
const storageKey = 'webln:providers'
|
|
|
|
function RawWebLNProvider ({ children }) {
|
|
const lnbits = useLNbits()
|
|
const nwc = useNWC()
|
|
const availableProviders = [lnbits, nwc]
|
|
const [enabledProviders, setEnabledProviders] = useState([])
|
|
|
|
// restore order on page reload
|
|
useEffect(() => {
|
|
const storedOrder = window.localStorage.getItem(storageKey)
|
|
if (!storedOrder) return
|
|
const providerNames = JSON.parse(storedOrder)
|
|
setEnabledProviders(providers => {
|
|
return providerNames.map(name => {
|
|
for (const p of availableProviders) {
|
|
if (p.name === name) return p
|
|
}
|
|
console.warn(`Stored provider with name ${name} not available`)
|
|
return null
|
|
})
|
|
})
|
|
}, [])
|
|
|
|
// keep list in sync with underlying providers
|
|
useEffect(() => {
|
|
setEnabledProviders(providers => {
|
|
// Sync existing provider state with new provider state
|
|
// in the list while keeping the order they are in.
|
|
// If provider does not exist but is enabled, it is just added to the end of the list.
|
|
// This can be the case if we're syncing from a page reload
|
|
// where the providers are initially not enabled.
|
|
// If provider is no longer enabled, it is removed from the list.
|
|
const isInitialized = p => p.initialized
|
|
const newProviders = availableProviders.filter(isInitialized).reduce(syncProvider, providers)
|
|
const newOrder = newProviders.map(({ name }) => name)
|
|
window.localStorage.setItem(storageKey, JSON.stringify(newOrder))
|
|
return newProviders
|
|
})
|
|
}, [lnbits, nwc])
|
|
|
|
// sanity check
|
|
for (const p of enabledProviders) {
|
|
if (!p.enabled && p.initialized) {
|
|
console.warn('Expected provider to be enabled but is not:', p.name)
|
|
}
|
|
}
|
|
|
|
// first provider in list is the default provider
|
|
// TODO: implement fallbacks via provider priority
|
|
const provider = enabledProviders[0]
|
|
|
|
const toaster = useToast()
|
|
const [cancelInvoice] = useMutation(gql`
|
|
mutation cancelInvoice($hash: String!, $hmac: String!) {
|
|
cancelInvoice(hash: $hash, hmac: $hmac) {
|
|
id
|
|
}
|
|
}
|
|
`)
|
|
|
|
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
|
|
})
|
|
}
|
|
|
|
const setProvider = useCallback((defaultProvider) => {
|
|
// move provider to the start to set it as default
|
|
setEnabledProviders(providers => {
|
|
const idx = providers.findIndex(({ name }) => defaultProvider.name === name)
|
|
if (idx === -1) {
|
|
console.warn(`tried to set unenabled provider ${defaultProvider.name} as default`)
|
|
return providers
|
|
}
|
|
return [defaultProvider, ...providers.slice(0, idx), ...providers.slice(idx + 1)]
|
|
})
|
|
}, [setEnabledProviders])
|
|
|
|
const value = { provider: { ...provider, sendPayment: sendPaymentWithToast }, enabledProviders, setProvider }
|
|
return (
|
|
<WebLNContext.Provider value={value}>
|
|
{children}
|
|
</WebLNContext.Provider>
|
|
)
|
|
}
|
|
|
|
export function WebLNProvider ({ children }) {
|
|
return (
|
|
<LNbitsProvider>
|
|
<NWCProvider>
|
|
<RawWebLNProvider>
|
|
{children}
|
|
</RawWebLNProvider>
|
|
</NWCProvider>
|
|
</LNbitsProvider>
|
|
)
|
|
}
|
|
|
|
export function useWebLN () {
|
|
const { provider } = useContext(WebLNContext)
|
|
return provider
|
|
}
|
|
|
|
export function useWebLNConfigurator () {
|
|
return useContext(WebLNContext)
|
|
}
|