Add wallet debug page (#2301)
* Add wallet debug page * Show key hash information * Show last key update * Show last wallet update * Show last device key update
This commit is contained in:
parent
6b440cfdf3
commit
faa26ec68f
@ -202,6 +202,7 @@ export default gql`
|
||||
withdrawMaxFeeDefault: Int!
|
||||
autoWithdrawThreshold: Int
|
||||
vaultKeyHash: String
|
||||
vaultKeyHashUpdatedAt: Date
|
||||
walletsUpdatedAt: Date
|
||||
}
|
||||
|
||||
|
@ -48,6 +48,7 @@ ${STREAK_FIELDS}
|
||||
wildWestMode
|
||||
disableFreebies
|
||||
vaultKeyHash
|
||||
vaultKeyHashUpdatedAt
|
||||
walletsUpdatedAt
|
||||
showPassphrase
|
||||
}
|
||||
|
@ -250,3 +250,13 @@ export const truncateString = (str, maxLength, suffix = ' ...') => {
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export function formatBytes (bytes) {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
let index = 0
|
||||
while (bytes >= 1024 && index < units.length - 1) {
|
||||
bytes /= 1024
|
||||
index++
|
||||
}
|
||||
return `${bytes.toFixed(2)} ${units[index]}`
|
||||
}
|
||||
|
15
pages/wallets/debug.js
Normal file
15
pages/wallets/debug.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { getGetServerSideProps } from '@/api/ssrApollo'
|
||||
import { WalletLayout, WalletLayoutHeader, WalletDebugSettings } from '@/wallets/client/components'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps({})
|
||||
|
||||
export default function WalletDebug () {
|
||||
return (
|
||||
<WalletLayout>
|
||||
<div className='py-5 mx-auto w-100' style={{ maxWidth: '600px' }}>
|
||||
<WalletLayoutHeader>wallet debug</WalletLayoutHeader>
|
||||
<WalletDebugSettings />
|
||||
</div>
|
||||
</WalletLayout>
|
||||
)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
-- AlterTable
|
||||
ALTER TABLE "users" ADD COLUMN "vaultKeyHashUpdatedAt" TIMESTAMP(3);
|
||||
|
@ -145,6 +145,7 @@ model User {
|
||||
oneDayReferrals OneDayReferral[] @relation("OneDayReferral_referrer")
|
||||
oneDayReferrees OneDayReferral[] @relation("OneDayReferral_referrees")
|
||||
vaultKeyHash String @default("")
|
||||
vaultKeyHashUpdatedAt DateTime?
|
||||
showPassphrase Boolean @default(true)
|
||||
walletsUpdatedAt DateTime?
|
||||
proxyReceive Boolean @default(true)
|
||||
|
86
wallets/client/components/debug.js
Normal file
86
wallets/client/components/debug.js
Normal file
@ -0,0 +1,86 @@
|
||||
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 { timeSince } from '@/lib/time'
|
||||
|
||||
export function WalletDebugSettings () {
|
||||
const localKeyHash = useKeyHash()
|
||||
const localKeyUpdatedAt = useKeyUpdatedAt()
|
||||
const remoteKeyHash = useRemoteKeyHash()
|
||||
const remoteKeyHashUpdatedAt = useRemoteKeyHashUpdatedAt()
|
||||
const walletsUpdatedAt = useWalletsUpdatedAt()
|
||||
const [persistent, setPersistent] = useState(null)
|
||||
const [quota, setQuota] = useState(null)
|
||||
const [usage, setUsage] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
async function init () {
|
||||
try {
|
||||
const persistent = await navigator.storage.persisted()
|
||||
setPersistent(persistent)
|
||||
} catch (err) {
|
||||
console.error('failed to check persistent storage:', err)
|
||||
}
|
||||
try {
|
||||
const estimate = await navigator.storage.estimate()
|
||||
setQuota(estimate.quota)
|
||||
setUsage(estimate.usage)
|
||||
} catch (err) {
|
||||
console.error('failed to get estimate:', err)
|
||||
}
|
||||
}
|
||||
init()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className='container text-muted mt-5'>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>persistent storage:</div>
|
||||
<div className='col'>{persistent !== null ? persistent?.toString() : 'unknown'}</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>storage quota:</div>
|
||||
<div className='col'>{quota !== null ? formatBytes(quota) : 'unknown'}</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>storage usage:</div>
|
||||
<div className='col'>{usage !== null ? formatBytes(usage) : 'unknown'}</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>storage remaining:</div>
|
||||
<div className='col'>{usage !== null && quota !== null ? formatBytes(quota - usage) : 'unknown'}</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>device key hash:</div>
|
||||
<div className='col'>{localKeyHash ? shortHash(localKeyHash) : 'unknown'}</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>server key hash:</div>
|
||||
<div className='col'>{remoteKeyHash ? shortHash(remoteKeyHash) : 'unknown'}</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>last device key update:</div>
|
||||
<div className='col' suppressHydrationWarning>
|
||||
{localKeyUpdatedAt ? timeSince(localKeyUpdatedAt) : 'unknown'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>last server key update:</div>
|
||||
<div className='col' suppressHydrationWarning>
|
||||
{remoteKeyHashUpdatedAt ? timeSince(new Date(remoteKeyHashUpdatedAt).getTime()) : 'unknown'}
|
||||
</div>
|
||||
</div>
|
||||
<div className='row'>
|
||||
<div className='col text-nowrap'>last wallet update:</div>
|
||||
<div className='col' suppressHydrationWarning>
|
||||
{walletsUpdatedAt ? timeSince(new Date(walletsUpdatedAt).getTime()) : 'unknown'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
function shortHash (hash) {
|
||||
return hash.slice(0, 6) + '...' + hash.slice(-6)
|
||||
}
|
@ -4,3 +4,4 @@ export * from './forms'
|
||||
export * from './layout'
|
||||
export * from './passphrase'
|
||||
export * from './logger'
|
||||
export * from './debug'
|
||||
|
@ -160,7 +160,7 @@ export function useKeyInit () {
|
||||
const { key: randomKey, hash: randomHash } = await generateRandomKey()
|
||||
|
||||
// run read and write in one transaction to avoid race conditions
|
||||
const { key, hash } = await new Promise((resolve, reject) => {
|
||||
const { key, hash, updatedAt } = await new Promise((resolve, reject) => {
|
||||
const tx = db.transaction('vault', 'readwrite')
|
||||
const read = tx.objectStore('vault').get('key')
|
||||
|
||||
@ -180,7 +180,8 @@ export function useKeyInit () {
|
||||
}
|
||||
|
||||
// no key found, write and return generated random key
|
||||
const write = tx.objectStore('vault').put({ key: randomKey, hash: randomHash }, 'key')
|
||||
const updatedAt = Date.now()
|
||||
const write = tx.objectStore('vault').put({ key: randomKey, hash: randomHash, updatedAt }, 'key')
|
||||
|
||||
write.onerror = () => {
|
||||
reject(write.error)
|
||||
@ -188,12 +189,12 @@ export function useKeyInit () {
|
||||
|
||||
write.onsuccess = (event) => {
|
||||
// return key+hash we just wrote to db
|
||||
resolve({ key: randomKey, hash: randomHash })
|
||||
resolve({ key: randomKey, hash: randomHash, updatedAt })
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
await setKey({ key, hash })
|
||||
await setKey({ key, hash, updatedAt }, { updateDb: false })
|
||||
} catch (err) {
|
||||
console.error('key init failed:', err)
|
||||
}
|
||||
|
@ -41,6 +41,11 @@ export function useKeyHash () {
|
||||
return keyHash
|
||||
}
|
||||
|
||||
export function useKeyUpdatedAt () {
|
||||
const { keyUpdatedAt } = useContext(WalletsContext)
|
||||
return keyUpdatedAt
|
||||
}
|
||||
|
||||
export function useKeyError () {
|
||||
const { keyError } = useContext(WalletsContext)
|
||||
return keyError
|
||||
@ -54,6 +59,7 @@ export default function WalletsProvider ({ children }) {
|
||||
templates: [],
|
||||
key: null,
|
||||
keyHash: null,
|
||||
keyUpdatedAt: null,
|
||||
keyError: null
|
||||
})
|
||||
|
||||
|
@ -40,7 +40,8 @@ export default function reducer (state, action) {
|
||||
return {
|
||||
...state,
|
||||
key: action.key,
|
||||
keyHash: action.hash
|
||||
keyHash: action.hash,
|
||||
keyUpdatedAt: action.updatedAt
|
||||
}
|
||||
case WRONG_KEY:
|
||||
return {
|
||||
|
@ -42,10 +42,13 @@ export function useSetKey () {
|
||||
const dispatch = useWalletsDispatch()
|
||||
const updateKeyHash = useUpdateKeyHash()
|
||||
|
||||
return useCallback(async ({ key, hash }) => {
|
||||
await set('vault', 'key', { key, hash })
|
||||
return useCallback(async ({ key, hash, updatedAt }, { updateDb = true } = {}) => {
|
||||
if (updateDb) {
|
||||
updatedAt = updatedAt ?? Date.now()
|
||||
await set('vault', 'key', { key, hash, updatedAt })
|
||||
}
|
||||
await updateKeyHash(hash)
|
||||
dispatch({ type: SET_KEY, key, hash })
|
||||
dispatch({ type: SET_KEY, key, hash, updatedAt })
|
||||
}, [set, dispatch, updateKeyHash])
|
||||
}
|
||||
|
||||
@ -86,6 +89,11 @@ export function useRemoteKeyHash () {
|
||||
return me?.privates?.vaultKeyHash
|
||||
}
|
||||
|
||||
export function useRemoteKeyHashUpdatedAt () {
|
||||
const { me } = useMe()
|
||||
return me?.privates?.vaultKeyHashUpdatedAt
|
||||
}
|
||||
|
||||
export function useIsWrongKey () {
|
||||
const localHash = useKeyHash()
|
||||
const remoteHash = useRemoteKeyHash()
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
UPDATE_KEY_HASH
|
||||
} from '@/wallets/client/fragments'
|
||||
import { useApolloClient, useMutation, useQuery } from '@apollo/client'
|
||||
import { useDecryption, useEncryption, useSetKey, useWalletLogger, WalletStatus } from '@/wallets/client/hooks'
|
||||
import { useDecryption, useEncryption, useSetKey, useWalletLogger, useWalletsUpdatedAt, WalletStatus } from '@/wallets/client/hooks'
|
||||
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import {
|
||||
isEncryptedField, isTemplate, isWallet, protocolAvailable, protocolClientSchema, reverseProtocolRelationName
|
||||
@ -109,12 +109,13 @@ function undoFieldAlias ({ id, ...wallet }) {
|
||||
|
||||
function useRefetchOnChange (refetch) {
|
||||
const { me } = useMe()
|
||||
const walletsUpdatedAt = useWalletsUpdatedAt()
|
||||
|
||||
useEffect(() => {
|
||||
if (!me?.id) return
|
||||
|
||||
refetch()
|
||||
}, [refetch, me?.id, me?.privates?.walletsUpdatedAt])
|
||||
}, [refetch, me?.id, walletsUpdatedAt])
|
||||
}
|
||||
|
||||
export function useWalletQuery ({ id, name }) {
|
||||
|
@ -1,3 +1,4 @@
|
||||
import { useMe } from '@/components/me'
|
||||
import { useWallets } from '@/wallets/client/context'
|
||||
import protocols from '@/wallets/client/protocols'
|
||||
import { isWallet } from '@/wallets/lib/util'
|
||||
@ -47,3 +48,8 @@ export function useWalletStatus (wallet) {
|
||||
|
||||
return useMemo(() => ({ send: wallet.send, receive: wallet.receive }), [wallet])
|
||||
}
|
||||
|
||||
export function useWalletsUpdatedAt () {
|
||||
const { me } = useMe()
|
||||
return me?.privates?.walletsUpdatedAt
|
||||
}
|
||||
|
@ -142,7 +142,11 @@ async function updateWalletEncryption (parent, { keyHash, wallets }, { me, model
|
||||
// make sure the user's vault key didn't change while we were updating the protocols
|
||||
await tx.user.update({
|
||||
where: { id: me.id, vaultKeyHash: oldKeyHash },
|
||||
data: { vaultKeyHash: keyHash, showPassphrase: false }
|
||||
data: {
|
||||
vaultKeyHash: keyHash,
|
||||
showPassphrase: false,
|
||||
vaultKeyHashUpdatedAt: new Date()
|
||||
}
|
||||
})
|
||||
|
||||
return true
|
||||
@ -154,7 +158,7 @@ async function updateKeyHash (parent, { keyHash }, { me, models }) {
|
||||
|
||||
const count = await models.$executeRaw`
|
||||
UPDATE users
|
||||
SET "vaultKeyHash" = ${keyHash}
|
||||
SET "vaultKeyHash" = ${keyHash}, "vaultKeyHashUpdatedAt" = NOW()
|
||||
WHERE id = ${me.id}
|
||||
AND "vaultKeyHash" = ''
|
||||
`
|
||||
@ -184,7 +188,11 @@ async function resetWallets (parent, { newKeyHash }, { me, models }) {
|
||||
await tx.user.update({
|
||||
where: { id: me.id, vaultKeyHash: oldHash },
|
||||
// TODO(wallet-v2): nullable vaultKeyHash column
|
||||
data: { vaultKeyHash: newKeyHash, showPassphrase: true }
|
||||
data: {
|
||||
vaultKeyHash: newKeyHash,
|
||||
showPassphrase: true,
|
||||
vaultKeyHashUpdatedAt: new Date()
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user