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 } })
 | 
			
		||||
      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
 | 
			
		||||
    deleteApiKey(id: ID!): User
 | 
			
		||||
    disableFreebies: Boolean
 | 
			
		||||
    setDiagnostics(diagnostics: Boolean!): Boolean
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  type User {
 | 
			
		||||
@ -86,7 +87,6 @@ export default gql`
 | 
			
		||||
 | 
			
		||||
  input SettingsInput {
 | 
			
		||||
    autoDropBolt11s: Boolean!
 | 
			
		||||
    diagnostics: Boolean @deprecated
 | 
			
		||||
    noReferralLinks: Boolean!
 | 
			
		||||
    fiatCurrency: String!
 | 
			
		||||
    satsFilter: Int!
 | 
			
		||||
@ -155,12 +155,12 @@ export default gql`
 | 
			
		||||
    hasInvites: Boolean!
 | 
			
		||||
    apiKeyEnabled: Boolean!
 | 
			
		||||
    showPassphrase: Boolean!
 | 
			
		||||
    diagnostics: Boolean!
 | 
			
		||||
 | 
			
		||||
    """
 | 
			
		||||
    mirrors SettingsInput
 | 
			
		||||
    """
 | 
			
		||||
    autoDropBolt11s: Boolean!
 | 
			
		||||
    diagnostics: Boolean @deprecated
 | 
			
		||||
    noReferralLinks: Boolean!
 | 
			
		||||
    fiatCurrency: String!
 | 
			
		||||
    satsFilter: Int!
 | 
			
		||||
 | 
			
		||||
@ -11,7 +11,7 @@ const typeDefs = gql`
 | 
			
		||||
    wallets: [WalletOrTemplate!]!
 | 
			
		||||
    wallet(id: ID, name: String): WalletOrTemplate
 | 
			
		||||
    walletSettings: WalletSettings!
 | 
			
		||||
    walletLogs(protocolId: Int, cursor: String): WalletLogs!
 | 
			
		||||
    walletLogs(protocolId: Int, cursor: String, debug: Boolean): WalletLogs!
 | 
			
		||||
    failedInvoices: [Invoice!]!
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -21,7 +21,7 @@ const typeDefs = gql`
 | 
			
		||||
    cancelInvoice(hash: String!, hmac: String, userCancel: Boolean): Invoice!
 | 
			
		||||
    dropBolt11(hash: String!): Boolean
 | 
			
		||||
    removeWallet(id: ID!): Boolean
 | 
			
		||||
    deleteWalletLogs(protocolId: Int): Boolean
 | 
			
		||||
    deleteWalletLogs(protocolId: Int, debug: Boolean): Boolean
 | 
			
		||||
    setWalletPriorities(priorities: [WalletPriorityUpdate!]!): Boolean
 | 
			
		||||
    buyCredits(credits: Int!): BuyCreditsPaidAction!
 | 
			
		||||
 | 
			
		||||
@ -44,7 +44,7 @@ const typeDefs = gql`
 | 
			
		||||
    resetWallets(newKeyHash: String!): Boolean
 | 
			
		||||
    disablePassphraseExport: 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 {
 | 
			
		||||
 | 
			
		||||
@ -51,6 +51,7 @@ ${STREAK_FIELDS}
 | 
			
		||||
      vaultKeyHashUpdatedAt
 | 
			
		||||
      walletsUpdatedAt
 | 
			
		||||
      showPassphrase
 | 
			
		||||
      diagnostics
 | 
			
		||||
    }
 | 
			
		||||
    optional {
 | 
			
		||||
      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 { 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 () {
 | 
			
		||||
  return (
 | 
			
		||||
@ -9,6 +9,7 @@ export default function WalletDebug () {
 | 
			
		||||
      <div className='py-5 mx-auto w-100' style={{ maxWidth: '600px' }}>
 | 
			
		||||
        <WalletLayoutHeader>wallet debug</WalletLayoutHeader>
 | 
			
		||||
        <WalletDebugSettings />
 | 
			
		||||
        <WalletLogs className='mt-3' debug />
 | 
			
		||||
      </div>
 | 
			
		||||
    </WalletLayout>
 | 
			
		||||
  )
 | 
			
		||||
 | 
			
		||||
@ -117,7 +117,7 @@ model User {
 | 
			
		||||
  followees                 UserSubscription[]   @relation("followee")
 | 
			
		||||
  hideWelcomeBanner         Boolean              @default(false)
 | 
			
		||||
  hideWalletRecvPrompt      Boolean              @default(false)
 | 
			
		||||
  diagnostics               Boolean              @default(false) @ignore
 | 
			
		||||
  diagnostics               Boolean              @default(false)
 | 
			
		||||
  hideIsContributor         Boolean              @default(false)
 | 
			
		||||
  lnAddr                    String?
 | 
			
		||||
  autoWithdrawMaxFeePercent Float?
 | 
			
		||||
 | 
			
		||||
@ -1,7 +1,7 @@
 | 
			
		||||
import { formatBytes } from '@/lib/format'
 | 
			
		||||
import { useEffect, useState } from 'react'
 | 
			
		||||
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'
 | 
			
		||||
 | 
			
		||||
export function WalletDebugSettings () {
 | 
			
		||||
@ -13,6 +13,7 @@ export function WalletDebugSettings () {
 | 
			
		||||
  const [persistent, setPersistent] = useState(null)
 | 
			
		||||
  const [quota, setQuota] = useState(null)
 | 
			
		||||
  const [usage, setUsage] = useState(null)
 | 
			
		||||
  const [diagnostics, setDiagnostics] = useDiagnostics()
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    async function init () {
 | 
			
		||||
@ -59,6 +60,14 @@ export function WalletDebugSettings () {
 | 
			
		||||
      <div className='text-end' suppressHydrationWarning>
 | 
			
		||||
        {walletsUpdatedAt ? `${timeSince(new Date(walletsUpdatedAt).getTime())} ago` : 'unknown'}
 | 
			
		||||
      </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>
 | 
			
		||||
  )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -10,9 +10,9 @@ import { ModalClosedError } from '@/components/modal'
 | 
			
		||||
//   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
 | 
			
		||||
 | 
			
		||||
export function WalletLogs ({ protocol, className }) {
 | 
			
		||||
  const { logs, loadMore, hasMore, loading, clearLogs } = useWalletLogs(protocol)
 | 
			
		||||
  const deleteLogs = useDeleteWalletLogs(protocol)
 | 
			
		||||
export function WalletLogs ({ protocol, className, debug }) {
 | 
			
		||||
  const { logs, loadMore, hasMore, loading, clearLogs } = useWalletLogs(protocol, debug)
 | 
			
		||||
  const deleteLogs = useDeleteWalletLogs(protocol, debug)
 | 
			
		||||
 | 
			
		||||
  const onDelete = useCallback(async () => {
 | 
			
		||||
    try {
 | 
			
		||||
@ -73,8 +73,11 @@ export function LogMessage ({ tag, level, message, context, ts }) {
 | 
			
		||||
    case 'warning':
 | 
			
		||||
      level = 'warn'
 | 
			
		||||
      className = 'text-warning'; break
 | 
			
		||||
    case 'info':
 | 
			
		||||
      className = 'text-info'; break
 | 
			
		||||
    case 'debug':
 | 
			
		||||
    default:
 | 
			
		||||
      className = 'text-info'
 | 
			
		||||
      className = 'text-muted'; break
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const filtered = context
 | 
			
		||||
 | 
			
		||||
@ -6,7 +6,8 @@ import useInvoice from '@/components/use-invoice'
 | 
			
		||||
import { useMe } from '@/components/me'
 | 
			
		||||
import {
 | 
			
		||||
  useWalletsQuery, useWalletPayment, useGenerateRandomKey, useSetKey, useLoadKey, useLoadOldKey,
 | 
			
		||||
  useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey
 | 
			
		||||
  useWalletMigrationMutation, CryptoKeyRequiredError, useIsWrongKey,
 | 
			
		||||
  useWalletLogger
 | 
			
		||||
} from '@/wallets/client/hooks'
 | 
			
		||||
import { WalletConfigurationError } from '@/wallets/client/errors'
 | 
			
		||||
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 wrongKey = useIsWrongKey()
 | 
			
		||||
 | 
			
		||||
  const logger = useWalletLogger()
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (typeof window.indexedDB === 'undefined') {
 | 
			
		||||
      dispatch({ type: KEY_STORAGE_UNAVAILABLE })
 | 
			
		||||
@ -165,17 +168,20 @@ export function useKeyInit () {
 | 
			
		||||
          const read = tx.objectStore('vault').get('key')
 | 
			
		||||
 | 
			
		||||
          read.onerror = () => {
 | 
			
		||||
            logger.debug('key init: error reading key: ' + read.error)
 | 
			
		||||
            reject(read.error)
 | 
			
		||||
          }
 | 
			
		||||
 | 
			
		||||
          read.onsuccess = () => {
 | 
			
		||||
            if (read.result) {
 | 
			
		||||
              // return key+hash found in db
 | 
			
		||||
              logger.debug('key init: key found in IndexedDB')
 | 
			
		||||
              return resolve(read.result)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (oldKeyAndHash) {
 | 
			
		||||
              // return key+hash found in old db
 | 
			
		||||
              logger.debug('key init: key found in old IndexedDB')
 | 
			
		||||
              return resolve(oldKeyAndHash)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
@ -184,11 +190,13 @@ export function useKeyInit () {
 | 
			
		||||
            const write = tx.objectStore('vault').put({ key: randomKey, hash: randomHash, updatedAt }, 'key')
 | 
			
		||||
 | 
			
		||||
            write.onerror = () => {
 | 
			
		||||
              logger.debug('key init: error writing new random key: ' + write.error)
 | 
			
		||||
              reject(write.error)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            write.onsuccess = (event) => {
 | 
			
		||||
              // return key+hash we just wrote to db
 | 
			
		||||
              logger.debug('key init: saved new random key')
 | 
			
		||||
              resolve({ key: randomKey, hash: randomHash, updatedAt })
 | 
			
		||||
            }
 | 
			
		||||
          }
 | 
			
		||||
@ -196,11 +204,12 @@ export function useKeyInit () {
 | 
			
		||||
 | 
			
		||||
        await setKey({ key, hash, updatedAt }, { updateDb: false })
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        console.error('key init failed:', err)
 | 
			
		||||
        logger.debug('key init: error: ' + err)
 | 
			
		||||
        console.error('key init: error:', err)
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
    keyInit()
 | 
			
		||||
  }, [me?.id, db, generateRandomKey, loadOldKey, setKey, loadKey])
 | 
			
		||||
  }, [me?.id, db, generateRandomKey, loadOldKey, setKey, loadKey, logger])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TODO(wallet-v2): remove migration code
 | 
			
		||||
 | 
			
		||||
@ -229,14 +229,14 @@ export const SET_WALLET_SETTINGS = 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)
 | 
			
		||||
  }
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
export const WALLET_LOGS = gql`
 | 
			
		||||
  query WalletLogs($protocolId: Int, $cursor: String) {
 | 
			
		||||
    walletLogs(protocolId: $protocolId, cursor: $cursor) {
 | 
			
		||||
  query WalletLogs($protocolId: Int, $cursor: String, $debug: Boolean) {
 | 
			
		||||
    walletLogs(protocolId: $protocolId, cursor: $cursor, debug: $debug) {
 | 
			
		||||
      entries {
 | 
			
		||||
        id
 | 
			
		||||
        level
 | 
			
		||||
@ -253,7 +253,7 @@ export const WALLET_LOGS = gql`
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
export const DELETE_WALLET_LOGS = gql`
 | 
			
		||||
  mutation DeleteWalletLogs($protocolId: Int) {
 | 
			
		||||
    deleteWalletLogs(protocolId: $protocolId)
 | 
			
		||||
  mutation DeleteWalletLogs($protocolId: Int, $debug: Boolean) {
 | 
			
		||||
    deleteWalletLogs(protocolId: $protocolId, debug: $debug)
 | 
			
		||||
  }
 | 
			
		||||
`
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ import bip39Words from '@/lib/bip39-words'
 | 
			
		||||
import { Form, PasswordInput, SubmitButton } from '@/components/form'
 | 
			
		||||
import { object, string } from 'yup'
 | 
			
		||||
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'
 | 
			
		||||
 | 
			
		||||
export class CryptoKeyRequiredError extends Error {
 | 
			
		||||
@ -41,6 +41,7 @@ export function useSetKey () {
 | 
			
		||||
  const { set } = useIndexedDB()
 | 
			
		||||
  const dispatch = useWalletsDispatch()
 | 
			
		||||
  const updateKeyHash = useUpdateKeyHash()
 | 
			
		||||
  const logger = useWalletLogger()
 | 
			
		||||
 | 
			
		||||
  return useCallback(async ({ key, hash, updatedAt }, { updateDb = true } = {}) => {
 | 
			
		||||
    if (updateDb) {
 | 
			
		||||
@ -49,7 +50,8 @@ export function useSetKey () {
 | 
			
		||||
    }
 | 
			
		||||
    await updateKeyHash(hash)
 | 
			
		||||
    dispatch({ type: SET_KEY, key, hash, updatedAt })
 | 
			
		||||
  }, [set, dispatch, updateKeyHash])
 | 
			
		||||
    logger.debug(`using key ${hash}`)
 | 
			
		||||
  }, [set, dispatch, updateKeyHash, logger])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useEncryption () {
 | 
			
		||||
@ -159,12 +161,14 @@ export function useSavePassphrase () {
 | 
			
		||||
  const setKey = useSetKey()
 | 
			
		||||
  const salt = useKeySalt()
 | 
			
		||||
  const disablePassphraseExport = useDisablePassphraseExport()
 | 
			
		||||
  const logger = useWalletLogger()
 | 
			
		||||
 | 
			
		||||
  return useCallback(async ({ passphrase }) => {
 | 
			
		||||
    logger.debug('passphrase entered')
 | 
			
		||||
    const { key, hash } = await deriveKey(passphrase, salt)
 | 
			
		||||
    await setKey({ key, hash })
 | 
			
		||||
    await disablePassphraseExport()
 | 
			
		||||
  }, [setKey, disablePassphraseExport])
 | 
			
		||||
  }, [setKey, disablePassphraseExport, logger])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useResetPassphrase () {
 | 
			
		||||
@ -173,19 +177,22 @@ export function useResetPassphrase () {
 | 
			
		||||
  const generateRandomKey = useGenerateRandomKey()
 | 
			
		||||
  const setKey = useSetKey()
 | 
			
		||||
  const toaster = useToast()
 | 
			
		||||
  const logger = useWalletLogger()
 | 
			
		||||
 | 
			
		||||
  const resetPassphrase = useCallback((close) =>
 | 
			
		||||
    async () => {
 | 
			
		||||
      try {
 | 
			
		||||
        logger.debug('passphrase reset')
 | 
			
		||||
        const { key: randomKey, hash } = await generateRandomKey()
 | 
			
		||||
        await setKey({ key: randomKey, hash })
 | 
			
		||||
        await walletReset({ newKeyHash: hash })
 | 
			
		||||
        close()
 | 
			
		||||
      } catch (err) {
 | 
			
		||||
        logger.debug('failed to reset passphrase: ' + err)
 | 
			
		||||
        console.error('failed to reset passphrase:', err)
 | 
			
		||||
        toaster.error('failed to reset passphrase')
 | 
			
		||||
      }
 | 
			
		||||
    }, [walletReset, generateRandomKey, setKey, toaster])
 | 
			
		||||
    }, [walletReset, generateRandomKey, setKey, toaster, logger])
 | 
			
		||||
 | 
			
		||||
  return useCallback(async () => {
 | 
			
		||||
    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 './query'
 | 
			
		||||
export * from './logger'
 | 
			
		||||
export * from './diagnostics'
 | 
			
		||||
 | 
			
		||||
@ -6,6 +6,7 @@ import { ModalClosedError, useShowModal } from '@/components/modal'
 | 
			
		||||
import { useToast } from '@/components/toast'
 | 
			
		||||
import { FAST_POLL_INTERVAL } from '@/lib/constants'
 | 
			
		||||
import { isTemplate } from '@/wallets/lib/util'
 | 
			
		||||
import { useDiagnostics } from '@/wallets/client/hooks/diagnostics'
 | 
			
		||||
 | 
			
		||||
const TemplateLogsContext = createContext({})
 | 
			
		||||
 | 
			
		||||
@ -38,17 +39,18 @@ export function TemplateLogsProvider ({ children }) {
 | 
			
		||||
export function useWalletLoggerFactory () {
 | 
			
		||||
  const { addTemplateLog } = useContext(TemplateLogsContext)
 | 
			
		||||
  const [addWalletLog] = useMutation(ADD_WALLET_LOG)
 | 
			
		||||
  const [diagnostics] = useDiagnostics()
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
      addTemplateLog?.({ level, message })
 | 
			
		||||
      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 => {
 | 
			
		||||
        console.error('error adding wallet log:', err)
 | 
			
		||||
      })
 | 
			
		||||
@ -68,9 +70,13 @@ export function useWalletLoggerFactory () {
 | 
			
		||||
      },
 | 
			
		||||
      warn: (message) => {
 | 
			
		||||
        log({ protocol, level: 'WARN', message, invoiceId })
 | 
			
		||||
      },
 | 
			
		||||
      debug: (message) => {
 | 
			
		||||
        if (!diagnostics) return
 | 
			
		||||
        log({ protocol, level: 'DEBUG', message, invoiceId })
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }, [log])
 | 
			
		||||
  }, [log, diagnostics])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useWalletLogger (protocol) {
 | 
			
		||||
@ -78,7 +84,7 @@ export function useWalletLogger (protocol) {
 | 
			
		||||
  return useMemo(() => loggerFactory(protocol), [loggerFactory, protocol])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useWalletLogs (protocol) {
 | 
			
		||||
export function useWalletLogs (protocol, debug) {
 | 
			
		||||
  const { templateLogs, clearTemplateLogs } = useContext(TemplateLogsContext)
 | 
			
		||||
 | 
			
		||||
  const [cursor, setCursor] = useState(null)
 | 
			
		||||
@ -90,7 +96,7 @@ export function useWalletLogs (protocol) {
 | 
			
		||||
  const protocolId = protocol ? Number(protocol.id) : undefined
 | 
			
		||||
 | 
			
		||||
  const [fetchLogs, { called, loading, error }] = useLazyQuery(WALLET_LOGS, {
 | 
			
		||||
    variables: { protocolId },
 | 
			
		||||
    variables: { protocolId, debug },
 | 
			
		||||
    skip,
 | 
			
		||||
    fetchPolicy: 'network-only'
 | 
			
		||||
  })
 | 
			
		||||
@ -99,7 +105,11 @@ export function useWalletLogs (protocol) {
 | 
			
		||||
    if (skip) return
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
      setLogs(logs => [...updatedLogs.filter(log => !logs.some(l => l.id === log.id)), ...logs])
 | 
			
		||||
      if (!called) {
 | 
			
		||||
@ -108,14 +118,14 @@ export function useWalletLogs (protocol) {
 | 
			
		||||
    }, FAST_POLL_INTERVAL)
 | 
			
		||||
 | 
			
		||||
    return () => clearInterval(interval)
 | 
			
		||||
  }, [fetchLogs, called, skip])
 | 
			
		||||
  }, [fetchLogs, called, skip, debug])
 | 
			
		||||
 | 
			
		||||
  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
 | 
			
		||||
    setLogs(logs => [...logs, ...cursorLogs.filter(log => !logs.some(l => l.id === log.id))])
 | 
			
		||||
    setCursor(newCursor)
 | 
			
		||||
  }, [fetchLogs, cursor, protocolId])
 | 
			
		||||
  }, [fetchLogs, cursor, protocolId, debug])
 | 
			
		||||
 | 
			
		||||
  const clearLogs = useCallback(() => {
 | 
			
		||||
    setLogs([])
 | 
			
		||||
@ -149,7 +159,7 @@ function mapLevelToConsole (level) {
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export function useDeleteWalletLogs (protocol) {
 | 
			
		||||
export function useDeleteWalletLogs (protocol, debug) {
 | 
			
		||||
  const showModal = useShowModal()
 | 
			
		||||
 | 
			
		||||
  return useCallback(async () => {
 | 
			
		||||
@ -174,6 +184,7 @@ export function useDeleteWalletLogs (protocol) {
 | 
			
		||||
            protocol={protocol}
 | 
			
		||||
            onClose={onClose}
 | 
			
		||||
            onDelete={onDelete}
 | 
			
		||||
            debug={debug}
 | 
			
		||||
          />
 | 
			
		||||
        )
 | 
			
		||||
      }, { onClose })
 | 
			
		||||
@ -181,7 +192,7 @@ export function useDeleteWalletLogs (protocol) {
 | 
			
		||||
  }, [showModal])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
 | 
			
		||||
function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete, debug }) {
 | 
			
		||||
  const toaster = useToast()
 | 
			
		||||
  const [deleteWalletLogs] = useMutation(DELETE_WALLET_LOGS)
 | 
			
		||||
 | 
			
		||||
@ -190,9 +201,9 @@ function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
 | 
			
		||||
    if (protocol && isTemplate(protocol)) return
 | 
			
		||||
 | 
			
		||||
    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 () => {
 | 
			
		||||
    try {
 | 
			
		||||
@ -206,7 +217,7 @@ function DeleteWalletLogsObstacle ({ protocol, onClose, onDelete }) {
 | 
			
		||||
    }
 | 
			
		||||
  }, [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) {
 | 
			
		||||
    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),
 | 
			
		||||
    info: (message, context) => log('INFO')(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))
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function walletLogs (parent, { protocolId, cursor }, { me, models }) {
 | 
			
		||||
async function walletLogs (parent, { protocolId, cursor, debug }, { me, models }) {
 | 
			
		||||
  if (!me) throw new GqlAuthenticationError()
 | 
			
		||||
 | 
			
		||||
  const decodedCursor = decodeCursor(cursor)
 | 
			
		||||
@ -248,7 +248,8 @@ async function walletLogs (parent, { protocolId, cursor }, { me, models }) {
 | 
			
		||||
      protocolId,
 | 
			
		||||
      createdAt: {
 | 
			
		||||
        lt: decodedCursor.time
 | 
			
		||||
      }
 | 
			
		||||
      },
 | 
			
		||||
      level: debug ? 'DEBUG' : { not: 'DEBUG' }
 | 
			
		||||
    },
 | 
			
		||||
    orderBy: {
 | 
			
		||||
      createdAt: 'desc'
 | 
			
		||||
@ -295,13 +296,14 @@ async function addWalletLog (parent, { protocolId, level, message, timestamp, in
 | 
			
		||||
  return true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
async function deleteWalletLogs (parent, { protocolId }, { me, models }) {
 | 
			
		||||
async function deleteWalletLogs (parent, { protocolId, debug }, { me, models }) {
 | 
			
		||||
  if (!me) throw new GqlAuthenticationError()
 | 
			
		||||
 | 
			
		||||
  await models.walletLog.deleteMany({
 | 
			
		||||
    where: {
 | 
			
		||||
      userId: me.id,
 | 
			
		||||
      protocolId
 | 
			
		||||
      protocolId,
 | 
			
		||||
      level: debug ? 'DEBUG' : { not: 'DEBUG' }
 | 
			
		||||
    }
 | 
			
		||||
  })
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user