Update wallet logging + other stuff

* add canPay and canSend to wallet definition
* rename 'default payment method' to 'enabled' and add enable + disable method
This commit is contained in:
ekzyis 2024-06-20 19:56:37 +02:00
parent a1b343ac89
commit 6ac8785c51
5 changed files with 99 additions and 55 deletions

View File

@ -228,8 +228,13 @@ export const WalletLoggerProvider = ({ children }) => {
const index = store.index('ts') const index = store.index('ts')
const request = index.getAll() const request = index.getAll()
request.onsuccess = () => { request.onsuccess = () => {
const logs = request.result let logs = request.result
setLogs((prevLogs) => { setLogs((prevLogs) => {
if (process.env.NODE_ENV !== 'production') {
// in dev mode, useEffect runs twice, so we filter out duplicates here
const existingIds = prevLogs.map(({ id }) => id)
logs = logs.filter(({ id }) => !existingIds.includes(id))
}
// sort oldest first to keep same order as logs are appended // sort oldest first to keep same order as logs are appended
return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts) return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
}) })
@ -254,16 +259,16 @@ export const WalletLoggerProvider = ({ children }) => {
}, [saveLog]) }, [saveLog])
const deleteLogs = useCallback(async (walletName) => { const deleteLogs = useCallback(async (walletName) => {
const wallet = getWalletByName(walletName, me) const wallet = walletName ? getWalletByName(walletName, me) : null
if (!walletName || wallet.server) { if (!wallet || wallet.canReceive) {
await deleteServerWalletLogs({ variables: { wallet: wallet?.type } }) await deleteServerWalletLogs({ variables: { wallet: wallet?.type } })
} }
if (!walletName || !wallet.server) { if (!wallet || wallet.canPay) {
const tx = idb.current.transaction(idbStoreName, 'readwrite') const tx = idb.current.transaction(idbStoreName, 'readwrite')
const objectStore = tx.objectStore(idbStoreName) const objectStore = tx.objectStore(idbStoreName)
const idx = objectStore.index('wallet_ts') const idx = objectStore.index('wallet_ts')
const request = walletName ? idx.openCursor(window.IDBKeyRange.bound([walletName, -Infinity], [walletName, Infinity])) : idx.openCursor() const request = wallet ? idx.openCursor(window.IDBKeyRange.bound([wallet.name, -Infinity], [wallet.name, Infinity])) : idx.openCursor()
request.onsuccess = function (event) { request.onsuccess = function (event) {
const cursor = event.target.result const cursor = event.target.result
if (cursor) { if (cursor) {
@ -271,7 +276,7 @@ export const WalletLoggerProvider = ({ children }) => {
cursor.continue() cursor.continue()
} else { } else {
// finished // finished
setLogs((logs) => logs.filter(l => walletName ? l.wallet !== walletName : false)) setLogs((logs) => logs.filter(l => wallet ? l.wallet !== wallet.name : false))
} }
} }
} }
@ -286,29 +291,33 @@ export const WalletLoggerProvider = ({ children }) => {
) )
} }
export function useWalletLogger (walletName) { export function useWalletLogger (wallet) {
const { appendLog, deleteLogs: innerDeleteLogs } = useContext(WalletLoggerContext) const { appendLog, deleteLogs: innerDeleteLogs } = useContext(WalletLoggerContext)
const log = useCallback(level => message => { const log = useCallback(level => message => {
if (!wallet) {
console.error('cannot log: no wallet set')
return
}
// TODO: // TODO:
// also send this to us if diagnostics was enabled, // also send this to us if diagnostics was enabled,
// very similar to how the service worker logger works. // very similar to how the service worker logger works.
appendLog(walletName, level, message) appendLog(wallet.name, level, message)
console[level !== 'error' ? 'info' : 'error'](`[${walletName}]`, message) console[level !== 'error' ? 'info' : 'error'](`[${wallet.name}]`, message)
}, [appendLog, walletName]) }, [appendLog, wallet?.name])
const logger = useMemo(() => ({ const logger = useMemo(() => ({
ok: (...message) => log('ok')(message.join(' ')), ok: (...message) => log('ok')(message.join(' ')),
info: (...message) => log('info')(message.join(' ')), info: (...message) => log('info')(message.join(' ')),
error: (...message) => log('error')(message.join(' ')) error: (...message) => log('error')(message.join(' '))
}), [log, walletName]) }), [log, wallet?.name])
const deleteLogs = useCallback((w) => innerDeleteLogs(w || walletName), [innerDeleteLogs, walletName]) const deleteLogs = useCallback((w) => innerDeleteLogs(w?.name || wallet?.name), [innerDeleteLogs, wallet?.name])
return { logger, deleteLogs } return { logger, deleteLogs }
} }
export function useWalletLogs (walletName) { export function useWalletLogs (wallet) {
const logs = useContext(WalletLogsContext) const logs = useContext(WalletLogsContext)
return logs.filter(l => !walletName || l.wallet === walletName) return logs.filter(l => !wallet || l.wallet === wallet.name)
} }

View File

@ -3,6 +3,7 @@ import { useMe } from '@/components/me'
import useLocalState from '@/components/use-local-state' import useLocalState from '@/components/use-local-state'
import { useWalletLogger } from '@/components/wallet-logger' import { useWalletLogger } from '@/components/wallet-logger'
import { SSR } from '@/lib/constants' import { SSR } from '@/lib/constants'
import { bolt11Tags } from '@/lib/bolt11'
// wallet definitions // wallet definitions
export const WALLET_DEFS = [ export const WALLET_DEFS = [
@ -18,25 +19,45 @@ export const Status = {
export function useWallet (name) { export function useWallet (name) {
const me = useMe() const me = useMe()
const { logger } = useWalletLogger(name)
const wallet = getWalletByName(name, me) const wallet = name ? getWalletByName(name, me) : getEnabledWallet(me)
const { logger } = useWalletLogger(wallet)
const storageKey = getStorageKey(wallet?.name, me) const storageKey = getStorageKey(wallet?.name, me)
const [config, saveConfig, clearConfig] = useLocalState(storageKey) const [config, saveConfig, clearConfig] = useLocalState(storageKey)
const isConfigured = !!config
const sendPayment = useCallback(async (bolt11) => { const sendPayment = useCallback(async (bolt11) => {
return await wallet.sendPayment({ bolt11, config, logger }) const hash = bolt11Tags(bolt11).payment_hash
logger.info('sending payment:', `payment_hash=${hash}`)
try {
const { preimage } = await wallet.sendPayment({ bolt11, config, logger })
logger.ok('payment successful:', `payment_hash=${hash}`, `preimage=${preimage}`)
} catch (err) {
const message = err.message || err.toString?.()
logger.error('payment failed:', `payment_hash=${hash}`, message)
throw err
}
}, [wallet, config, logger]) }, [wallet, config, logger])
const validate = useCallback(async (values) => { const validate = useCallback(async (values) => {
try {
// validate should log custom INFO and OK message
return await wallet.validate({ logger, ...values }) return await wallet.validate({ logger, ...values })
}, [logger]) } catch (err) {
const message = err.message || err.toString?.()
logger.error(message)
throw err
}
}, [wallet, logger])
const enable = useCallback(() => { const enable = useCallback(() => {
enableWallet(name, me) enableWallet(name, me)
}, [name, me]) logger.ok('wallet enabled')
}, [name, me, logger])
const disable = useCallback(() => {
disableWallet(name, me)
logger.ok('wallet disabled')
}, [name, me, logger])
return { return {
...wallet, ...wallet,
@ -46,15 +67,22 @@ export function useWallet (name) {
saveConfig, saveConfig,
clearConfig, clearConfig,
enable, enable,
isConfigured, disable,
status: config?.enabled ? Status.Enabled : Status.Initialized isConfigured: !!config,
status: config?.enabled ? Status.Enabled : Status.Initialized,
logger
} }
} }
export function getWalletByName (name, me) { export function getWalletByName (name, me) {
return name return WALLET_DEFS.find(def => def.name === name)
? WALLET_DEFS.find(def => def.name === name) }
: WALLET_DEFS.find(def => {
export function getEnabledWallet (me) {
// TODO: handle multiple enabled wallets
return WALLET_DEFS
.filter(def => def.canPay)
.find(def => {
const key = getStorageKey(def.name, me) const key = getStorageKey(def.name, me)
const config = SSR ? null : JSON.parse(window?.localStorage.getItem(key)) const config = SSR ? null : JSON.parse(window?.localStorage.getItem(key))
return config?.enabled return config?.enabled
@ -70,6 +98,7 @@ function getStorageKey (name, me) {
} }
function enableWallet (name, me) { function enableWallet (name, me) {
// mark all wallets as disabled except the one to enable
for (const walletDef of WALLET_DEFS) { for (const walletDef of WALLET_DEFS) {
const toEnable = walletDef.name === name const toEnable = walletDef.name === name
const key = getStorageKey(name, me) const key = getStorageKey(name, me)
@ -80,3 +109,11 @@ function enableWallet (name, me) {
} }
} }
} }
function disableWallet (name, me) {
const key = getStorageKey(name, me)
const config = JSON.parse(window.localStorage.getItem(key))
if (!config) return
config.enabled = false
window.localStorage.setItem(key, JSON.stringify(config))
}

View File

@ -1,7 +1,8 @@
import { bolt11Tags } from '@/lib/bolt11'
export const name = 'lnbits' export const name = 'lnbits'
export const canPay = true
export const canReceive = false
export const fields = [ export const fields = [
{ {
name: 'url', name: 'url',
@ -25,7 +26,9 @@ export async function validate ({ logger, ...config }) {
} }
async function getInfo ({ logger, ...config }) { async function getInfo ({ logger, ...config }) {
logger.info('trying to fetch wallet')
const response = await getWallet(config.url, config.adminKey) const response = await getWallet(config.url, config.adminKey)
logger.ok('wallet found')
return { return {
node: { node: {
alias: response.name, alias: response.name,
@ -41,13 +44,9 @@ async function getInfo ({ logger, ...config }) {
} }
} }
export async function sendPayment ({ bolt11, config, logger }) { export async function sendPayment ({ bolt11, config }) {
const { url, adminKey } = config 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 response = await postPayment(url, adminKey, bolt11)
const checkResponse = await getPayment(url, adminKey, response.payment_hash) const checkResponse = await getPayment(url, adminKey, response.payment_hash)
@ -56,12 +55,7 @@ export async function sendPayment ({ bolt11, config, logger }) {
} }
const preimage = checkResponse.preimage const preimage = checkResponse.preimage
logger.ok('payment successful:', `payment_hash=${hash}`, `preimage=${preimage}`)
return { preimage } return { preimage }
} catch (err) {
logger.error('payment failed:', `payment_hash=${hash}`, err.message || err.toString?.())
throw err
}
} }
async function getWallet (baseUrl, adminKey) { async function getWallet (baseUrl, adminKey) {

View File

@ -7,7 +7,7 @@ import { WalletSecurityBanner } from '@/components/banners'
import { WalletLogs } from '@/components/wallet-logger' import { WalletLogs } from '@/components/wallet-logger'
import { useToast } from '@/components/toast' import { useToast } from '@/components/toast'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { useWallet } from '@/components/wallet' import { useWallet, Status } from '@/components/wallet'
export const getServerSideProps = getGetServerSideProps({ authRequired: true }) export const getServerSideProps = getGetServerSideProps({ authRequired: true })
@ -34,25 +34,27 @@ export default function WalletSettings () {
<Form <Form
initial={initial} initial={initial}
schema={lnbitsSchema} schema={lnbitsSchema}
onSubmit={async ({ isDefault, ...values }) => { onSubmit={async ({ enabled, ...values }) => {
try { try {
await wallet.validate(values) await wallet.validate(values)
wallet.saveConfig(values) wallet.saveConfig(values)
wallet.enable() if (enabled) wallet.enable()
else wallet.disable()
toaster.success('saved settings') toaster.success('saved settings')
router.push('/settings/wallets') router.push('/settings/wallets')
} catch (err) { } catch (err) {
console.error(err) console.error(err)
toaster.danger('failed to attach: ' + err.message || err.toString?.()) const message = 'failed to attach: ' + err.message || err.toString?.()
toaster.danger(message)
} }
}} }}
> >
<WalletFields wallet={wallet} /> <WalletFields wallet={wallet} />
<ClientCheckbox <ClientCheckbox
disabled={false} disabled={false}
initialValue={false} initialValue={wallet.status === Status.Enabled}
label='default payment method' label='enabled'
name='isDefault' name='enabled'
/> />
<WalletButtonBar <WalletButtonBar
wallet={wallet} onDelete={async () => { wallet={wallet} onDelete={async () => {
@ -62,7 +64,9 @@ export default function WalletSettings () {
router.push('/settings/wallets') router.push('/settings/wallets')
} catch (err) { } catch (err) {
console.error(err) console.error(err)
toaster.danger('failed to detach: ' + err.message || err.toString?.()) const message = 'failed to detach: ' + err.message || err.toString?.()
wallet.logger.error(message)
toaster.danger(message)
} }
}} }}
/> />

View File

@ -1,6 +1,6 @@
import { CenterLayout } from '@/components/layout' import { CenterLayout } from '@/components/layout'
import { getGetServerSideProps } from '@/api/ssrApollo' import { getGetServerSideProps } from '@/api/ssrApollo'
import { WalletLogs } from '@/components/wallet-logs' import { WalletLogs } from '@/components/wallet-logger'
export const getServerSideProps = getGetServerSideProps({ query: null }) export const getServerSideProps = getGetServerSideProps({ query: null })