From 48640cbed6449623b2c02ea8ee9d50c11b37aed9 Mon Sep 17 00:00:00 2001 From: k00b Date: Wed, 23 Oct 2024 12:42:34 -0500 Subject: [PATCH] pages load *kazoo* --- api/resolvers/vault.js | 7 +-- api/resolvers/wallet.js | 3 +- api/typeDefs/wallet.js | 4 +- components/autowithdraw-shared.js | 3 +- components/device-sync.js | 37 +----------- components/item-act.js | 2 +- components/nav/common.js | 5 +- components/payment.js | 26 +-------- components/qr.js | 2 +- components/use-indexeddb.js | 4 +- components/vault/use-vault-configurator.js | 9 ++- components/wallet-buttonbar.js | 5 +- components/wallet-card.js | 18 ++---- components/wallet-logger.js | 10 +++- pages/_app.js | 6 +- pages/settings/wallets/[wallet].js | 31 +++++----- pages/settings/wallets/index.js | 68 ++++++++-------------- wallets/common.js | 64 +++++++++++++++++--- wallets/config.js | 53 ++--------------- wallets/errors.js | 22 +++++++ wallets/graphql.js | 2 +- wallets/index.js | 18 +++--- wallets/lnc/client.js | 2 +- 23 files changed, 177 insertions(+), 224 deletions(-) create mode 100644 wallets/errors.js diff --git a/api/resolvers/vault.js b/api/resolvers/vault.js index ecb8ec59..99c0486e 100644 --- a/api/resolvers/vault.js +++ b/api/resolvers/vault.js @@ -54,10 +54,9 @@ export default { } for (const entry of entries) { - txs.push(models.vaultEntry.upsert({ - where: { userId: me.id, key: entry.key }, - update: { key: entry.key, value: entry.value }, - create: { key: entry.key, value: entry.value, userId: me.id, walletId: entry.walletId } + txs.push(models.vaultEntry.update({ + where: { id: entry.id }, + data: { key: entry.key, value: entry.value } })) } await models.prisma.$transaction(txs) diff --git a/api/resolvers/wallet.js b/api/resolvers/wallet.js index aecd05fd..a0951a81 100644 --- a/api/resolvers/wallet.js +++ b/api/resolvers/wallet.js @@ -19,7 +19,8 @@ import assertApiKeyNotPermitted from './apiKey' import { bolt11Tags } from '@/lib/bolt11' import { finalizeHodlInvoice } from 'worker/wallet' import walletDefs from 'wallets/server' -import { generateResolverName, generateTypeDefName, isConfigured } from '@/lib/wallet' +import { generateResolverName, generateTypeDefName } from '@/wallets/graphql' +import { isConfigured } from '@/wallets/common' import { lnAddrOptions } from '@/lib/lnurl' import { GqlAuthenticationError, GqlAuthorizationError, GqlInputError } from '@/lib/error' import { getNodeSockets, getOurPubkey } from '../lnd' diff --git a/api/typeDefs/wallet.js b/api/typeDefs/wallet.js index 406b8891..ad697217 100644 --- a/api/typeDefs/wallet.js +++ b/api/typeDefs/wallet.js @@ -1,6 +1,6 @@ import { gql } from 'graphql-tag' -import { fieldToGqlArg, fieldToGqlArgOptional, generateResolverName, generateTypeDefName, isServerField } from '@/lib/wallet' - +import { fieldToGqlArg, fieldToGqlArgOptional, generateResolverName, generateTypeDefName } from '@/wallets/graphql' +import { isServerField } from '@/wallets/common' import walletDefs from 'wallets/server' function injectTypeDefs (typeDefs) { diff --git a/components/autowithdraw-shared.js b/components/autowithdraw-shared.js index 27917ed9..47f01bb8 100644 --- a/components/autowithdraw-shared.js +++ b/components/autowithdraw-shared.js @@ -5,6 +5,7 @@ import { useEffect, useState } from 'react' import { isNumber } from '@/lib/validate' import { useIsClient } from './use-client' import Link from 'next/link' +import { isConfigured } from '@/wallets/common' function autoWithdrawThreshold ({ me }) { return isNumber(me?.privates?.autoWithdrawThreshold) ? me?.privates?.autoWithdrawThreshold : 10000 @@ -33,7 +34,7 @@ export function AutowithdrawSettings ({ wallet }) { return ( <> { if (enabled && connected) { showModal((onClose) => ( @@ -55,7 +52,7 @@ export default function DeviceSync () { )) } - }, [migrate, enabled, connected, value]) + }, [enabled, connected, value]) const reset = useCallback(async () => { const schema = yup.object().shape({ @@ -106,7 +103,6 @@ export default function DeviceSync () { if (values.passphrase) { try { await setVaultKey(values.passphrase) - await migrate() apollo.cache.evict({ fieldName: 'BestWallets' }) apollo.cache.gc() await apollo.refetchQueries({ include: ['BestWallets'] }) @@ -115,7 +111,7 @@ export default function DeviceSync () { throw e } } - }, [setVaultKey, migrate]) + }, [setVaultKey]) return ( <> @@ -139,33 +135,6 @@ export default function DeviceSync () {

- - - {enabled && !connected && (
diff --git a/components/item-act.js b/components/item-act.js index 36eaeaf7..36d5a0c7 100644 --- a/components/item-act.js +++ b/components/item-act.js @@ -13,7 +13,7 @@ import { usePaidMutation } from './use-paid-mutation' import { ACT_MUTATION } from '@/fragments/paidAction' import { meAnonSats } from '@/lib/apollo' import { BoostItemInput } from './adv-post-form' -import { useWallet } from '../wallets/common' +import { useWallet } from '@/wallets/index' const defaultTips = [100, 1000, 10_000, 100_000] diff --git a/components/nav/common.js b/components/nav/common.js index 184c199a..0f0f1763 100644 --- a/components/nav/common.js +++ b/components/nav/common.js @@ -22,10 +22,9 @@ import SearchIcon from '../../svgs/search-line.svg' import classNames from 'classnames' import SnIcon from '@/svgs/sn.svg' import { useHasNewNotes } from '../use-has-new-notes' -import { useWallets } from '@/wallets/common' +import { useWallets } from '@/wallets/index' import SwitchAccountList, { useAccounts } from '@/components/account' import { useShowModal } from '@/components/modal' -import { unsetLocalKey as resetVaultKey } from '@/components/use-vault' export function Brand ({ className }) { return ( @@ -266,7 +265,6 @@ function LogoutObstacle ({ onClose }) { const { registration: swRegistration, togglePushSubscription } = useServiceWorker() const wallets = useWallets() const { multiAuthSignout } = useAccounts() - const { me } = useMe() return (
@@ -295,7 +293,6 @@ function LogoutObstacle ({ onClose }) { } await wallets.resetClient().catch(console.error) - await resetVaultKey(me?.id) await signOut({ callbackUrl: '/' }) }} diff --git a/components/payment.js b/components/payment.js index f5eb6e67..8bba56da 100644 --- a/components/payment.js +++ b/components/payment.js @@ -1,35 +1,13 @@ import { useCallback, useMemo } from 'react' import { useMe } from './me' import { gql, useApolloClient, useMutation } from '@apollo/client' -import { useWallet } from '@/wallets/common' +import { useWallet } from '@/wallets/index' import { FAST_POLL_INTERVAL, JIT_INVOICE_TIMEOUT_MS } from '@/lib/constants' import { INVOICE } from '@/fragments/wallet' import Invoice from '@/components/invoice' import { useFeeButton } from './fee-button' import { useShowModal } from './modal' - -export class InvoiceCanceledError extends Error { - constructor (hash, actionError) { - super(actionError ?? `invoice canceled: ${hash}`) - this.name = 'InvoiceCanceledError' - this.hash = hash - this.actionError = actionError - } -} - -export class NoAttachedWalletError extends Error { - constructor () { - super('no attached wallet found') - this.name = 'NoAttachedWalletError' - } -} - -export class InvoiceExpiredError extends Error { - constructor (hash) { - super(`invoice expired: ${hash}`) - this.name = 'InvoiceExpiredError' - } -} +import { InvoiceCanceledError, NoAttachedWalletError, InvoiceExpiredError } from '@/wallets/errors' export const useInvoice = () => { const client = useApolloClient() diff --git a/components/qr.js b/components/qr.js index 12bf5a93..8b3f4563 100644 --- a/components/qr.js +++ b/components/qr.js @@ -2,7 +2,7 @@ import { QRCodeSVG } from 'qrcode.react' import { CopyInput, InputSkeleton } from './form' import InvoiceStatus from './invoice-status' import { useEffect } from 'react' -import { useWallet } from '@/wallets/common' +import { useWallet } from '@/wallets/index' import Bolt11Info from './bolt11-info' export default function Qr ({ asIs, value, useWallet: automated, statusVariant, description, status }) { diff --git a/components/use-indexeddb.js b/components/use-indexeddb.js index 948440a4..5086ff69 100644 --- a/components/use-indexeddb.js +++ b/components/use-indexeddb.js @@ -1,7 +1,7 @@ import { useState, useEffect, useCallback, useRef } from 'react' -export function getDbName (userId) { - return `app:storage${userId ? `:${userId}` : ''}` +export function getDbName (userId, name) { + return `app:storage:${userId ?? ''}${name ? `:${name}` : ''}` } function useIndexedDB ({ dbName, storeName, options = { keyPath: 'id', autoIncrement: true }, indices = [], version = 1 }) { diff --git a/components/vault/use-vault-configurator.js b/components/vault/use-vault-configurator.js index ec5d849c..5056208d 100644 --- a/components/vault/use-vault-configurator.js +++ b/components/vault/use-vault-configurator.js @@ -1,11 +1,10 @@ -import { UPDATE_VAULT_KEY } from '@/fragments/users' import { useMutation, useQuery } from '@apollo/client' import { useMe } from '../me' import { useToast } from '../toast' import useIndexedDB, { getDbName } from '../use-indexeddb' import { useCallback, useEffect, useState } from 'react' import { E_VAULT_KEY_EXISTS } from '@/lib/error' -import { CLEAR_VAULT, GET_VAULT_ENTRIES } from '@/fragments/vault' +import { CLEAR_VAULT, GET_VAULT_ENTRIES, UPDATE_VAULT_KEY } from '@/fragments/vault' import { toHex } from '@/lib/hex' import { decryptData, encryptData } from './use-vault' @@ -22,7 +21,7 @@ const useImperativeQuery = (query) => { export function useVaultConfigurator () { const { me } = useMe() const toaster = useToast() - const { set, get, remove } = useIndexedDB({ dbName: getDbName(me?.id), storeName: 'vault' }) + const { set, get, remove } = useIndexedDB({ dbName: getDbName(me?.id, 'vault'), storeName: 'vault' }) const [updateVaultKey] = useMutation(UPDATE_VAULT_KEY) const getVaultEntries = useImperativeQuery(GET_VAULT_ENTRIES) const [key, setKey] = useState(null) @@ -44,10 +43,10 @@ export function useVaultConfigurator () { } setKey(localVaultKey) } catch (e) { - toaster.danger('error loading vault configuration ' + e.message) + // toaster?.danger('error loading vault configuration ' + e.message) } })() - }, [me?.privates?.vaultKeyHash, keyHash, get, remove]) + }, [me?.privates?.vaultKeyHash, keyHash, get, remove, toaster]) // clear vault: remove everything and reset the key const [clearVault] = useMutation(CLEAR_VAULT, { diff --git a/components/wallet-buttonbar.js b/components/wallet-buttonbar.js index e07995f3..76b60e92 100644 --- a/components/wallet-buttonbar.js +++ b/components/wallet-buttonbar.js @@ -1,6 +1,7 @@ import { Button } from 'react-bootstrap' import CancelButton from './cancel-button' import { SubmitButton } from './form' +import { isConfigured } from '@/wallets/common' export default function WalletButtonBar ({ wallet, disable, @@ -10,12 +11,12 @@ export default function WalletButtonBar ({ return (
- {wallet.hasConfig && wallet.isConfigured && + {isConfigured(wallet) && } {children}
{hasCancel && } - {wallet.isConfigured ? editText : createText} + {isConfigured(wallet) ? editText : createText}
diff --git a/components/wallet-card.js b/components/wallet-card.js index 7f2ae297..36328920 100644 --- a/components/wallet-card.js +++ b/components/wallet-card.js @@ -3,26 +3,18 @@ import styles from '@/styles/wallet.module.css' import Plug from '@/svgs/plug.svg' import Gear from '@/svgs/settings-5-fill.svg' import Link from 'next/link' -import { Status } from '@/wallets/common' +import { Status, isConfigured } from '@/wallets/common' import DraggableIcon from '@/svgs/draggable.svg' export default function WalletCard ({ wallet, draggable, onDragStart, onDragEnter, onDragEnd, onTouchStart, sourceIndex, targetIndex, index }) { - const { card: { title, badges } } = wallet + const { card: { title, badges } } = wallet.def let indicator = styles.disabled switch (wallet.status) { case Status.Enabled: - case true: indicator = styles.success break - case Status.Locked: - indicator = styles.warning - break - case Status.Error: - indicator = styles.error - break - case Status.Initialized: - case false: + default: indicator = styles.disabled break } @@ -57,9 +49,9 @@ export default function WalletCard ({ wallet, draggable, onDragStart, onDragEnte )} - + - {wallet.isConfigured + {isConfigured(wallet) ? <>configure : <>attach} diff --git a/components/wallet-logger.js b/components/wallet-logger.js index f9814c80..19e23f57 100644 --- a/components/wallet-logger.js +++ b/components/wallet-logger.js @@ -86,10 +86,14 @@ const INDICES = [ { name: 'wallet_ts', keyPath: ['wallet', 'ts'] } ] +function getWalletLogDbName (userId) { + return getDbName(userId) +} + function useWalletLogDB () { const { me } = useMe() const { add, getPage, clear, error, notSupported } = useIndexedDB({ - dbName: getDbName(me?.id), + dbName: getWalletLogDbName(me?.id), storeName: 'wallet_logs', indices: INDICES }) @@ -127,7 +131,7 @@ export function useWalletLogger (wallet, setLogs) { ) const deleteLogs = useCallback(async (wallet, options) => { - if ((!wallet || wallet.walletType) && !options?.clientOnly) { + if ((!wallet || wallet.def.walletType) && !options?.clientOnly) { await deleteServerWalletLogs({ variables: { wallet: wallet?.walletType } }) } if (!wallet || wallet.sendPayment) { @@ -190,7 +194,7 @@ export function useWalletLogs (wallet, initialPage = 1, logsPerPage = 10) { result = await getPage(page, pageSize, indexName, query, 'prev') // no walletType means we're using the local IDB - if (wallet && !wallet.walletType) { + if (wallet && !wallet.def.walletType) { return result } } diff --git a/pages/_app.js b/pages/_app.js index 95f2a446..d138e412 100644 --- a/pages/_app.js +++ b/pages/_app.js @@ -22,7 +22,7 @@ import dynamic from 'next/dynamic' import { HasNewNotesProvider } from '@/components/use-has-new-notes' import { WebLnProvider } from '@/wallets/webln/client' import { AccountProvider } from '@/components/account' -import { WalletProvider } from '@/wallets/common' +import { WalletsProvider } from '@/wallets/index' const PWAPrompt = dynamic(() => import('react-ios-pwa-prompt'), { ssr: false }) @@ -105,7 +105,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) { - + @@ -132,7 +132,7 @@ export default function MyApp ({ Component, pageProps: { ...props } }) { - + diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js index 80f41465..196dffc2 100644 --- a/pages/settings/wallets/[wallet].js +++ b/pages/settings/wallets/[wallet].js @@ -5,14 +5,13 @@ import { WalletSecurityBanner } from '@/components/banners' import { WalletLogs } from '@/components/wallet-logger' import { useToast } from '@/components/toast' import { useRouter } from 'next/router' -import { useWallet } from '@/wallets/common' +import { useWallet } from '@/wallets/index' import Info from '@/components/info' import Text from '@/components/text' import { AutowithdrawSettings } from '@/components/autowithdraw-shared' -import dynamic from 'next/dynamic' -import { useIsClient } from '@/components/use-client' - -const WalletButtonBar = dynamic(() => import('@/components/wallet-buttonbar.js'), { ssr: false }) +import { isConfigured } from '@/wallets/common' +import { SSR } from '@/lib/constants' +import WalletButtonBar from '@/components/wallet-buttonbar' export const getServerSideProps = getGetServerSideProps({ authRequired: true }) @@ -22,7 +21,7 @@ export default function WalletSettings () { const { wallet: name } = router.query const wallet = useWallet(name) - const initial = wallet?.fields.reduce((acc, field) => { + const initial = wallet?.def.fields.reduce((acc, field) => { // We still need to run over all wallet fields via reduce // even though we use wallet.config as the initial value // since wallet.config is empty when wallet is not configured. @@ -41,8 +40,8 @@ export default function WalletSettings () { return ( -

{wallet?.card?.title}

-
{wallet?.card?.subtitle}
+

{wallet?.def.card.title}

+
{wallet?.def.card.subtitle}
{wallet?.canSend && wallet?.hasConfig > 0 && }
{ try { - const newConfig = !wallet?.isConfigured + const newConfig = !isConfigured(wallet) // enable wallet if wallet was just configured if (newConfig) { @@ -68,11 +67,11 @@ export default function WalletSettings () { }} > {wallet && } - {wallet?.clientOnly + {wallet?.def.clientOnly ? ( { const rawProps = { ...props, name, - initialValue: config?.[name], - readOnly: isClient && isConfigured && editable === false && !!config?.[name], + initialValue: wallet.config?.[name], + readOnly: !SSR && isConfigured(wallet) && editable === false && !!wallet.config?.[name], groupClassName: props.hidden ? 'd-none' : undefined, label: label ? ( diff --git a/pages/settings/wallets/index.js b/pages/settings/wallets/index.js index 7712fd2f..bd9a6cad 100644 --- a/pages/settings/wallets/index.js +++ b/pages/settings/wallets/index.js @@ -2,12 +2,10 @@ import { getGetServerSideProps } from '@/api/ssrApollo' import Layout from '@/components/layout' import styles from '@/styles/wallet.module.css' import Link from 'next/link' -import { useWallets, walletPrioritySort } from '@/wallets/common' +import { useWallets } from '@/wallets/index' import { useState } from 'react' -import dynamic from 'next/dynamic' import { useIsClient } from '@/components/use-client' - -const WalletCard = dynamic(() => import('@/components/wallet-card'), { ssr: false }) +import WalletCard from '@/components/wallet-card' export const getServerSideProps = getGetServerSideProps({ authRequired: true }) @@ -28,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) @@ -76,49 +74,33 @@ export default function Wallet ({ ssrData }) {
- {wallets - .sort((w1, w2) => { - // enabled/configured wallets always come before disabled/unconfigured wallets - if ((w1.enabled && !w2.enabled) || (w1.isConfigured && !w2.isConfigured)) { - return -1 - } else if ((w2.enabled && !w1.enabled) || (w2.isConfigured && !w1.isConfigured)) { - return 1 - } + {wallets.map((w, i) => { + const draggable = isClient && w.config?.enabled - return walletPrioritySort(w1, w2) - }) - .map((w, i) => { - const draggable = isClient && w.enabled - - return ( -
- -
- ) - } - )} + suppressHydrationWarning + > + +
+ ) + } + )}
diff --git a/wallets/common.js b/wallets/common.js index 07124483..b6416fd5 100644 --- a/wallets/common.js +++ b/wallets/common.js @@ -1,10 +1,8 @@ import walletDefs from 'wallets/client' export const Status = { - Initialized: 'Initialized', Enabled: 'Enabled', - Locked: 'Locked', - Error: 'Error' + Disabled: 'Disabled' } export function getWalletByName (name) { @@ -28,13 +26,20 @@ export function getStorageKey (name, me) { } export function walletPrioritySort (w1, w2) { - const delta = w1.priority - w2.priority + // enabled/configured wallets always come before disabled/unconfigured wallets + if ((w1.config?.enabled && !w2.config?.enabled) || (isConfigured(w1) && !isConfigured(w2))) { + return -1 + } else if ((w2.config?.enabled && !w1.config?.enabled) || (isConfigured(w2) && !isConfigured(w1))) { + return 1 + } + + const delta = w1.config?.priority - w2.config?.priority // delta is NaN if either priority is undefined if (!Number.isNaN(delta) && delta !== 0) return delta // if one wallet has a priority but the other one doesn't, the one with the priority comes first - if (w1.priority !== undefined && w2.priority === undefined) return -1 - if (w1.priority === undefined && w2.priority !== undefined) return 1 + if (w1.config?.priority !== undefined && w2.config?.priority === undefined) return -1 + if (w1.config?.priority === undefined && w2.config?.priority !== undefined) return 1 // both wallets have no priority set, falling back to other methods @@ -43,5 +48,50 @@ export function walletPrioritySort (w1, w2) { if (w1.config?.id && w2.config?.id) return Number(w1.config.id) - Number(w2.config.id) // else we will use the card title as tie breaker - return w1.card.title < w2.card.title ? -1 : 1 + return w1.def.card.title < w2.def.card.title ? -1 : 1 +} + +export function isServerField (f) { + return f.serverOnly || !f.clientOnly +} + +export function isClientField (f) { + return f.clientOnly || !f.serverOnly +} + +function checkFields ({ fields, config }) { + // a wallet is configured if all of its required fields are set + let val = fields.every(f => { + return f.optional ? true : !!config?.[f.name] + }) + + // however, a wallet is not configured if all fields are optional and none are set + // since that usually means that one of them is required + if (val && fields.length > 0) { + val = !(fields.every(f => f.optional) && fields.every(f => !config?.[f.name])) + } + + return val +} + +export function isConfigured (wallet) { + return isSendConfigured(wallet) || isReceiveConfigured(wallet) +} + +function isSendConfigured (wallet) { + const fields = wallet.def.fields.filter(isClientField) + return checkFields({ fields, config: wallet.config }) +} + +function isReceiveConfigured (wallet) { + const fields = wallet.def.fields.filter(isServerField) + return checkFields({ fields, config: wallet.config }) +} + +export function canSend (wallet) { + return !!wallet.def.sendPayment && isSendConfigured(wallet) +} + +export function canReceive (wallet) { + return !wallet.def.clientOnly && isReceiveConfigured(wallet) } diff --git a/wallets/config.js b/wallets/config.js index 05cc3cdf..7e941b84 100644 --- a/wallets/config.js +++ b/wallets/config.js @@ -1,7 +1,7 @@ import { useMe } from '@/components/me' -import useVault from '@/components/use-vault' +import useVault from '@/components/vault/use-vault' import { useCallback } from 'react' -import { getStorageKey } from './common' +import { getStorageKey, isClientField, isServerField } from './common' import { useMutation } from '@apollo/client' import { generateMutation } from './graphql' import { REMOVE_WALLET } from '@/fragments/wallet' @@ -11,8 +11,8 @@ import { useWalletLogger } from '@/components/wallet-logger' export function useWalletConfigurator (wallet) { const { me } = useMe() const { encrypt, isActive } = useVault() - const { logger } = useWalletLogger(wallet.def) - const [upsertWallet] = useMutation(generateMutation(wallet.def)) + const { logger } = useWalletLogger(wallet?.def) + const [upsertWallet] = useMutation(generateMutation(wallet?.def)) const [removeWallet] = useMutation(REMOVE_WALLET) const _saveToServer = useCallback(async (serverConfig, clientConfig) => { @@ -117,48 +117,3 @@ function extractClientConfig (fields, config) { function extractServerConfig (fields, config) { return extractConfig(fields, config, false, true) } - -export function isServerField (f) { - return f.serverOnly || !f.clientOnly -} - -export function isClientField (f) { - return f.clientOnly || !f.serverOnly -} - -function checkFields ({ fields, config }) { - // a wallet is configured if all of its required fields are set - let val = fields.every(f => { - return f.optional ? true : !!config?.[f.name] - }) - - // however, a wallet is not configured if all fields are optional and none are set - // since that usually means that one of them is required - if (val && fields.length > 0) { - val = !(fields.every(f => f.optional) && fields.every(f => !config?.[f.name])) - } - - return val -} - -export function isConfigured (wallet) { - return isSendConfigured(wallet) || isReceiveConfigured(wallet) -} - -function isSendConfigured (wallet) { - const fields = wallet.def.fields.filter(isClientField) - return checkFields({ fields, config: wallet.config }) -} - -function isReceiveConfigured (wallet) { - const fields = wallet.def.fields.filter(isServerField) - return checkFields({ fields, config: wallet.config }) -} - -export function canSend (wallet) { - return !!wallet.def.sendPayment && isSendConfigured(wallet) -} - -export function canReceive (wallet) { - return !wallet.def.clientOnly && isReceiveConfigured(wallet) -} diff --git a/wallets/errors.js b/wallets/errors.js new file mode 100644 index 00000000..5cfde92b --- /dev/null +++ b/wallets/errors.js @@ -0,0 +1,22 @@ +export class InvoiceCanceledError extends Error { + constructor (hash, actionError) { + super(actionError ?? `invoice canceled: ${hash}`) + this.name = 'InvoiceCanceledError' + this.hash = hash + this.actionError = actionError + } +} + +export class NoAttachedWalletError extends Error { + constructor () { + super('no attached wallet found') + this.name = 'NoAttachedWalletError' + } +} + +export class InvoiceExpiredError extends Error { + constructor (hash) { + super(`invoice expired: ${hash}`) + this.name = 'InvoiceExpiredError' + } +} diff --git a/wallets/graphql.js b/wallets/graphql.js index 4fe0d31f..36bd6710 100644 --- a/wallets/graphql.js +++ b/wallets/graphql.js @@ -1,5 +1,5 @@ import gql from 'graphql-tag' -import { isServerField } from './config' +import { isServerField } from './common' export function fieldToGqlArg (field) { let arg = `${field.name}: String` diff --git a/wallets/index.js b/wallets/index.js index 4f466e0b..95da558d 100644 --- a/wallets/index.js +++ b/wallets/index.js @@ -3,12 +3,11 @@ import { WALLETS } from '@/fragments/wallet' import { NORMAL_POLL_INTERVAL, SSR } from '@/lib/constants' import { useQuery } from '@apollo/client' import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react' -import { getStorageKey, getWalletByType } from './common' -import useVault from '@/components/use-vault' +import { getStorageKey, getWalletByType, Status, walletPrioritySort, canSend } from './common' +import useVault from '@/components/vault/use-vault' import { useWalletLogger } from '@/components/wallet-logger' import { bolt11Tags } from '@/lib/bolt11' import walletDefs from 'wallets/client' -import { canSend } from './config' const WalletsContext = createContext({ wallets: [] @@ -47,6 +46,8 @@ function useLocalWallets () { return wallets } +const walletDefsOnly = walletDefs.map(w => ({ def: w, config: {} })) + export function WalletsProvider ({ children }) { const { me } = useMe() const { decrypt } = useVault() @@ -70,16 +71,19 @@ export function WalletsProvider ({ children }) { } return { config, def } - }) + }) ?? [] // merge wallets on name const merged = {} - for (const wallet of [...localWallets, ...wallets]) { + for (const wallet of [...walletDefsOnly, ...localWallets, ...wallets]) { merged[wallet.def.name] = { ...merged[wallet.def.name], ...wallet } } return Object.values(merged) + .sort(walletPrioritySort) + .map(w => ({ ...w, status: w.config?.enabled ? Status.Enabled : Status.Disabled })) }, [data?.wallets, localWallets]) + // provides priority sorted wallets to children return ( {children} @@ -101,10 +105,10 @@ export function useWallet (name) { return wallets .filter(w => !w.def.isAvailable || w.def.isAvailable()) - .filter(w => w.config.enabled && canSend(w))[0] + .filter(w => w.config?.enabled && canSend(w))[0] }, [wallets, name]) - const { logger } = useWalletLogger(wallet.def) + const { logger } = useWalletLogger(wallet?.def) const sendPayment = useCallback(async (bolt11) => { const hash = bolt11Tags(bolt11).payment_hash diff --git a/wallets/lnc/client.js b/wallets/lnc/client.js index 46371866..4e6b1fe4 100644 --- a/wallets/lnc/client.js +++ b/wallets/lnc/client.js @@ -1,4 +1,4 @@ -import { InvoiceCanceledError, InvoiceExpiredError } from '@/components/payment' +import { InvoiceCanceledError, InvoiceExpiredError } from '@/wallets/errors' import { bolt11Tags } from '@/lib/bolt11' import { Mutex } from 'async-mutex' export * from 'wallets/lnc'