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 ( {children} ) } export function useLNbits () { return useContext(LNbitsContext) }