refactor webln

This commit is contained in:
ekzyis 2024-06-03 17:41:15 -05:00
parent 3d3dc52cec
commit bb8c1ccffc
4 changed files with 148 additions and 17 deletions

View File

@ -2,6 +2,7 @@ import { createContext, useCallback, useContext, useEffect, useMemo, useState }
import { LNbitsProvider, useLNbits } from './lnbits' import { LNbitsProvider, useLNbits } from './lnbits'
import { NWCProvider, useNWC } from './nwc' import { NWCProvider, useNWC } from './nwc'
import { LNCProvider, useLNC } from './lnc' import { LNCProvider, useLNC } from './lnc'
import lnbits from './lnbits2'
const WebLNContext = createContext({}) const WebLNContext = createContext({})

View File

@ -0,0 +1,22 @@
import { createContext, useContext, useMemo, useState } from 'react'
import lnbits from './lnbits2'
const storageKey = 'webln:providers'
const WebLNContext = createContext({})
export function useWebLN () {
const { provider } = useContext(WebLNContext)
return provider
}
export function RawWebLNProvider ({ children }) {
const [provider, setProvider] = useState()
const value = useMemo(() => ({ provider, setProvider }), [])
return (
<WebLNContext.Provider value={value}>
{children}
</WebLNContext.Provider>
)
}

View File

