stacker.news/components/webln/lnbits.js

168 lines
4.7 KiB
JavaScript

import { createContext, useCallback, useContext, useEffect, useState } from 'react'
// Reference: https://github.com/getAlby/bitcoin-connect/blob/v3.2.0-alpha/src/connectors/LnbitsConnector.ts
const LNbitsContext = createContext()
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
}
const postPayment = async (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
}
const getPayment = async (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
}
export function LNbitsProvider ({ children }) {
const [url, setUrl] = useState('')
const [adminKey, setAdminKey] = useState('')
const [enabled, setEnabled] = useState()
const [initialized, setInitialized] = useState(false)
const name = 'LNbits'
const storageKey = 'webln:provider:lnbits'
const getInfo = useCallback(async () => {
const response = await getWallet(url, adminKey)
return {
node: {
alias: response.name,
pubkey: ''
},
methods: [
'getInfo',
'getBalance',
'sendPayment'
],
version: '1.0',
supports: ['lightning']
}
}, [url, adminKey])
const sendPayment = useCallback(async (bolt11) => {
const response = await postPayment(url, adminKey, bolt11)
const checkResponse = await getPayment(url, adminKey, response.payment_hash)
if (!checkResponse.preimage) {
throw new Error('No preimage')
}
return { preimage: checkResponse.preimage }
}, [url, adminKey])
const loadConfig = useCallback(async () => {
const configStr = window.localStorage.getItem(storageKey)
if (!configStr) {
setEnabled(undefined)
setInitialized(true)
return
}
const config = JSON.parse(configStr)
const { url, adminKey } = config
setUrl(url)
setAdminKey(adminKey)
try {
// validate config by trying to fetch wallet
await getWallet(url, adminKey)
setEnabled(true)
} catch (err) {
console.error('invalid LNbits config:', err)
setEnabled(false)
throw err
} finally {
setInitialized(true)
}
}, [])
const saveConfig = useCallback(async (config) => {
// immediately store config so it's not lost even if config is invalid
setUrl(config.url)
setAdminKey(config.adminKey)
// 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
// https://thenewstack.io/leveraging-web-workers-to-safely-store-access-tokens/
window.localStorage.setItem(storageKey, JSON.stringify(config))
try {
// validate config by trying to fetch wallet
await getWallet(config.url, config.adminKey)
} catch (err) {
console.error('invalid LNbits config:', err)
setEnabled(false)
throw err
}
setEnabled(true)
}, [])
const clearConfig = useCallback(() => {
window.localStorage.removeItem(storageKey)
setUrl('')
setAdminKey('')
setEnabled(undefined)
}, [])
useEffect(() => {
loadConfig().catch(console.error)
}, [])
const value = { name, url, adminKey, initialized, enabled, saveConfig, clearConfig, getInfo, sendPayment }
return (
<LNbitsContext.Provider value={value}>
{children}
</LNbitsContext.Provider>
)
}
export function useLNbits () {
return useContext(LNbitsContext)
}