diff --git a/components/logger.js b/components/logger.js
index 9f2923c1..45ac1607 100644
--- a/components/logger.js
+++ b/components/logger.js
@@ -1,11 +1,6 @@
-import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
+import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react'
import { useMe } from './me'
import fancyNames from '@/lib/fancy-names.json'
-import { gql, useMutation, useQuery } from '@apollo/client'
-import { WALLET_LOGS } from '@/fragments/wallet'
-import { getWalletBy } from '@/lib/constants'
-// TODO: why can't I import this without errors?
-// import { getWalletByName } from './wallet'
const generateFancyName = () => {
// 100 adjectives * 100 nouns * 10000 = 100M possible names
@@ -46,9 +41,7 @@ export const LoggerContext = createContext()
export const LoggerProvider = ({ children }) => {
return (
-
- {children}
-
+ {children}
)
}
@@ -124,191 +117,3 @@ function ServiceWorkerLoggerProvider ({ children }) {
export function useServiceWorkerLogger () {
return useContext(ServiceWorkerLoggerContext)
}
-
-const WalletLoggerContext = createContext()
-const WalletLogsContext = createContext()
-
-const initIndexedDB = async (dbName, storeName) => {
- return new Promise((resolve, reject) => {
- if (!window.indexedDB) {
- return reject(new Error('IndexedDB not supported'))
- }
-
- // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
- const request = window.indexedDB.open(dbName, 1)
-
- let db
- request.onupgradeneeded = () => {
- // this only runs if version was changed during open
- db = request.result
- if (!db.objectStoreNames.contains(storeName)) {
- const objectStore = db.createObjectStore(storeName, { autoIncrement: true })
- objectStore.createIndex('ts', 'ts')
- objectStore.createIndex('wallet_ts', ['wallet', 'ts'])
- }
- }
-
- request.onsuccess = () => {
- // this gets called after onupgradeneeded finished
- db = request.result
- resolve(db)
- }
-
- request.onerror = () => {
- reject(new Error('failed to open IndexedDB'))
- }
- })
-}
-
-const WalletLoggerProvider = ({ children }) => {
- const me = useMe()
- const [logs, setLogs] = useState([])
- let dbName = 'app:storage'
- if (me) {
- dbName = `${dbName}:${me.id}`
- }
- const idbStoreName = 'wallet_logs'
- const idb = useRef()
- const logQueue = useRef([])
-
- useQuery(WALLET_LOGS, {
- fetchPolicy: 'network-only',
- // required to trigger onCompleted on refetches
- notifyOnNetworkStatusChange: true,
- onCompleted: ({ walletLogs }) => {
- setLogs((prevLogs) => {
- const existingIds = prevLogs.map(({ id }) => id)
- const logs = walletLogs
- .filter(({ id }) => !existingIds.includes(id))
- .map(({ createdAt, wallet: walletType, ...log }) => {
- return {
- ts: +new Date(createdAt),
- wallet: getWalletBy('type', walletType).logTag,
- ...log
- }
- })
- return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
- })
- }
- })
-
- const [deleteServerWalletLogs] = useMutation(
- gql`
- mutation deleteWalletLogs($wallet: String) {
- deleteWalletLogs(wallet: $wallet)
- }
- `,
- {
- onCompleted: (_, { variables: { wallet: walletType } }) => {
- setLogs((logs) => {
- return logs.filter(l => walletType ? l.wallet !== getWalletBy('type', walletType).logTag : false)
- })
- }
- }
- )
-
- const saveLog = useCallback((log) => {
- if (!idb.current) {
- // IDB may not be ready yet
- return logQueue.current.push(log)
- }
- const tx = idb.current.transaction(idbStoreName, 'readwrite')
- const request = tx.objectStore(idbStoreName).add(log)
- request.onerror = () => console.error('failed to save log:', log)
- }, [])
-
- useEffect(() => {
- initIndexedDB(dbName, idbStoreName)
- .then(db => {
- idb.current = db
-
- // load all logs from IDB
- const tx = idb.current.transaction(idbStoreName, 'readonly')
- const store = tx.objectStore(idbStoreName)
- const index = store.index('ts')
- const request = index.getAll()
- request.onsuccess = () => {
- const logs = request.result
- setLogs((prevLogs) => {
- // sort oldest first to keep same order as logs are appended
- return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
- })
- }
-
- // flush queued logs to IDB
- logQueue.current.forEach(q => {
- const isLog = !!q.wallet
- if (isLog) saveLog(q)
- })
-
- logQueue.current = []
- })
- .catch(console.error)
- return () => idb.current?.close()
- }, [])
-
- const appendLog = useCallback((walletName, level, message) => {
- const log = { wallet: walletName, level, message, ts: +new Date() }
- saveLog(log)
- setLogs((prevLogs) => [...prevLogs, log])
- }, [saveLog])
-
- const deleteLogs = useCallback(async (walletName) => {
- const wallet = getWalletByName(walletName, me)
-
- if (!walletName || wallet.server) {
- await deleteServerWalletLogs({ variables: { wallet: wallet?.type } })
- }
- if (!walletName || !wallet.server) {
- const tx = idb.current.transaction(idbStoreName, 'readwrite')
- const objectStore = tx.objectStore(idbStoreName)
- const idx = objectStore.index('wallet_ts')
- const request = walletName ? idx.openCursor(window.IDBKeyRange.bound([walletName, -Infinity], [walletName, Infinity])) : idx.openCursor()
- request.onsuccess = function (event) {
- const cursor = event.target.result
- if (cursor) {
- cursor.delete()
- cursor.continue()
- } else {
- // finished
- setLogs((logs) => logs.filter(l => walletName ? l.wallet !== walletName : false))
- }
- }
- }
- }, [me, setLogs])
-
- return (
-
-
- {children}
-
-
- )
-}
-
-export function useWalletLogger (walletName) {
- const { appendLog, deleteLogs: innerDeleteLogs } = useContext(WalletLoggerContext)
-
- const log = useCallback(level => message => {
- // TODO:
- // also send this to us if diagnostics was enabled,
- // very similar to how the service worker logger works.
- appendLog(walletName, level, message)
- console[level !== 'error' ? 'info' : 'error'](`[${walletName}]`, message)
- }, [appendLog, walletName])
-
- const logger = useMemo(() => ({
- ok: (...message) => log('ok')(message.join(' ')),
- info: (...message) => log('info')(message.join(' ')),
- error: (...message) => log('error')(message.join(' '))
- }), [log, walletName])
-
- const deleteLogs = useCallback((w) => innerDeleteLogs(w || walletName), [innerDeleteLogs, walletName])
-
- return { logger, deleteLogs }
-}
-
-export function useWalletLogs (walletName) {
- const logs = useContext(WalletLogsContext)
- return logs.filter(l => !walletName || l.wallet === walletName)
-}
diff --git a/components/nav/common.js b/components/nav/common.js
index 667a34db..d04cf3f2 100644
--- a/components/nav/common.js
+++ b/components/nav/common.js
@@ -22,8 +22,7 @@ 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 { useWalletLogger } from '@/components/logger'
-// import { useWallet } from '@/components/wallet'
+import { useWalletLogger } from '@/components/wallet-logger'
export function Brand ({ className }) {
return (
diff --git a/components/wallet-logger.js b/components/wallet-logger.js
new file mode 100644
index 00000000..27993913
--- /dev/null
+++ b/components/wallet-logger.js
@@ -0,0 +1,314 @@
+import { useRouter } from 'next/router'
+import LogMessage from './log-message'
+import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
+import { Checkbox, Form } from './form'
+import { useField } from 'formik'
+import styles from '@/styles/log.module.css'
+import { Button } from 'react-bootstrap'
+import { useToast } from './toast'
+import { useShowModal } from './modal'
+import { WALLET_LOGS } from '@/fragments/wallet'
+import { getWalletByName } from './wallet'
+import { gql, useMutation, useQuery } from '@apollo/client'
+import { useMe } from './me'
+
+const FollowCheckbox = ({ value, ...props }) => {
+ const [,, helpers] = useField(props.name)
+
+ useEffect(() => {
+ helpers.setValue(value)
+ }, [value])
+
+ return (
+
+ )
+}
+
+export function WalletLogs ({ wallet, embedded }) {
+ const logs = useWalletLogs(wallet)
+
+ const router = useRouter()
+ const { follow: defaultFollow } = router.query
+ const [follow, setFollow] = useState(defaultFollow ?? true)
+ const tableRef = useRef()
+ const scrollY = useRef()
+ const showModal = useShowModal()
+
+ useEffect(() => {
+ if (follow) {
+ tableRef.current?.scroll({ top: tableRef.current.scrollHeight, behavior: 'smooth' })
+ }
+ }, [logs, follow])
+
+ useEffect(() => {
+ function onScroll (e) {
+ const y = e.target.scrollTop
+
+ const down = y - scrollY.current >= -1
+ if (!!scrollY.current && !down) {
+ setFollow(false)
+ }
+
+ const maxY = e.target.scrollHeight - e.target.clientHeight
+ const dY = maxY - y
+ const isBottom = dY >= -1 && dY <= 1
+ if (isBottom) {
+ setFollow(true)
+ }
+
+ scrollY.current = y
+ }
+ tableRef.current?.addEventListener('scroll', onScroll)
+ return () => tableRef.current?.removeEventListener('scroll', onScroll)
+ }, [])
+
+ return (
+ <>
+
+
+ {
+ showModal(onClose => )
+ }}
+ >clear
+
+
+
+
------ start of logs ------
+ {logs.length === 0 &&
empty
}
+
+
+ {logs.map((log, i) => )}
+
+
+
+ >
+ )
+}
+
+function DeleteWalletLogsObstacle ({ wallet, onClose }) {
+ const toaster = useToast()
+ const { deleteLogs } = useWalletLogger(wallet)
+
+ const prompt = `Do you really want to delete all ${wallet ? '' : 'wallet'} logs ${wallet ? 'of this wallet' : ''}?`
+ return (
+
+ {prompt}
+
+ cancel
+
+
+
+ )
+}
+
+const WalletLoggerContext = createContext()
+const WalletLogsContext = createContext()
+
+const initIndexedDB = async (dbName, storeName) => {
+ return new Promise((resolve, reject) => {
+ if (!window.indexedDB) {
+ return reject(new Error('IndexedDB not supported'))
+ }
+
+ // https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API/Using_IndexedDB
+ const request = window.indexedDB.open(dbName, 1)
+
+ let db
+ request.onupgradeneeded = () => {
+ // this only runs if version was changed during open
+ db = request.result
+ if (!db.objectStoreNames.contains(storeName)) {
+ const objectStore = db.createObjectStore(storeName, { autoIncrement: true })
+ objectStore.createIndex('ts', 'ts')
+ objectStore.createIndex('wallet_ts', ['wallet', 'ts'])
+ }
+ }
+
+ request.onsuccess = () => {
+ // this gets called after onupgradeneeded finished
+ db = request.result
+ resolve(db)
+ }
+
+ request.onerror = () => {
+ reject(new Error('failed to open IndexedDB'))
+ }
+ })
+}
+
+export const WalletLoggerProvider = ({ children }) => {
+ const me = useMe()
+ const [logs, setLogs] = useState([])
+ let dbName = 'app:storage'
+ if (me) {
+ dbName = `${dbName}:${me.id}`
+ }
+ const idbStoreName = 'wallet_logs'
+ const idb = useRef()
+ const logQueue = useRef([])
+
+ useQuery(WALLET_LOGS, {
+ fetchPolicy: 'network-only',
+ // required to trigger onCompleted on refetches
+ notifyOnNetworkStatusChange: true,
+ onCompleted: ({ walletLogs }) => {
+ setLogs((prevLogs) => {
+ const existingIds = prevLogs.map(({ id }) => id)
+ const logs = walletLogs
+ .filter(({ id }) => !existingIds.includes(id))
+ .map(({ createdAt, wallet: walletType, ...log }) => {
+ return {
+ ts: +new Date(createdAt),
+ // TODO: use wallet defs
+ // wallet: getWalletBy('type', walletType).logTag,
+ ...log
+ }
+ })
+ return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
+ })
+ }
+ })
+
+ const [deleteServerWalletLogs] = useMutation(
+ gql`
+ mutation deleteWalletLogs($wallet: String) {
+ deleteWalletLogs(wallet: $wallet)
+ }
+ `,
+ {
+ onCompleted: (_, { variables: { wallet: walletType } }) => {
+ setLogs((logs) => {
+ // TODO: use wallet defs
+ return logs.filter(l => walletType ? l.wallet !== getWalletByName('type', walletType) : false)
+ })
+ }
+ }
+ )
+
+ const saveLog = useCallback((log) => {
+ if (!idb.current) {
+ // IDB may not be ready yet
+ return logQueue.current.push(log)
+ }
+ const tx = idb.current.transaction(idbStoreName, 'readwrite')
+ const request = tx.objectStore(idbStoreName).add(log)
+ request.onerror = () => console.error('failed to save log:', log)
+ }, [])
+
+ useEffect(() => {
+ initIndexedDB(dbName, idbStoreName)
+ .then(db => {
+ idb.current = db
+
+ // load all logs from IDB
+ const tx = idb.current.transaction(idbStoreName, 'readonly')
+ const store = tx.objectStore(idbStoreName)
+ const index = store.index('ts')
+ const request = index.getAll()
+ request.onsuccess = () => {
+ const logs = request.result
+ setLogs((prevLogs) => {
+ // sort oldest first to keep same order as logs are appended
+ return [...prevLogs, ...logs].sort((a, b) => a.ts - b.ts)
+ })
+ }
+
+ // flush queued logs to IDB
+ logQueue.current.forEach(q => {
+ const isLog = !!q.wallet
+ if (isLog) saveLog(q)
+ })
+
+ logQueue.current = []
+ })
+ .catch(console.error)
+ return () => idb.current?.close()
+ }, [])
+
+ const appendLog = useCallback((walletName, level, message) => {
+ const log = { wallet: walletName, level, message, ts: +new Date() }
+ saveLog(log)
+ setLogs((prevLogs) => [...prevLogs, log])
+ }, [saveLog])
+
+ const deleteLogs = useCallback(async (walletName) => {
+ const wallet = getWalletByName(walletName, me)
+
+ if (!walletName || wallet.server) {
+ await deleteServerWalletLogs({ variables: { wallet: wallet?.type } })
+ }
+ if (!walletName || !wallet.server) {
+ const tx = idb.current.transaction(idbStoreName, 'readwrite')
+ const objectStore = tx.objectStore(idbStoreName)
+ const idx = objectStore.index('wallet_ts')
+ const request = walletName ? idx.openCursor(window.IDBKeyRange.bound([walletName, -Infinity], [walletName, Infinity])) : idx.openCursor()
+ request.onsuccess = function (event) {
+ const cursor = event.target.result
+ if (cursor) {
+ cursor.delete()
+ cursor.continue()
+ } else {
+ // finished
+ setLogs((logs) => logs.filter(l => walletName ? l.wallet !== walletName : false))
+ }
+ }
+ }
+ }, [me, setLogs])
+
+ return (
+
+
+ {children}
+
+
+ )
+}
+
+export function useWalletLogger (walletName) {
+ const { appendLog, deleteLogs: innerDeleteLogs } = useContext(WalletLoggerContext)
+
+ const log = useCallback(level => message => {
+ // TODO:
+ // also send this to us if diagnostics was enabled,
+ // very similar to how the service worker logger works.
+ appendLog(walletName, level, message)
+ console[level !== 'error' ? 'info' : 'error'](`[${walletName}]`, message)
+ }, [appendLog, walletName])
+
+ const logger = useMemo(() => ({
+ ok: (...message) => log('ok')(message.join(' ')),
+ info: (...message) => log('info')(message.join(' ')),
+ error: (...message) => log('error')(message.join(' '))
+ }), [log, walletName])
+
+ const deleteLogs = useCallback((w) => innerDeleteLogs(w || walletName), [innerDeleteLogs, walletName])
+
+ return { logger, deleteLogs }
+}
+
+export function useWalletLogs (walletName) {
+ const logs = useContext(WalletLogsContext)
+ return logs.filter(l => !walletName || l.wallet === walletName)
+}
diff --git a/components/wallet-logs.js b/components/wallet-logs.js
deleted file mode 100644
index c5cb678d..00000000
--- a/components/wallet-logs.js
+++ /dev/null
@@ -1,121 +0,0 @@
-import { useRouter } from 'next/router'
-import LogMessage from './log-message'
-import { useWalletLogger, useWalletLogs } from './logger'
-import { useEffect, useRef, useState } from 'react'
-import { Checkbox, Form } from './form'
-import { useField } from 'formik'
-import styles from '@/styles/log.module.css'
-import { Button } from 'react-bootstrap'
-import { useToast } from './toast'
-import { useShowModal } from './modal'
-
-const FollowCheckbox = ({ value, ...props }) => {
- const [,, helpers] = useField(props.name)
-
- useEffect(() => {
- helpers.setValue(value)
- }, [value])
-
- return (
-
- )
-}
-
-export default function WalletLogs ({ wallet, embedded }) {
- const logs = useWalletLogs(wallet)
-
- const router = useRouter()
- const { follow: defaultFollow } = router.query
- const [follow, setFollow] = useState(defaultFollow ?? true)
- const tableRef = useRef()
- const scrollY = useRef()
- const showModal = useShowModal()
-
- useEffect(() => {
- if (follow) {
- tableRef.current?.scroll({ top: tableRef.current.scrollHeight, behavior: 'smooth' })
- }
- }, [logs, follow])
-
- useEffect(() => {
- function onScroll (e) {
- const y = e.target.scrollTop
-
- const down = y - scrollY.current >= -1
- if (!!scrollY.current && !down) {
- setFollow(false)
- }
-
- const maxY = e.target.scrollHeight - e.target.clientHeight
- const dY = maxY - y
- const isBottom = dY >= -1 && dY <= 1
- if (isBottom) {
- setFollow(true)
- }
-
- scrollY.current = y
- }
- tableRef.current?.addEventListener('scroll', onScroll)
- return () => tableRef.current?.removeEventListener('scroll', onScroll)
- }, [])
-
- return (
- <>
-
-
- {
- showModal(onClose => )
- }}
- >clear
-
-
-
-
------ start of logs ------
- {logs.length === 0 &&
empty
}
-
-
- {logs.map((log, i) => )}
-
-
-
- >
- )
-}
-
-function DeleteWalletLogsObstacle ({ wallet, onClose }) {
- const toaster = useToast()
- const { deleteLogs } = useWalletLogger(wallet)
-
- const prompt = `Do you really want to delete all ${wallet ? '' : 'wallet'} logs ${wallet ? 'of this wallet' : ''}?`
- return (
-
- {prompt}
-
- cancel
-
-
-
- )
-}
diff --git a/components/wallet/index.js b/components/wallet/index.js
index fbddb07f..321a14ea 100644
--- a/components/wallet/index.js
+++ b/components/wallet/index.js
@@ -1,7 +1,7 @@
import { useCallback } from 'react'
import { useMe } from '@/components/me'
import useLocalState from '@/components/use-local-state'
-import { useWalletLogger } from '@/components/logger'
+import { useWalletLogger } from '@/components/wallet-logger'
import { SSR } from '@/lib/constants'
// wallet definitions
diff --git a/pages/_app.js b/pages/_app.js
index 82bd105a..9161017e 100644
--- a/pages/_app.js
+++ b/pages/_app.js
@@ -17,6 +17,7 @@ import { SSR } from '@/lib/constants'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { LoggerProvider } from '@/components/logger'
+import { WalletLoggerProvider } from '@/components/wallet-logger'
import { ChainFeeProvider } from '@/components/chain-fee.js'
import dynamic from 'next/dynamic'
import { HasNewNotesProvider } from '@/components/use-has-new-notes'
@@ -104,24 +105,26 @@ export default function MyApp ({ Component, pageProps: { ...props } }) {
-
-
-
-
-
-
-
-
-
- {!router?.query?.disablePrompt && }
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+ {!router?.query?.disablePrompt && }
+
+
+
+
+
+
+
+
+
diff --git a/pages/settings/wallets/[wallet].js b/pages/settings/wallets/[wallet].js
index de6a223d..8c82a35c 100644
--- a/pages/settings/wallets/[wallet].js
+++ b/pages/settings/wallets/[wallet].js
@@ -4,7 +4,7 @@ import { CenterLayout } from '@/components/layout'
import { WalletButtonBar } from '@/components/wallet-card'
import { lnbitsSchema } from '@/lib/validate'
import { WalletSecurityBanner } from '@/components/banners'
-import WalletLogs from '@/components/wallet-logs'
+import { WalletLogs } from '@/components/wallet-logger'
import { useToast } from '@/components/toast'
import { useRouter } from 'next/router'
import { useWallet } from '@/components/wallet'
diff --git a/pages/wallet/logs.js b/pages/wallet/logs.js
index debad4c2..86b540b5 100644
--- a/pages/wallet/logs.js
+++ b/pages/wallet/logs.js
@@ -1,6 +1,6 @@
import { CenterLayout } from '@/components/layout'
import { getGetServerSideProps } from '@/api/ssrApollo'
-import WalletLogs from '@/components/wallet-logs'
+import { WalletLogs } from '@/components/wallet-logs'
export const getServerSideProps = getGetServerSideProps({ query: null })