@ -67,8 +67,7 @@ const getPayment = async (baseUrl, adminKey, paymentHash) => {
export function LNbitsProvider ({ children }) { export function LNbitsProvider ({ children }) {
const me = useMe() const me = useMe()
const [url, setUrl] = useState('') const [config, setConfig] = useState({})
const [adminKey, setAdminKey] = useState('')
const [status, setStatus] = useState() const [status, setStatus] = useState()
const { logger } = useWalletLogger(Wallet.LNbits) const { logger } = useWalletLogger(Wallet.LNbits)
@ -78,7 +77,7 @@ export function LNbitsProvider ({ children }) {
} }
const getInfo = useCallback(async () => { const getInfo = useCallback(async () => {
const response = await getWallet(url, adminKey) const response = await getWallet(config.url, config.adminKey)
return { return {
node: { node: {
alias: response.name, alias: response.name,
@ -92,9 +91,11 @@ export function LNbitsProvider ({ children }) {
version: '1.0', version: '1.0',
supports: ['lightning'] supports: ['lightning']
} }
}, [url, adminKey]) }, [config])
const sendPayment = useCallback(async (bolt11) => { const sendPayment = useCallback(async (bolt11) => {
const { url, adminKey } = config
const hash = bolt11Tags(bolt11).payment_hash const hash = bolt11Tags(bolt11).payment_hash
logger.info('sending payment:', `payment_hash=${hash}`) logger.info('sending payment:', `payment_hash=${hash}`)
@ -111,7 +112,7 @@ export function LNbitsProvider ({ children }) {
logger.error('payment failed:', `payment_hash=${hash}`, err.message || err.toString?.()) logger.error('payment failed:', `payment_hash=${hash}`, err.message || err.toString?.())
throw err throw err
} }
}, [logger, url, adminKey]) }, [logger, config])
const loadConfig = useCallback(async () => { const loadConfig = useCallback(async () => {
let configStr = window.localStorage.getItem(storageKey) let configStr = window.localStorage.getItem(storageKey)
@ -129,20 +130,17 @@ export function LNbitsProvider ({ children }) {
} }
const config = JSON.parse(configStr) const config = JSON.parse(configStr)
setConfig(config)
const { url, adminKey } = config
setUrl(url)
setAdminKey(adminKey)
logger.info( logger.info(
'loaded wallet config: ' + 'loaded wallet config: ' +
'adminKey=****** ' + 'adminKey=****** ' +
`url=${url}`) `url=${config.url}`)
try { try {
// validate config by trying to fetch wallet // validate config by trying to fetch wallet
logger.info('trying to fetch wallet') logger.info('trying to fetch wallet')
await getWallet(url, adminKey) await getWallet(config.url, config.adminKey)
logger.ok('wallet found') logger.ok('wallet found')
setStatus(Status.Enabled) setStatus(Status.Enabled)
logger.ok('wallet enabled') logger.ok('wallet enabled')
@ -156,8 +154,7 @@ export function LNbitsProvider ({ children }) {
const saveConfig = useCallback(async (config) => { const saveConfig = useCallback(async (config) => {
// immediately store config so it's not lost even if config is invalid // immediately store config so it's not lost even if config is invalid
setUrl(config.url) setConfig(config)
setAdminKey(config.adminKey)
// XXX This is insecure, XSS vulns could lead to loss of funds! // XXX This is insecure, XSS vulns could lead to loss of funds!
// -> check how mutiny encrypts their wallet and/or check if we can leverage web workers // -> check how mutiny encrypts their wallet and/or check if we can leverage web workers
@ -186,8 +183,7 @@ export function LNbitsProvider ({ children }) {
const clearConfig = useCallback(() => { const clearConfig = useCallback(() => {
window.localStorage.removeItem(storageKey) window.localStorage.removeItem(storageKey)
setUrl('') setConfig({})
setAdminKey('')
setStatus(undefined) setStatus(undefined)
}, []) }, [])
@ -196,8 +192,8 @@ export function LNbitsProvider ({ children }) {
}, []) }, [])
const value = useMemo( const value = useMemo(
() => ({ name: 'LNbits', url, adminKey, status, saveConfig, clearConfig, getInfo, sendPayment }), () => ({ name: 'LNbits', ...config, status, saveConfig, clearConfig, getInfo, sendPayment }),
[url, adminKey, status, saveConfig, clearConfig, getInfo, sendPayment]) [config, status, saveConfig, clearConfig, getInfo, sendPayment])
return ( return (
<LNbitsContext.Provider value={value}> <LNbitsContext.Provider value={value}>
{children} {children}

112
components/webln/lnbits2.js Normal file
View File

@ -0,0 +1,112 @@
import { bolt11Tags } from '@/lib/bolt11'
export const name = 'LNbits'
let config, logger
export function setConfig (_config) {
config = _config
}
export function setLogger (_logger) {
logger = _logger
}
export async function getInfo () {
const response = await getWallet(config.url, config.adminKey)
return {
node: {
alias: response.name,
pubkey: ''
},
methods: [
'getInfo',
'getBalance',
'sendPayment'
],
version: '1.0',
supports: ['lightning']
}
}
export async function sendPayment (bolt11) {
const { url, adminKey } = config
const hash = bolt11Tags(bolt11).payment_hash
logger.info('sending payment:', `payment_hash=${hash}`)
try {
const response = await postPayment(url, adminKey, bolt11)
const checkResponse = await getPayment(url, adminKey, response.payment_hash)
if (!checkResponse.preimage) {
throw new Error('No preimage')
}
const preimage = checkResponse.preimage
logger.ok('payment successful:', `payment_hash=${hash}`, `preimage=${preimage}`)
return { preimage }
} catch (err) {
logger.error('payment failed:', `payment_hash=${hash}`, err.message || err.toString?.())
throw err
}
}
const getWallet = async (baseUrl, adminKey) => {
const url = baseUrl.replace(/\/+$/, '')
const path = '/api/v1/wallet'
const headers = new Headers()
headers.append('Accept', 'application/json')
headers.append('Content-Type', 'application/json')
headers.append('X-Api-Key', adminKey)
const res = await fetch(url + path, { method: 'GET', headers })
if (!res.ok) {
const errBody = await res.json()
throw new Error(errBody.detail)
}
const wallet = await res.json()
return wallet
}
async function postPayment (baseUrl, adminKey, bolt11) {
const url = baseUrl.replace(/\/+$/, '')
const path = '/api/v1/payments'
const headers = new Headers()
headers.append('Accept', 'application/json')
headers.append('Content-Type', 'application/json')
headers.append('X-Api-Key', adminKey)
const body = JSON.stringify({ bolt11, out: true })
const res = await fetch(url + path, { method: 'POST', headers, body })
if (!res.ok) {
const errBody = await res.json()
throw new Error(errBody.detail)
}
const payment = await res.json()
return payment
}
async function getPayment (baseUrl, adminKey, paymentHash) {
const url = baseUrl.replace(/\/+$/, '')
const path = `/api/v1/payments/${paymentHash}`
const headers = new Headers()
headers.append('Accept', 'application/json')
headers.append('Content-Type', 'application/json')
headers.append('X-Api-Key', adminKey)
const res = await fetch(url + path, { method: 'GET', headers })
if (!res.ok) {
const errBody = await res.json()
throw new Error(errBody.detail)
}
const payment = await res.json()
return payment
}