From 2bdbb433df8c36d848d00a6a423a5c5d2df4eb8f Mon Sep 17 00:00:00 2001 From: k00b Date: Wed, 23 Oct 2024 17:17:35 -0500 Subject: [PATCH] webln saves at least *double kazoo* --- components/nav/common.js | 2 +- components/wallet-logger.js | 28 ++++++++++++------------ lib/validate.js | 6 ++--- pages/settings/wallets/[wallet].js | 12 +++++----- pages/settings/wallets/index.js | 2 +- wallets/common.js | 6 ++--- wallets/config.js | 35 +++++++++++++++++------------- wallets/index.js | 28 ++++++++---------------- wallets/server.js | 4 ++-- 9 files changed, 59 insertions(+), 64 deletions(-) diff --git a/components/nav/common.js b/components/nav/common.js index 0f0f1763..9574c50d 100644 --- a/components/nav/common.js +++ b/components/nav/common.js @@ -263,7 +263,7 @@ export default function LoginButton () { function LogoutObstacle ({ onClose }) { const { registration: swRegistration, togglePushSubscription } = useServiceWorker() - const wallets = useWallets() + const { wallets } = useWallets() const { multiAuthSignout } = useAccounts() return ( diff --git a/components/wallet-logger.js b/components/wallet-logger.js index 19e23f57..ca291c97 100644 --- a/components/wallet-logger.js +++ b/components/wallet-logger.js @@ -132,7 +132,7 @@ export function useWalletLogger (wallet, setLogs) { const deleteLogs = useCallback(async (wallet, options) => { if ((!wallet || wallet.def.walletType) && !options?.clientOnly) { - await deleteServerWalletLogs({ variables: { wallet: wallet?.walletType } }) + await deleteServerWalletLogs({ variables: { wallet: wallet?.def.walletType } }) } if (!wallet || wallet.sendPayment) { try { @@ -163,13 +163,13 @@ export function useWalletLogger (wallet, setLogs) { ok: (...message) => log('ok')(message.join(' ')), info: (...message) => log('info')(message.join(' ')), error: (...message) => log('error')(message.join(' ')) - }), [log, wallet?.name]) + }), [log]) return { logger, deleteLogs } } -function tag (wallet) { - return wallet?.shortName || wallet?.name +function tag (walletDef) { + return walletDef.shortName || walletDef.name } export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) { @@ -183,24 +183,24 @@ export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) { const { getPage, error, notSupported } = useWalletLogDB() const [getWalletLogs] = useLazyQuery(WALLET_LOGS, SSR ? {} : { fetchPolicy: 'cache-and-network' }) - const loadLogsPage = useCallback(async (page, pageSize, wallet) => { + const loadLogsPage = useCallback(async (page, pageSize, walletDef) => { try { let result = { data: [], hasMore: false } if (notSupported) { console.log('cannot get client wallet logs: indexeddb not supported') } else { - const indexName = wallet ? 'wallet_ts' : 'ts' - const query = wallet ? window.IDBKeyRange.bound([tag(wallet), -Infinity], [tag(wallet), Infinity]) : null + const indexName = walletDef ? 'wallet_ts' : 'ts' + const query = walletDef ? window.IDBKeyRange.bound([tag(walletDef), -Infinity], [tag(walletDef), Infinity]) : null result = await getPage(page, pageSize, indexName, query, 'prev') // no walletType means we're using the local IDB - if (wallet && !wallet.def.walletType) { + if (!walletDef?.walletType) { return result } } const { data } = await getWalletLogs({ variables: { - type: wallet?.walletType, + type: walletDef.walletType, // if it client logs has more, page based on it's range from: result?.data[result.data.length - 1]?.ts && result.hasMore ? String(result.data[result.data.length - 1].ts) : null, // if we have a cursor (this isn't the first page), page based on it's range @@ -231,28 +231,28 @@ export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) { const loadMore = useCallback(async () => { if (hasMore) { setLoading(true) - const result = await loadLogsPage(page + 1, logsPerPage, wallet) + const result = await loadLogsPage(page + 1, logsPerPage, wallet?.def) setLogs(prevLogs => [...prevLogs, ...result.data]) setHasMore(result.hasMore) setTotal(result.total) setPage(prevPage => prevPage + 1) setLoading(false) } - }, [loadLogsPage, page, logsPerPage, wallet, hasMore]) + }, [loadLogsPage, page, logsPerPage, wallet?.def, hasMore]) const loadLogs = useCallback(async () => { setLoading(true) - const result = await loadLogsPage(1, logsPerPage, wallet) + const result = await loadLogsPage(1, logsPerPage, wallet?.def) setLogs(result.data) setHasMore(result.hasMore) setTotal(result.total) setPage(1) setLoading(false) - }, [wallet, loadLogsPage]) + }, [wallet?.def, loadLogsPage]) useEffect(() => { loadLogs() - }, [wallet]) + }, [wallet?.def]) return { logs, hasMore, total, loadMore, loadLogs, setLogs, loading } } diff --git a/lib/validate.js b/lib/validate.js index 7fbadd34..3ca29757 100644 --- a/lib/validate.js +++ b/lib/validate.js @@ -43,10 +43,10 @@ export async function formikValidate (validate, data) { } export async function walletValidate (wallet, data) { - if (typeof wallet.fieldValidation === 'function') { - return await formikValidate(wallet.fieldValidation, data) + if (typeof wallet.def.fieldValidation === 'function') { + return await formikValidate(wallet.def.fieldValidation, data) } else { - return await ssValidate(wallet.fieldValidation, data) + return await ssValidate(wallet.def.fieldValidation, data) } } diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js index 196dffc2..a70cefba 100644 --- a/pages/settings/wallets/[wallet].js +++ b/pages/settings/wallets/[wallet].js @@ -9,9 +9,10 @@ import { useWallet } from '@/wallets/index' import Info from '@/components/info' import Text from '@/components/text' import { AutowithdrawSettings } from '@/components/autowithdraw-shared' -import { isConfigured } from '@/wallets/common' +import { canSend, isConfigured } from '@/wallets/common' import { SSR } from '@/lib/constants' import WalletButtonBar from '@/components/wallet-buttonbar' +import { useWalletConfigurator } from '@/wallets/config' export const getServerSideProps = getGetServerSideProps({ authRequired: true }) @@ -20,6 +21,7 @@ export default function WalletSettings () { const router = useRouter() const { wallet: name } = router.query const wallet = useWallet(name) + const { save, detach } = useWalletConfigurator(wallet) const initial = wallet?.def.fields.reduce((acc, field) => { // We still need to run over all wallet fields via reduce @@ -42,7 +44,7 @@ export default function WalletSettings () {

{wallet?.def.card.title}

{wallet?.def.card.subtitle}
- {wallet?.canSend && wallet?.hasConfig > 0 && } + {canSend(wallet) && }
{ try { - await wallet?.delete() + await detach() toaster.success('saved settings') router.push('/settings/wallets') } catch (err) { @@ -101,8 +103,6 @@ export default function WalletSettings () { } function WalletFields ({ wallet }) { - console.log('wallet', wallet) - return wallet.def.fields .map(({ name, label = '', type, help, optional, editable, clientOnly, serverOnly, ...props }, i) => { const rawProps = { diff --git a/pages/settings/wallets/index.js b/pages/settings/wallets/index.js index bd9a6cad..54a50cb7 100644 --- a/pages/settings/wallets/index.js +++ b/pages/settings/wallets/index.js @@ -26,7 +26,7 @@ async function reorder (wallets, sourceIndex, targetIndex) { } export default function Wallet ({ ssrData }) { - const wallets = useWallets() + const { wallets } = useWallets() const isClient = useIsClient() const [sourceIndex, setSourceIndex] = useState(null) diff --git a/wallets/common.js b/wallets/common.js index b6416fd5..877f49ed 100644 --- a/wallets/common.js +++ b/wallets/common.js @@ -13,13 +13,13 @@ export function getWalletByType (type) { return walletDefs.find(def => def.walletType === type) } -export function getStorageKey (name, me) { +export function getStorageKey (name, userId) { let storageKey = `wallet:${name}` // WebLN has no credentials we need to scope to users // so we can use the same storage key for all users - if (me && name !== 'webln') { - storageKey = `${storageKey}:${me.id}` + if (userId && name !== 'webln') { + storageKey = `${storageKey}:${userId}` } return storageKey diff --git a/wallets/config.js b/wallets/config.js index 7e941b84..e8d51a9f 100644 --- a/wallets/config.js +++ b/wallets/config.js @@ -1,15 +1,17 @@ import { useMe } from '@/components/me' import useVault from '@/components/vault/use-vault' import { useCallback } from 'react' -import { getStorageKey, isClientField, isServerField } from './common' +import { canReceive, canSend, getStorageKey, isClientField, isServerField } from './common' import { useMutation } from '@apollo/client' import { generateMutation } from './graphql' import { REMOVE_WALLET } from '@/fragments/wallet' import { walletValidate } from '@/lib/validate' import { useWalletLogger } from '@/components/wallet-logger' +import { useWallets } from '.' export function useWalletConfigurator (wallet) { const { me } = useMe() + const { reloadLocalWallets } = useWallets() const { encrypt, isActive } = useVault() const { logger } = useWalletLogger(wallet?.def) const [upsertWallet] = useMutation(generateMutation(wallet?.def)) @@ -26,26 +28,29 @@ export function useWalletConfigurator (wallet) { }, [encrypt, isActive]) const _saveToLocal = useCallback(async (newConfig) => { - window.localStorage.setItem(getStorageKey(wallet.name, me), JSON.stringify(newConfig)) - }, [me, wallet.name]) + window.localStorage.setItem(getStorageKey(wallet.def.name, me?.id), JSON.stringify(newConfig)) + reloadLocalWallets() + }, [me?.id, wallet.def.name, reloadLocalWallets]) const save = useCallback(async (newConfig, validate = true) => { let clientConfig = extractClientConfig(wallet.def.fields, newConfig) let serverConfig = extractServerConfig(wallet.def.fields, newConfig) if (validate) { - if (clientConfig) { + if (canSend(wallet)) { let transformedConfig = await walletValidate(wallet, clientConfig) if (transformedConfig) { clientConfig = Object.assign(clientConfig, transformedConfig) } - transformedConfig = await wallet.def.testSendPayment(clientConfig, { me, logger }) - if (transformedConfig) { - clientConfig = Object.assign(clientConfig, transformedConfig) + if (wallet.def.testSendPayment) { + transformedConfig = await wallet.def.testSendPayment(clientConfig, { me, logger }) + if (transformedConfig) { + clientConfig = Object.assign(clientConfig, transformedConfig) + } } } - if (serverConfig) { + if (canReceive(wallet)) { const transformedConfig = await walletValidate(wallet, serverConfig) if (transformedConfig) { serverConfig = Object.assign(serverConfig, transformedConfig) @@ -57,14 +62,14 @@ export function useWalletConfigurator (wallet) { if (isActive) { await _saveToServer(serverConfig, clientConfig) } else { - if (clientConfig) { + if (canSend(wallet)) { await _saveToLocal(clientConfig) } - if (serverConfig) { + if (canReceive(wallet)) { await _saveToServer(serverConfig) } } - }, [wallet.def, encrypt, isActive]) + }, [wallet, encrypt, isActive]) const _detachFromServer = useCallback(async () => { await removeWallet({ variables: { id: wallet.config.id } }) @@ -72,8 +77,8 @@ export function useWalletConfigurator (wallet) { const _detachFromLocal = useCallback(async () => { // if vault is not active and has a client config, delete from local storage - window.localStorage.removeItem(getStorageKey(wallet.name, me)) - }, [me, wallet.name]) + window.localStorage.removeItem(getStorageKey(wallet.def.name, me?.id)) + }, [me?.id, wallet.def.name]) const detach = useCallback(async () => { if (isActive) { @@ -87,7 +92,7 @@ export function useWalletConfigurator (wallet) { } }, [isActive, _detachFromServer, _detachFromLocal]) - return [save, detach] + return { save, detach } } function extractConfig (fields, config, client, includeMeta = true) { @@ -111,7 +116,7 @@ function extractConfig (fields, config, client, includeMeta = true) { } function extractClientConfig (fields, config) { - return extractConfig(fields, config, true, false) + return extractConfig(fields, config, true, true) } function extractServerConfig (fields, config) { diff --git a/wallets/index.js b/wallets/index.js index 95da558d..8b1ff6b2 100644 --- a/wallets/index.js +++ b/wallets/index.js @@ -1,6 +1,6 @@ import { useMe } from '@/components/me' import { WALLETS } from '@/fragments/wallet' -import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants' +import { LONG_POLL_INTERVAL, SSR } from '@/lib/constants' import { useQuery } from '@apollo/client' import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' import { getStorageKey, getWalletByType, Status, walletPrioritySort, canSend } from './common' @@ -21,44 +21,34 @@ function useLocalWallets () { // form wallets into a list of { config, def } const wallets = walletDefs.map(w => { try { - const config = window.localStorage.getItem(getStorageKey(w.name, me)) + const config = window.localStorage.getItem(getStorageKey(w.name, me?.id)) return { def: w, config: JSON.parse(config) } } catch (e) { return null } }).filter(Boolean) setWallets(wallets) - }, [me, setWallets]) + }, [me?.id, setWallets]) - // watch for changes to local storage useEffect(() => { loadWallets() - // reload wallets if local storage to wallet changes - const handler = (event) => { - if (event.key.startsWith('wallet:')) { - loadWallets() - } - } - window.addEventListener('storage', handler) - return () => window.removeEventListener('storage', handler) }, [loadWallets]) - return wallets + return { wallets, reloadLocalWallets: loadWallets } } const walletDefsOnly = walletDefs.map(w => ({ def: w, config: {} })) export function WalletsProvider ({ children }) { - const { me } = useMe() const { decrypt } = useVault() - const localWallets = useLocalWallets() + const { wallets: localWallets, reloadLocalWallets } = useLocalWallets() // TODO: instead of polling, this should only be called when the vault key is updated // or a denormalized field on the user 'vaultUpdatedAt' is changed const { data } = useQuery(WALLETS, { - pollInterval: NORMAL_POLL_INTERVAL, + pollInterval: LONG_POLL_INTERVAL, nextFetchPolicy: 'cache-and-network', - skip: !me?.id || SSR + skip: SSR }) const wallets = useMemo(() => { @@ -85,7 +75,7 @@ export function WalletsProvider ({ children }) { // provides priority sorted wallets to children return ( - + {children} ) @@ -96,7 +86,7 @@ export function useWallets () { } export function useWallet (name) { - const wallets = useWallets() + const { wallets } = useWallets() const wallet = useMemo(() => { if (name) { diff --git a/wallets/server.js b/wallets/server.js index 12dc2ce0..09624ecb 100644 --- a/wallets/server.js +++ b/wallets/server.js @@ -39,14 +39,14 @@ export async function createInvoice (userId, { msats, description, descriptionHa msats = toPositiveNumber(msats) for (const wallet of wallets) { - const w = walletDefs.find(w => w.walletType === wallet.type) + const w = walletDefs.find(w => w.walletType === wallet.def.walletType) try { const { walletType, walletField, createInvoice } = w const walletFull = await models.wallet.findFirst({ where: { userId, - type: walletType + type: wallet.def.walletType }, include: { [walletField]: true