webln saves at least *double kazoo*

This commit is contained in:
k00b 2024-10-23 17:17:35 -05:00
parent 48640cbed6
commit 2bdbb433df
9 changed files with 59 additions and 64 deletions

View File

@ -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 (

View File

@ -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 }
}

View File

@ -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)
}
}

View File

@ -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 () {
<CenterLayout>
<h2 className='pb-2'>{wallet?.def.card.title}</h2>
<h6 className='text-muted text-center pb-3'><Text>{wallet?.def.card.subtitle}</Text></h6>
{wallet?.canSend && wallet?.hasConfig > 0 && <WalletSecurityBanner />}
{canSend(wallet) && <WalletSecurityBanner />}
<Form
initial={initial}
enableReinitialize
@ -56,7 +58,7 @@ export default function WalletSettings () {
values.enabled = true
}
await wallet.save(values)
await save(values, true)
toaster.success('saved settings')
router.push('/settings/wallets')
@ -82,7 +84,7 @@ export default function WalletSettings () {
<WalletButtonBar
wallet={wallet} onDelete={async () => {
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 = {

View File

@ -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)

View File

@ -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

View File

@ -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) {

View File

@ -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 (
<WalletsContext.Provider value={wallets}>
<WalletsContext.Provider value={{ wallets, reloadLocalWallets }}>
{children}
</WalletsContext.Provider>
)
@ -96,7 +86,7 @@ export function useWallets () {
}
export function useWallet (name) {
const wallets = useWallets()
const { wallets } = useWallets()
const wallet = useMemo(() => {
if (name) {

View File

@ -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