Wallet debug logs (#2307)
* Add wallet debug logs * Add checkbox to toggle diagnostics * Require authentication for /wallets/debug * Update debug log messages * Use me.privates.diagnostics as source of truth
This commit is contained in:
parent
2913e9a9b5
commit
243b094fcd
@ -919,6 +919,14 @@ export default {
|
|||||||
|
|
||||||
await models.user.update({ where: { id: me.id }, data: { hideWalletRecvPrompt: true } })
|
await models.user.update({ where: { id: me.id }, data: { hideWalletRecvPrompt: true } })
|
||||||
return true
|
return true
|
||||||
|
},
|
||||||
|
setDiagnostics: async (parent, { diagnostics }, { me, models }) => {
|
||||||
|
if (!me) {
|
||||||
|
throw new GqlAuthenticationError()
|
||||||
|
}
|
||||||
|
|
||||||
|
await models.user.update({ where: { id: me.id }, data: { diagnostics } })
|
||||||
|
return diagnostics
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -56,6 +56,7 @@ export default gql`
|
|||||||
generateApiKey(id: ID!): String
|
generateApiKey(id: ID!): String
|
||||||
deleteApiKey(id: ID!): User
|
deleteApiKey(id: ID!): User
|
||||||
disableFreebies: Boolean
|
disableFreebies: Boolean
|
||||||
|
setDiagnostics(diagnostics: Boolean!): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type User {
|
type User {
|
||||||
@ -86,7 +87,6 @@ export default gql`
|
|||||||
|
|
||||||
input SettingsInput {
|
input SettingsInput {
|
||||||
autoDropBolt11s: Boolean!
|
autoDropBolt11s: Boolean!
|
||||||
diagnostics: Boolean @deprecated
|
|
||||||
noReferralLinks: Boolean!
|
noReferralLinks: Boolean!
|
||||||
fiatCurrency: String!
|
fiatCurrency: String!
|
||||||
satsFilter: Int!
|
satsFilter: Int!
|
||||||
@ -155,12 +155,12 @@ export default gql`
|
|||||||
hasInvites: Boolean!
|
hasInvites: Boolean!
|
||||||
apiKeyEnabled: Boolean!
|
apiKeyEnabled: Boolean!
|
||||||
showPassphrase: Boolean!
|
showPassphrase: Boolean!
|
||||||
|
diagnostics: Boolean!
|
||||||
|
|
||||||
"""
|
"""
|
||||||
mirrors SettingsInput
|
mirrors SettingsInput
|
||||||
"""
|
"""
|
||||||
autoDropBolt11s: Boolean!
|
autoDropBolt11s: Boolean!
|
||||||
diagnostics: Boolean @deprecated
|
|
||||||
noReferralLinks: Boolean!
|
noReferralLinks: Boolean!
|
||||||
fiatCurrency: String!
|
fiatCurrency: String!
|
||||||
satsFilter: Int!
|
satsFilter: Int!
|
||||||
|
@ -11,7 +11,7 @@ const typeDefs = gql`
|
|||||||
wallets: [WalletOrTemplate!]!
|
wallets: [WalletOrTemplate!]!
|
||||||
wallet(id: ID, name: String): WalletOrTemplate
|
wallet(id: ID, name: String): WalletOrTemplate
|
||||||
walletSettings: WalletSettings!
|
walletSettings: WalletSettings!
|
||||||
walletLogs(protocolId: Int, cursor: String): WalletLogs!
|
walletLogs(protocolId: Int, cursor: String, debug: Boolean): WalletLogs!
|
||||||
failedInvoices: [Invoice!]!
|
failedInvoices: [Invoice!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,7 +21,7 @@ const typeDefs = gql`
|
|||||||
cancelInvoice(hash: String!, hmac: String, userCancel: Boolean): Invoice!
|
cancelInvoice(hash: String!, hmac: String, userCancel: Boolean): Invoice!
|
||||||
dropBolt11(hash: String!): Boolean
|
dropBolt11(hash: String!): Boolean
|
||||||
removeWallet(id: ID!): Boolean
|
removeWallet(id: ID!): Boolean
|
||||||
deleteWalletLogs(protocolId: Int): Boolean
|
deleteWalletLogs(protocolId: Int, debug: Boolean): Boolean
|
||||||
setWalletPriorities(priorities: [WalletPriorityUpdate!]!): Boolean
|
setWalletPriorities(priorities: [WalletPriorityUpdate!]!): Boolean
|
||||||
buyCredits(credits: Int!): BuyCreditsPaidAction!
|
buyCredits(credits: Int!): BuyCreditsPaidAction!
|
||||||
|
|
||||||
@ -44,7 +44,7 @@ const typeDefs = gql`
|
|||||||
resetWallets(newKeyHash: String!): Boolean
|
resetWallets(newKeyHash: String!): Boolean
|
||||||
disablePassphraseExport: Boolean
|
disablePassphraseExport: Boolean
|
||||||
setWalletSettings(settings: WalletSettingsInput!): Boolean
|
setWalletSettings(settings: WalletSettingsInput!): Boolean
|
||||||
addWalletLog(protocolId: Int!, level: String!, message: String!, timestamp: Date!, invoiceId: Int): Boolean
|
addWalletLog(protocolId: Int, level: String!, message: String!, timestamp: Date!, invoiceId: Int): Boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
type BuyCreditsResult {
|
type BuyCreditsResult {
|
||||||
|
@ -51,6 +51,7 @@ ${STREAK_FIELDS}
|
|||||||
vaultKeyHashUpdatedAt
|
vaultKeyHashUpdatedAt
|
||||||
walletsUpdatedAt
|
walletsUpdatedAt
|
||||||
showPassphrase
|
showPassphrase
|
||||||
|
diagnostics
|
||||||
}
|
}
|
||||||
optional {
|
optional {
|
||||||
isContributor
|
isContributor
|
||||||
@ -392,3 +393,9 @@ export const MY_SUBSCRIBED_SUBS = gql`
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
export const SET_DIAGNOSTICS = gql`
|
||||||
|
mutation setDiagnostics($diagnostics: Boolean!) {
|
||||||
|
setDiagnostics(diagnostics: $diagnostics)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { getGetServerSideProps } from '@/api/ssrApollo'
|
import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||||
import { WalletLayout, WalletLayoutHeader, WalletDebugSettings } from '@/wallets/client/components'
|
import { WalletLayout, WalletLayoutHeader, WalletDebugSettings, WalletLogs } from '@/wallets/client/components'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps({})
|
export const getServerSideProps = getGetServerSideProps({ authRequired: true })
|
||||||
|
|
||||||
export default function WalletDebug () {
|
export default function WalletDebug () {
|
||||||
return (
|
return (
|
||||||
@ -9,6 +9,7 @@ export default function WalletDebug () {
|
|||||||
<div className='py-5 mx-auto w-100' style={{ maxWidth: '600px' }}>
|
<div className='py-5 mx-auto w-100' style={{ maxWidth: '600px' }}>
|
||||||
<WalletLayoutHeader>wallet debug</WalletLayoutHeader>
|
<WalletLayoutHeader>wallet debug</WalletLayoutHeader>
|
||||||
<WalletDebugSettings />
|
<WalletDebugSettings />
|
||||||
|
<WalletLogs className='mt-3' debug />
|
||||||
</div>
|
</div>
|
||||||
</WalletLayout>
|
</WalletLayout>
|
||||||
)
|
)
|
||||||
|
@ -117,7 +117,7 @@ model User {
|
|||||||
followees UserSubscription[] @relation("followee")
|
followees UserSubscription[] @relation("followee")
|
||||||
hideWelcomeBanner Boolean @default(false)
|
hideWelcomeBanner Boolean @default(false)
|
||||||
hideWalletRecvPrompt Boolean @default(false)
|
hideWalletRecvPrompt Boolean @default(false)
|
||||||
diagnostics Boolean @default(false) @ignore
|
diagnostics Boolean @default(false)
|
||||||
hideIsContributor Boolean @default(false)
|
hideIsContributor Boolean @default(false)
|
||||||
lnAddr String?
|
lnAddr String?
|
||||||
autoWithdrawMaxFeePercent Float?
|
autoWithdrawMaxFeePercent Float?
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { formatBytes } from '@/lib/format'
|
import { formatBytes } from '@/lib/format'
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from 'react'
|
||||||
import { useKeyHash, useKeyUpdatedAt } from '@/wallets/client/context'
|
import { useKeyHash, useKeyUpdatedAt } from '@/wallets/client/context'
|
||||||
import { useRemoteKeyHash, useRemoteKeyHashUpdatedAt, useWalletsUpdatedAt } from '@/wallets/client/hooks'
|
import { useDiagnostics, useRemoteKeyHash, useRemoteKeyHashUpdatedAt, useWalletsUpdatedAt } from '@/wallets/client/hooks'
|
||||||
import { timeSince } from '@/lib/time'
|
import { timeSince } from '@/lib/time'
|
||||||
|
|
||||||
export function WalletDebugSettings () {
|
export function WalletDebugSettings () {
|
||||||
@ -13,6 +13,7 @@ export function WalletDebugSettings () {
|
|||||||
const [persistent, setPersistent] = useState(null)
|
const [persistent, setPersistent] = useState(null)
|
||||||
const [quota, setQuota] = useState(null)
|
const [quota, setQuota] = useState(null)
|
||||||
const [usage, setUsage] = useState(null)
|
const [usage, setUsage] = useState(null)
|
||||||
|
const [diagnostics, setDiagnostics] = useDiagnostics()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function init () {
|
async function init () {
|
||||||
@ -59,6 +60,14 @@ export function WalletDebugSettings () {
|
|||||||
<div className='text-end' suppressHydrationWarning>
|
<div className='text-end' suppressHydrationWarning>
|
||||||
{walletsUpdatedAt ? `${timeSince(new Date(walletsUpdatedAt).getTime())} ago` : 'unknown'}
|
{walletsUpdatedAt ? `${timeSince(new Date(walletsUpdatedAt).getTime())} ago` : 'unknown'}
|
||||||
</div>
|
</div>
|
||||||
|
<div className='text-nowrap'>diagnostics:</div>
|
||||||
|
{/* not using Formik here because we want to submit immediately on change */}
|
||||||
|
<input
|
||||||
|
type='checkbox'
|
||||||
|
checked={diagnostics}
|
||||||
|
style={{ justifySelf: 'end', accentColor: 'var(--bs-primary)' }}
|
||||||
|
onChange={(e) => setDiagnostics(e.target.checked)}
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ import { ModalClosedError } from '@/components/modal'
|
|||||||
// when we delete logs for a protocol, the cache is not updated
|
// when we delete logs for a protocol, the cache is not updated
|
||||||
// so when we go to all wallet logs, we still see the deleted logs until the query is refetched
|
// so when we go to all wallet logs, we still see the deleted logs until the query is refetched
|
||||||
|
|
||||||
export function WalletLogs ({ protocol, className }) {
|
export function WalletLogs ({ protocol, className, debug }) {
|
||||||
const { logs, loadMore, hasMore, loading, clearLogs } = useWalletLogs(protocol)
|
const { logs, loadMore, hasMore, loading, clearLogs } = useWalletLogs(protocol, debug)
|
||||||
const deleteLogs = useDeleteWalletLogs(protocol)
|
const deleteLogs = useDeleteWalletLogs(protocol, debug)
|
||||||
|
|
||||||
const onDelete = useCallback(async () => {
|
const onDelete = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@ -73,8 +73,11 @@ export function LogMessage ({ tag, level, message, context, ts }) {
|
|||||||
case 'warning':
|
case 'warning':
|
||||||
level = 'warn'
|
level = 'warn'
|
||||||
className = 'text-warning'; break
|
className = 'text-warning'; break
|
||||||
|
case 'info':
|
||||||
|
className = 'text-info'; break
|
||||||
|
case 'debug':
|
||||||
default:
|
default:
|
||||||
className = 'text-info'
|
className = 'text-muted'; break
|
||||||
}
|
}
|
||||||
|
|
||||||
const filtered = context
|
const filtered = context
|
||||||
|
@ -6,7 +6,8 @@ import useInvoice from '@/components/use-invoice'
|
|||||||
import { useMe } from '@/components/me'
|
import { useMe } from '@/components/me'
|
||||||
import {
|
import {
|
||||||
useWalletsQuery, useWalletPayment, useGenerateRandomKey, useSetKey, useLoadKey, useLoadOldKey,
|
useWalletsQuery, useWalletPayment, useGenerateRandomKey, useSetKey, useLoadKey, useLoadOldKey,
|
||||||
useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey
|
useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey,
|
||||||
|
useWalletLogger
|
||||||
} from '@/wallets/client/hooks'
|
} from '@/wallets/client/hooks'
|
||||||
import { WalletConfigurationError } from '@/wallets/client/errors'
|
import { WalletConfigurationError } from '@/wallets/client/errors'
|
||||||
import { SET_WALLETS, WRONG_KEY, KEY_MATCH, useWalletsDispatch, WALLETS_QUERY_ERROR, KEY_STORAGE_UNAVAILABLE } from '@/wallets/client/context'
|
import { SET_WALLETS, WRONG_KEY, KEY_MATCH, useWalletsDispatch, WALLETS_QUERY_ERROR, KEY_STORAGE_UNAVAILABLE } from '@/wallets/client/context'
|
||||||
@ -108,6 +109,8 @@ export function useKeyInit () {
|
|||||||
const dispatch = useWalletsDispatch()
|
const dispatch = useWalletsDispatch()
|
||||||
const wrongKey = useIsWrongKey()
|
const wrongKey = useIsWrongKey()
|
||||||
|
|
||||||
|
const logger = useWalletLogger()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (typeof window.indexedDB === 'undefined') {
|
if (typeof window.indexedDB === 'undefined') {
|
||||||
dispatch({ type: KEY_STORAGE_UNAVAILABLE })
|
dispatch({ type: KEY_STORAGE_UNAVAILABLE })
|
||||||
@ -165,17 +168,20 @@ export function useKeyInit () {
|
|||||||
const read = tx.objectStore('vault').get('key')
|
const read = tx.objectStore('vault').get('key')
|
||||||
|
|
||||||
read.onerror = () => {
|
read.onerror = () => {
|
||||||
|
logger.debug('key init: error reading key: ' + read.error)
|
||||||
reject(read.error)
|
reject(read.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
read.onsuccess = () => {
|
read.onsuccess = () => {
|
||||||
if (read.result) {
|
if (read.result) {
|
||||||
// return key+hash found in db
|
// return key+hash found in db
|
||||||
|
logger.debug('key init: key found in IndexedDB')
|
||||||
return resolve(read.result)
|
return resolve(read.result)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldKeyAndHash) {
|
if (oldKeyAndHash) {
|
||||||
// return key+hash found in old db
|
// return key+hash found in old db
|
||||||
|
logger.debug('key init: key found in old IndexedDB')
|
||||||
return resolve(oldKeyAndHash)
|
return resolve(oldKeyAndHash)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,11 +190,13 @@ export function useKeyInit () {
|
|||||||
const write = tx.objectStore('vault').put({ key: randomKey, hash: randomHash, updatedAt }, 'key')
|
const write = tx.objectStore('vault').put({ key: randomKey, hash: randomHash, updatedAt }, 'key')
|
||||||
|
|
||||||
write.onerror = () => {
|
write.onerror = () => {
|
||||||
|
logger.debug('key init: error writing new random key: ' + write.error)
|
||||||
reject(write.error)
|
reject(write.error)
|
||||||
}
|
}
|
||||||
|
|
||||||
write.onsuccess = (event) => {
|
write.onsuccess = (event) => {
|
||||||
// return key+hash we just wrote to db
|
// return key+hash we just wrote to db
|
||||||
|
logger.debug('key init: saved new random key')
|
||||||
resolve({ key: randomKey, hash: randomHash, updatedAt })
|
resolve({ key: randomKey, hash: randomHash, updatedAt })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -196,11 +204,12 @@ export function useKeyInit () {
|
|||||||
|
|
||||||
await setKey({ key, hash, updatedAt }, { updateDb: false })
|
await setKey({ key, hash, updatedAt }, { updateDb: false })
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('key init failed:', err)
|
logger.debug('key init: error: ' + err)
|
||||||
|
console.error('key init: error:', err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
keyInit()
|
keyInit()
|
||||||
}, [me?.id, db, generateRandomKey, loadOldKey, setKey, loadKey])
|
}, [me?.id, db, generateRandomKey, loadOldKey, setKey, loadKey, logger])
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(wallet-v2): remove migration code
|
// TODO(wallet-v2): remove migration code
|
||||||
|
@ -229,14 +229,14 @@ export const SET_WALLET_SETTINGS = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const ADD_WALLET_LOG = gql`
|
export const ADD_WALLET_LOG = gql`
|
||||||
mutation AddWalletLog($protocolId: Int!, $level: String!, $message: String!, $timestamp: Date!, $invoiceId: Int) {
|
mutation AddWalletLog($protocolId: Int, $level: String!, $message: String!, $timestamp: Date!, $invoiceId: Int) {
|
||||||
addWalletLog(protocolId: $protocolId, level: $level, message: $message, timestamp: $timestamp, invoiceId: $invoiceId)
|
addWalletLog(protocolId: $protocolId, level: $level, message: $message, timestamp: $timestamp, invoiceId: $invoiceId)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
export const WALLET_LOGS = gql`
|
export const WALLET_LOGS = gql`
|
||||||
query WalletLogs($protocolId: Int, $cursor: String) {
|
query WalletLogs($protocolId: Int, $cursor: String, $debug: Boolean) {
|
||||||
walletLogs(protocolId: $protocolId, cursor: $cursor) {
|
walletLogs(protocolId: $protocolId, cursor: $cursor, debug: $debug) {
|
||||||
entries {
|
entries {
|
||||||
id
|
id
|
||||||
level
|
level
|
||||||
@ -253,7 +253,7 @@ export const WALLET_LOGS = gql`
|
|||||||
`
|
`
|
||||||
|
|
||||||
export const DELETE_WALLET_LOGS = gql`
|
export const DELETE_WALLET_LOGS = gql`
|
||||||
mutation DeleteWalletLogs($protocolId: Int) {
|
mutation DeleteWalletLogs($protocolId: Int, $debug: Boolean) {
|
||||||
deleteWalletLogs(protocolId: $protocolId)
|
deleteWalletLogs(protocolId: $protocolId, debug: $debug)
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
@ -9,7 +9,7 @@ import bip39Words from '@/lib/bip39-words'
|
|||||||
import { Form, PasswordInput, SubmitButton } from '@/components/form'
|
import { Form, PasswordInput, SubmitButton } from '@/components/form'
|
||||||
import { object, string } from 'yup'
|
import { object, string } from 'yup'
|
||||||
import { SET_KEY, useKey, useKeyHash, useWalletsDispatch } from '@/wallets/client/context'
|
import { SET_KEY, useKey, useKeyHash, useWalletsDispatch } from '@/wallets/client/context'
|
||||||
import { useDisablePassphraseExport, useUpdateKeyHash, useWalletEncryptionUpdate, useWalletReset } from '@/wallets/client/hooks'
|
import { useDisablePassphraseExport, useUpdateKeyHash, useWalletEncryptionUpdate, useWalletLogger, useWalletReset } from '@/wallets/client/hooks'
|
||||||
import { useToast } from '@/components/toast'
|
import { useToast } from '@/components/toast'
|
||||||
|
|
||||||
export class CryptoKeyRequiredError extends Error {
|
export class CryptoKeyRequiredError extends Error {
|
||||||
@ -41,6 +41,7 @@ export function useSetKey () {
|
|||||||
const { set } = useIndexedDB()
|
const { set } = useIndexedDB()
|
||||||
const dispatch = useWalletsDispatch()
|
const dispatch = useWalletsDispatch()
|
||||||
const updateKeyHash = useUpdateKeyHash()
|
const updateKeyHash = useUpdateKeyHash()
|
||||||
|
const logger = useWalletLogger()
|
||||||
|
|
||||||
return useCallback(async ({ key, hash, updatedAt }, { updateDb = true } = {}) => {
|
return useCallback(async ({ key, hash, updatedAt }, { updateDb = true } = {}) => {
|
||||||
if (updateDb) {
|
if (updateDb) {
|
||||||
@ -49,7 +50,8 @@ export function useSetKey () {
|
|||||||
}
|
}
|
||||||
await updateKeyHash(hash)
|
await updateKeyHash(hash)
|
||||||
dispatch({ type: SET_KEY, key, hash, updatedAt })
|
dispatch({ type: SET_KEY, key, hash, updatedAt })
|
||||||
}, [set, dispatch, updateKeyHash])
|
logger.debug(`using key ${hash}`)
|
||||||
|
}, [set, dispatch, updateKeyHash, logger])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useEncryption () {
|
export function useEncryption () {
|
||||||
@ -159,12 +161,14 @@ export function useSavePassphrase () {
|
|||||||
const setKey = useSetKey()
|
const setKey = useSetKey()
|
||||||
const salt = useKeySalt()
|
const salt = useKeySalt()
|
||||||
const disablePassphraseExport = useDisablePassphraseExport()
|
const disablePassphraseExport = useDisablePassphraseExport()
|
||||||
|
const logger = useWalletLogger()
|
||||||
|
|
||||||
return useCallback(async ({ passphrase }) => {
|
return useCallback(async ({ passphrase }) => {
|
||||||
|
logger.debug('passphrase entered')
|
||||||
const { key, hash } = await deriveKey(passphrase, salt)
|
const { key, hash } = await deriveKey(passphrase, salt)
|
||||||
await setKey({ key, hash })
|
await setKey({ key, hash })
|
||||||
await disablePassphraseExport()
|
await disablePassphraseExport()
|
||||||
}, [setKey, disablePassphraseExport])
|
}, [setKey, disablePassphraseExport, logger])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useResetPassphrase () {
|
export function useResetPassphrase () {
|
||||||
@ -173,19 +177,22 @@ export function useResetPassphrase () {
|
|||||||
const generateRandomKey = useGenerateRandomKey()
|
const generateRandomKey = useGenerateRandomKey()
|
||||||
const setKey = useSetKey()
|
const setKey = useSetKey()
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
|
const logger = useWalletLogger()
|
||||||
|
|
||||||
const resetPassphrase = useCallback((close) =>
|
const resetPassphrase = useCallback((close) =>
|
||||||
async () => {
|
async () => {
|
||||||
try {
|
try {
|
||||||
|
logger.debug('passphrase reset')
|
||||||
const { key: randomKey, hash } = await generateRandomKey()
|
const { key: randomKey, hash } = await generateRandomKey()
|
||||||
await setKey({ key: randomKey, hash })
|
await setKey({ key: randomKey, hash })
|
||||||
await walletReset({ newKeyHash: hash })
|
await walletReset({ newKeyHash: hash })
|
||||||
close()
|
close()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
logger.debug('failed to reset passphrase: ' + err)
|
||||||
console.error('failed to reset passphrase:', err)
|
console.error('failed to reset passphrase:', err)
|
||||||
toaster.error('failed to reset passphrase')
|
toaster.error('failed to reset passphrase')
|
||||||
}
|
}
|
||||||
}, [walletReset, generateRandomKey, setKey, toaster])
|
}, [walletReset, generateRandomKey, setKey, toaster, logger])
|
||||||
|
|
||||||
return useCallback(async () => {
|
return useCallback(async () => {
|
||||||
showModal(close => (
|
showModal(close => (
|
||||||
|
23
wallets/client/hooks/diagnostics.js
Normal file
23
wallets/client/hooks/diagnostics.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { useMe } from '@/components/me'
|
||||||
|
import { useToast } from '@/components/toast'
|
||||||
|
import { SET_DIAGNOSTICS } from '@/fragments/users'
|
||||||
|
import { useMutation } from '@apollo/client'
|
||||||
|
import { useCallback } from 'react'
|
||||||
|
|
||||||
|
export function useDiagnostics () {
|
||||||
|
const { me, refreshMe } = useMe()
|
||||||
|
const [mutate] = useMutation(SET_DIAGNOSTICS)
|
||||||
|
const toaster = useToast()
|
||||||
|
|
||||||
|
const setDiagnostics = useCallback(async (diagnostics) => {
|
||||||
|
try {
|
||||||
|
await mutate({ variables: { diagnostics } })
|
||||||
|
await refreshMe()
|
||||||
|
} catch (err) {
|
||||||
|
console.error('failed to toggle diagnostics:', err)
|
||||||
|
toaster.danger('failed to toggle diagnostics')
|
||||||
|
}
|
||||||
|
}, [mutate, toaster, refreshMe])
|
||||||
|
|
||||||
|
return [me?.privates?.diagnostics ?? false, setDiagnostics]
|
||||||
|
}
|
@ -6,3 +6,4 @@ export * from './wallet'
|
|||||||
export * from './crypto'
|
export * from './crypto'
|
||||||
export * from './query'
|
export * from './query'
|
||||||
export * from './logger'
|
export * from './logger'
|
||||||
|
export * from './diagnostics'
|
||||||
|
@ -6,6 +6,7 @@ import { ModalClosedError, useShowModal } from '@/components/modal'
|
|||||||
import { useToast } from '@/components/toast'
|
import { useToast } from '@/components/toast'
|
||||||
import { FAST_POLL_INTERVAL } from '@/lib/constants'
|
import { FAST_POLL_INTERVAL } from '@/lib/constants'
|
||||||
import { isTemplate } from '@/wallets/lib/util'
|
import { isTemplate } from '@/wallets/lib/util'
|
||||||
|
import { useDiagnostics } from '@/wallets/client/hooks/diagnostics'
|
||||||
|
|
||||||
const TemplateLogsContext = createContext({})
|
const TemplateLogsContext = createContext({})
|
||||||
|
|
||||||
@ -38,17 +39,18 @@ export function TemplateLogsProvider ({ children }) {
|
|||||||
export function useWalletLoggerFactory () {
|
export function useWalletLoggerFactory () {
|
||||||
const { addTemplateLog } = useContext(TemplateLogsContext)
|
const { addTemplateLog } = useContext(TemplateLogsContext)
|
||||||
const [addWalletLog] = useMutation(ADD_WALLET_LOG)
|
const [addWalletLog] = useMutation(ADD_WALLET_LOG)
|
||||||
|
const [diagnostics] = useDiagnostics()
|
||||||
|
|
||||||
const log = useCallback(({ protocol, level, message, invoiceId }) => {
|
const log = useCallback(({ protocol, level, message, invoiceId }) => {
|
||||||
console[mapLevelToConsole(level)](`[${protocol.name}] ${message}`)
|
console[mapLevelToConsole(level)](`[${protocol ? protocol.name : 'system'}] ${message}`)
|
||||||
|
|
||||||
if (isTemplate(protocol)) {
|
if (protocol && isTemplate(protocol)) {
|
||||||
// this is a template, so there's no protocol yet to which we could attach logs in the db
|
// this is a template, so there's no protocol yet to which we could attach logs in the db
|
||||||
addTemplateLog?.({ level, message })
|
addTemplateLog?.({ level, message })
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
return addWalletLog({ variables: { protocolId: Number(protocol.id), level, message, invoiceId, timestamp: new Date() } })
|
return addWalletLog({ variables: { protocolId: protocol ? Number(protocol.id) : null, level, message, invoiceId, timestamp: new Date() } })
|
||||||
.catch(err => {
|
.catch(err => {
|
||||||
console.error('error adding wallet log:', err)
|
console.error('error adding wallet log:', err)
|
||||||
})
|
})
|
||||||
@ -68,9 +70,13 @@ export function useWalletLoggerFactory () {
|
|||||||
},
|
},
|
||||||
warn: (message) => {
|
warn: (message) => {
|
||||||
log({ protocol, level: 'WARN', message, invoiceId })
|
log({ protocol, level: 'WARN', message, invoiceId })
|
||||||
|
},
|
||||||
|
debug: (message) => {
|
||||||
|
if (!diagnostics) return
|
||||||
|
log({ protocol, level: 'DEBUG', message, invoiceId })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [log])
|
}, [log, diagnostics])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWalletLogger (protocol) {
|
export function useWalletLogger (protocol) {
|
||||||
@ -78,7 +84,7 @@ export function useWalletLogger (protocol) {
|
|||||||
return useMemo(() => loggerFactory(protocol), [loggerFactory, protocol])
|
return useMemo(() => loggerFactory(protocol), [loggerFactory, protocol])
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWalletLogs (protocol) {
|
export function useWalletLogs (protocol, debug) {
|
||||||
const { templateLogs, clearTemplateLogs } = useContext(TemplateLogsContext)
|
const { templateLogs, clearTemplateLogs } = useContext(TemplateLogsContext)
|
||||||
|
|
||||||
const [cursor, setCursor] = useState(null)
|
const [cursor, setCursor] = useState(null)
|
||||||
@ -90,7 +96,7 @@ export function useWalletLogs (protocol) {
|
|||||||
const protocolId = protocol ? Number(protocol.id) : undefined
|
const protocolId = protocol ? Number(protocol.id) : undefined
|
||||||
|
|
||||||
const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, {
|
const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, {
|
||||||
variables: { protocolId },
|
variables: { protocolId, debug },
|
||||||
skip,
|
skip,
|
||||||
fetchPolicy: 'network-only'
|
fetchPolicy: 'network-only'
|
||||||
})
|
})
|
||||||
@ -99,7 +105,11 @@ export function useWalletLogs (protocol) {
|
|||||||
if (skip) return
|
if (skip) return
|
||||||
|
|
||||||
const interval = setInterval(async () => {
|
const interval = setInterval(async () => {
|
||||||
const { data } = await fetchLogs({ variables: { protocolId } })
|
const { data, error } = await fetchLogs({ variables: { protocolId, debug } })
|
||||||
|
if (error) {
|
||||||
|
console.error('failed to fetch wallet logs:', error.message)
|
||||||
|
return
|
||||||
|
}
|
||||||
const { entries: updatedLogs, cursor } = data.walletLogs
|
const { entries: updatedLogs, cursor } = data.walletLogs
|
||||||
setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs])
|
setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs])
|
||||||
if (!called) {
|
if (!called) {
|
||||||
@ -108,14 +118,14 @@ export function useWalletLogs (protocol) {
|
|||||||
}, FAST_POLL_INTERVAL)
|
}, FAST_POLL_INTERVAL)
|
||||||
|
|
||||||
return () => clearInterval(interval)
|
return () => clearInterval(interval)
|
||||||
}, [fetchLogs, called, skip])
|
}, [fetchLogs, called, skip, debug])
|
||||||
|
|
||||||
const loadMore = useCallback(async () => {
|
const loadMore = useCallback(async () => {
|
||||||
const { data } = await fetchLogs({ variables: { protocolId, cursor } })
|
const { data } = await fetchLogs({ variables: { protocolId, cursor, debug } })
|
||||||
const { entries: cursorLogs, cursor: newCursor } = data.walletLogs
|
const { entries: cursorLogs, cursor: newCursor } = data.walletLogs
|
||||||
setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))])
|
setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))])
|
||||||
setCursor(newCursor)
|
setCursor(newCursor)
|
||||||
}, [fetchLogs, cursor, protocolId])
|
}, [fetchLogs, cursor, protocolId, debug])
|
||||||
|
|
||||||
const clearLogs = useCallback(() => {
|
const clearLogs = useCallback(() => {
|
||||||
setLogs([])
|
setLogs([])
|
||||||
@ -149,7 +159,7 @@ function mapLevelToConsole (level) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useDeleteWalletLogs (protocol) {
|
export function useDeleteWalletLogs (protocol, debug) {
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
|
|
||||||
return useCallback(async () => {
|
return useCallback(async () => {
|
||||||
@ -174,6 +184,7 @@ export function useDeleteWalletLogs (protocol) {
|
|||||||
protocol={protocol}
|
protocol={protocol}
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
onDelete={onDelete}
|
onDelete={onDelete}
|
||||||
|
debug={debug}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}, { onClose })
|
}, { onClose })
|
||||||
@ -181,7 +192,7 @@ export function useDeleteWalletLogs (protocol) {
|
|||||||
}, [showModal])
|
}, [showModal])
|
||||||
}
|
}
|
||||||
|
|
||||||
function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
|
function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete, debug }) {
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
const [deleteWalletLogs] = useMutation(DELETE_WALLET_LOGS)
|
const [deleteWalletLogs] = useMutation(DELETE_WALLET_LOGS)
|
||||||
|
|
||||||
@ -190,9 +201,9 @@ function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
|
|||||||
if (protocol && isTemplate(protocol)) return
|
if (protocol && isTemplate(protocol)) return
|
||||||
|
|
||||||
await deleteWalletLogs({
|
await deleteWalletLogs({
|
||||||
variables: { protocolId: protocol ? Number(protocol.id) : undefined }
|
variables: { protocolId: protocol ? Number(protocol.id) : undefined, debug }
|
||||||
})
|
})
|
||||||
}, [protocol, deleteWalletLogs])
|
}, [protocol, deleteWalletLogs, debug])
|
||||||
|
|
||||||
const onClick = useCallback(async () => {
|
const onClick = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
@ -206,7 +217,7 @@ function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
|
|||||||
}
|
}
|
||||||
}, [onClose, deleteLogs, toaster])
|
}, [onClose, deleteLogs, toaster])
|
||||||
|
|
||||||
let prompt = 'Do you really want to delete all wallet logs?'
|
let prompt = debug ? 'Do you really want to delete all debug logs?' : 'Do you really want to delete all logs?'
|
||||||
if (protocol) {
|
if (protocol) {
|
||||||
prompt = 'Do you really want to delete all logs of this protocol?'
|
prompt = 'Do you really want to delete all logs of this protocol?'
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,8 @@ export function walletLogger ({
|
|||||||
ok: (message, context) => log('OK')(message, context),
|
ok: (message, context) => log('OK')(message, context),
|
||||||
info: (message, context) => log('INFO')(message, context),
|
info: (message, context) => log('INFO')(message, context),
|
||||||
error: (message, context) => log('ERROR')(message, context),
|
error: (message, context) => log('ERROR')(message, context),
|
||||||
warn: (message, context) => log('WARNING')(message, context)
|
warn: (message, context) => log('WARNING')(message, context),
|
||||||
|
debug: (message, context) => log('DEBUG')(message, context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -237,7 +237,7 @@ export async function removeWalletProtocol (parent, { id }, { me, models, tx })
|
|||||||
return await (tx ? transaction(tx) : models.$transaction(transaction))
|
return await (tx ? transaction(tx) : models.$transaction(transaction))
|
||||||
}
|
}
|
||||||
|
|
||||||
async function walletLogs (parent, { protocolId, cursor }, { me, models }) {
|
async function walletLogs (parent, { protocolId, cursor, debug }, { me, models }) {
|
||||||
if (!me) throw new GqlAuthenticationError()
|
if (!me) throw new GqlAuthenticationError()
|
||||||
|
|
||||||
const decodedCursor = decodeCursor(cursor)
|
const decodedCursor = decodeCursor(cursor)
|
||||||
@ -248,7 +248,8 @@ async function walletLogs (parent, { protocolId, cursor }, { me, models }) {
|
|||||||
protocolId,
|
protocolId,
|
||||||
createdAt: {
|
createdAt: {
|
||||||
lt: decodedCursor.time
|
lt: decodedCursor.time
|
||||||
}
|
},
|
||||||
|
level: debug ? 'DEBUG' : { not: 'DEBUG' }
|
||||||
},
|
},
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: 'desc'
|
createdAt: 'desc'
|
||||||
@ -295,13 +296,14 @@ async function addWalletLog (parent, { protocolId, level, message, timestamp, in
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteWalletLogs (parent, { protocolId }, { me, models }) {
|
async function deleteWalletLogs (parent, { protocolId, debug }, { me, models }) {
|
||||||
if (!me) throw new GqlAuthenticationError()
|
if (!me) throw new GqlAuthenticationError()
|
||||||
|
|
||||||
await models.walletLog.deleteMany({
|
await models.walletLog.deleteMany({
|
||||||
where: {
|
where: {
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
protocolId
|
protocolId,
|
||||||
|
level: debug ? 'DEBUG' : { not: 'DEBUG' }
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user