From 6ac8785c5112adc902782223637a974071f5ba44 Mon Sep 17 00:00:00 2001 From: ekzyis Date: Thu, 20 Jun 2024 19:56:37 +0200 Subject: [PATCH] Update wallet logging + other stuff * add canPay and canSend to wallet definition * rename 'default payment method' to 'enabled' and add enable + disable method --- components/wallet-logger.js | 37 +++++++++++------- components/wallet/index.js | 63 ++++++++++++++++++++++++------ components/wallet/lnbits.js | 32 ++++++--------- pages/settings/wallets/[wallet].js | 20 ++++++---- pages/wallet/logs.js | 2 +- 5 files changed, 99 insertions(+), 55 deletions(-) diff --git a/components/wallet-logger.js b/components/wallet-logger.js index 27993913..c296dd1a 100644 --- a/components/wallet-logger.js +++ b/components/wallet-logger.js @@ -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) } diff --git a/components/wallet/index.js b/components/wallet/index.js index 321a14ea..c1e7f10c 100644 --- a/components/wallet/index.js +++ b/components/wallet/index.js @@ -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)) +} diff --git a/components/wallet/lnbits.js b/components/wallet/lnbits.js index af3aeb6a..186cb142 100644 --- a/components/wallet/lnbits.js +++ b/components/wallet/lnbits.js @@ -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) { diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js index 8c82a35c..f7b9d857 100644 --- a/pages/settings/wallets/[wallet].js +++ b/pages/settings/wallets/[wallet].js @@ -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 () {
{ + 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) } }} > { @@ -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) } }} /> diff --git a/pages/wallet/logs.js b/pages/wallet/logs.js index 86b540b5..e207a32c 100644 --- a/pages/wallet/logs.js +++ b/pages/wallet/logs.js @@ -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 })