* 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).
167 lines
4.6 KiB
JavaScript
167 lines
4.6 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)
|
|
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)
|
|
}
|