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 request = index.getAll()
request.onsuccess = () => {
const logs = request.result
let logs = request.result
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
return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
})
@ -254,16 +259,16 @@ export const WalletLoggerProvider = ({ children }) => {
}, [saveLog])
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 } })
}
if (!walletName || !wallet.server) {
if (!wallet || wallet.canPay) {
const tx = idb.current.transaction(idbStoreName, 'readwrite')
const objectStore = tx.objectStore(idbStoreName)
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) {
const cursor = event.target.result
if (cursor) {
@ -271,7 +276,7 @@ export const WalletLoggerProvider = ({ children }) => {
cursor.continue()
} else {
// 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 log = useCallback(level => message => {
if (!wallet) {
console.error('cannot log: no wallet set')
return
}
// TODO:
// also send this to us if diagnostics was enabled,
// very similar to how the service worker logger works.
appendLog(walletName, level, message)
console[level !== 'error' ? 'info' : 'error'](`[${walletName}]`, message)
}, [appendLog, walletName])
appendLog(wallet.name, level, message)
console[level !== 'error' ? 'info' : 'error'](`[${wallet.name}]`, message)
}, [appendLog, wallet?.name])
const logger = useMemo(() => ({
ok: (...message) => log('ok')(message.join(' ')),
info: (...message) => log('info')(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 }
}
export function useWalletLogs (walletName) {
export function useWalletLogs (wallet) {
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 { useWalletLogger } from '@/components/wallet-logger'
import { SSR } from '@/lib/constants'
import { bolt11Tags } from '@/lib/bolt11'
// wallet definitions
export const WALLET_DEFS = [
@ -18,25 +19,45 @@ export const Status = {
export function useWallet (name) {
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 [config, saveConfig, clearConfig] = useLocalState(storageKey)
const isConfigured = !!config
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])
const validate = useCallback(async (values) => {
return await wallet.validate({ logger, ...values })
}, [logger])
try {
// validate should log custom INFO and OK message
return await wallet.validate({ logger, ...values })
} catch (err) {
const message = err.message || err.toString?.()
logger.error(message)
throw err
}
}, [wallet, logger])
const enable = useCallback(() => {
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 {
...wallet,
@ -46,15 +67,22 @@ export function useWallet (name) {
saveConfig,
clearConfig,
enable,
isConfigured,
status: config?.enabled ? Status.Enabled : Status.Initialized
disable,
isConfigured: !!config,
status: config?.enabled ? Status.Enabled : Status.Initialized,
logger
}
}
export function getWalletByName (name, me) {
return name
? WALLET_DEFS.find(def => def.name === name)
: WALLET_DEFS.find(def => {
return WALLET_DEFS.find(def => def.name === name)
}
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 config = SSR ? null : JSON.parse(window?.localStorage.getItem(key))
return config?.enabled
@ -70,6 +98,7 @@ function getStorageKey (name, me) {
}
function enableWallet (name, me) {
// mark all wallets as disabled except the one to enable
for (const walletDef of WALLET_DEFS) {
const toEnable = walletDef.name === name
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 canPay = true
export const canReceive = false
export const fields = [
{
name: 'url',
@ -25,7 +26,9 @@ export async function validate ({ logger, ...config }) {
}
async function getInfo ({ logger, ...config }) {
logger.info('trying to fetch wallet')
const response = await getWallet(config.url, config.adminKey)
logger.ok('wallet found')
return {
node: {
alias: response.name,
@ -41,27 +44,18 @@ async function getInfo ({ logger, ...config }) {
}
}
export async function sendPayment ({ bolt11, config, logger }) {
export async function sendPayment ({ bolt11, config }) {
const { url, adminKey } = config
const hash = bolt11Tags(bolt11).payment_hash
logger.info('sending payment:', `payment_hash=${hash}`)
const response = await postPayment(url, adminKey, bolt11)
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 checkResponse = await getPayment(url, adminKey, response.payment_hash)
if (!checkResponse.preimage) {
throw new Error('No preimage')
}
const preimage = checkResponse.preimage
return { preimage }
}
async function getWallet (baseUrl, adminKey) {

View File

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