refactor webln

This commit is contained in:
ekzyis 2024-06-03 17:41:15 -05:00
parent 8329da1f56
commit 0f553b1aef
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 { NWCProvider, useNWC } from './nwc'
import { LNCProvider, useLNC } from './lnc'
import lnbits from './lnbits2'
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 }) {
const me = useMe()
const [url, setUrl] = useState('')
const [adminKey, setAdminKey] = useState('')
const [config, setConfig] = useState({})
const [status, setStatus] = useState()
const { logger } = useWalletLogger(Wallet.LNbits)
@ -78,7 +77,7 @@ export function LNbitsProvider ({ children }) {
}
const getInfo = useCallback(async () => {
const response = await getWallet(url, adminKey)
const response = await getWallet(config.url, config.adminKey)
return {
node: {
alias: response.name,
@ -92,9 +91,11 @@ export function LNbitsProvider ({ children }) {
version: '1.0',
supports: ['lightning']
}
}, [url, adminKey])
}, [config])
const sendPayment = useCallback(async (bolt11) => {
const { url, adminKey } = config
const hash = bolt11Tags(bolt11).payment_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?.())
throw err
}
}, [logger, url, adminKey])
}, [logger, config])
const loadConfig = useCallback(async () => {
let configStr = window.localStorage.getItem(storageKey)
@ -129,20 +130,17 @@ export function LNbitsProvider ({ children }) {
}
const config = JSON.parse(configStr)
const { url, adminKey } = config
setUrl(url)
setAdminKey(adminKey)
setConfig(config)
logger.info(
'loaded wallet config: ' +
'adminKey=****** ' +
`url=${url}`)
`url=${config.url}`)
try {
// validate config by trying to fetch wallet
logger.info('trying to fetch wallet')
await getWallet(url, adminKey)
await getWallet(config.url, config.adminKey)
logger.ok('wallet found')
setStatus(Status.Enabled)
logger.ok('wallet enabled')
@ -156,8 +154,7 @@ export function LNbitsProvider ({ children }) {
const saveConfig = useCallback(async (config) => {
// immediately store config so it's not lost even if config is invalid
setUrl(config.url)
setAdminKey(config.adminKey)
setConfig(config)
// 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
@ -186,8 +183,7 @@ export function LNbitsProvider ({ children }) {
const clearConfig = useCallback(() => {
window.localStorage.removeItem(storageKey)
setUrl('')
setAdminKey('')
setConfig({})
setStatus(undefined)
}, [])
@ -196,8 +192,8 @@ export function LNbitsProvider ({ children }) {
}, [])
const value = useMemo(
() => ({ name: 'LNbits', url, adminKey, status, saveConfig, clearConfig, getInfo, sendPayment }),
[url, adminKey, status, saveConfig, clearConfig, getInfo, sendPayment])
() => ({ name: 'LNbits', ...config, status, saveConfig, clearConfig, getInfo, sendPayment }),
[config, status, saveConfig, clearConfig, getInfo, sendPayment])
return (
<LNbitsContext.Provider value={value}>
{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
}