Files
xrpl-hooks-ide/components/DebugStream.tsx
2022-08-17 11:50:49 +05:30

180 lines
5.0 KiB
TypeScript

import { useEffect } from 'react'
import ReconnectingWebSocket, { CloseEvent } from 'reconnecting-websocket'
import { proxy, ref, useSnapshot } from 'valtio'
import { subscribeKey } from 'valtio/utils'
import { Select } from '.'
import state, { ILog, transactionsState } from '../state'
import { extractJSON } from '../utils/json'
import LogBox from './LogBox'
interface ISelect<T = string> {
label: string
value: T
}
export interface IStreamState {
selectedAccount: ISelect | null
status: 'idle' | 'opened' | 'closed'
statusChangeTimestamp?: number
logs: ILog[]
socket?: ReconnectingWebSocket
}
export const streamState = proxy<IStreamState>({
selectedAccount: null as ISelect | null,
status: 'idle',
logs: [] as ILog[]
})
const onOpen = (account: ISelect | null) => {
if (!account) {
return
}
// streamState.logs = [];
streamState.status = 'opened'
streamState.statusChangeTimestamp = Date.now()
pushLog(`Debug stream opened for account ${account?.value}`, {
type: 'success'
})
}
const onError = () => {
pushLog('Something went wrong! Check your connection and try again.', {
type: 'error'
})
}
const onClose = (e: CloseEvent) => {
// 999 = closed websocket connection by switching account
if (e.code !== 4999) {
pushLog(`Connection was closed. [code: ${e.code}]`, {
type: 'error'
})
}
streamState.status = 'closed'
streamState.statusChangeTimestamp = Date.now()
}
const onMessage = (event: any) => {
// Ping returns just account address, if we get that
// response we don't need to log anything
if (event.data !== streamState.selectedAccount?.value) {
pushLog(event.data)
}
}
let interval: NodeJS.Timer | null = null
const addListeners = (account: ISelect | null) => {
if (account?.value && streamState.socket?.url.endsWith(account?.value)) {
return
}
streamState.logs = []
if (account?.value) {
if (interval) {
clearInterval(interval)
}
if (streamState.socket) {
streamState.socket?.removeEventListener('open', () => onOpen(account))
streamState.socket?.removeEventListener('close', onClose)
streamState.socket?.removeEventListener('error', onError)
streamState.socket?.removeEventListener('message', onMessage)
}
streamState.socket = ref(
new ReconnectingWebSocket(
`wss://${process.env.NEXT_PUBLIC_DEBUG_STREAM_URL}/${account?.value}`
)
)
if (streamState.socket) {
interval = setInterval(() => {
streamState.socket?.send('')
}, 45000)
}
streamState.socket.addEventListener('open', () => onOpen(account))
streamState.socket.addEventListener('close', onClose)
streamState.socket.addEventListener('error', onError)
streamState.socket.addEventListener('message', onMessage)
}
}
subscribeKey(streamState, 'selectedAccount', addListeners)
const DebugStream = () => {
const { selectedAccount, logs } = useSnapshot(streamState)
const { activeHeader: activeTxTab } = useSnapshot(transactionsState)
const { accounts } = useSnapshot(state)
const accountOptions = accounts.map(acc => ({
label: acc.name,
value: acc.address
}))
const renderNav = () => (
<>
<Select
instanceId="DSAccount"
placeholder="Select account"
options={accountOptions}
hideSelectedOptions
value={selectedAccount}
onChange={acc => {
streamState.socket?.close(4999, 'Old connection closed because user switched account')
streamState.selectedAccount = acc as any
}}
css={{ width: '100%' }}
/>
</>
)
useEffect(() => {
const account = transactionsState.transactions.find(tx => tx.header === activeTxTab)?.state
.selectedAccount
if (account && account.value !== streamState.selectedAccount?.value)
streamState.selectedAccount = account
}, [activeTxTab])
const clearLog = () => {
streamState.logs = []
streamState.statusChangeTimestamp = Date.now()
}
return (
<LogBox enhanced renderNav={renderNav} title="Debug stream" logs={logs} clearLog={clearLog} />
)
}
export default DebugStream
export const pushLog = (str: any, opts: Partial<Pick<ILog, 'type'>> = {}): ILog | undefined => {
if (!str) return
if (typeof str !== 'string') throw Error('Unrecognized debug log stream!')
const match = str.match(/([\s\S]+(?:UTC|ISO|GMT[+|-]\d+))?\ ?([\s\S]*)/m)
const [_, tm, msg] = match || []
const timestamp = Date.parse(tm || '') || undefined
const timestring = !timestamp ? tm : new Date(timestamp).toLocaleTimeString()
const extracted = extractJSON(msg)
const message = !extracted ? msg : msg.slice(0, extracted.start) + msg.slice(extracted.end + 1)
const jsonData = extracted ? JSON.stringify(extracted.result, null, 2) : undefined
if (extracted?.result?.id?._Request?.includes('hooks-builder-req')) {
return
}
const { type = 'log' } = opts
const log: ILog = {
type,
message,
timestring,
jsonData,
defaultCollapsed: true
}
if (log) streamState.logs.push(log)
return log
}