| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- import React, { useEffect } from 'react'
- import type { ConnectionStatus } from './types'
- import { useSocketManager } from './use-socket-manager'
- import {
- ActionButton,
- ConnectionBadge,
- DiagnosticItem,
- ErrorAlert,
- } from './diagnostic-component'
- import { Col, Container, Row } from 'react-bootstrap'
- import MaterialIcon from '@/shared/components/material-icon'
- import OLFormCheckbox from '@/shared/components/ol/ol-form-checkbox'
- import { CopyToClipboard } from '@/shared/components/copy-to-clipboard'
- type NetworkInformation = {
- downlink: number
- effectiveType: string
- rtt: number
- saveData: boolean
- type: string
- }
- const navigatorInfo = (): string[] => {
- if (!('connection' in navigator)) {
- return ['Network Information API not supported']
- }
- const connection = navigator.connection as NetworkInformation
- return [
- `Downlink: ${connection.downlink} Mbps`,
- `Effective Type: ${connection.effectiveType}`,
- `Round Trip Time: ${connection.rtt} ms`,
- `Save Data: ${connection.saveData ? 'Enabled' : 'Disabled'}`,
- `Platform: ${navigator.platform}`,
- // @ts-ignore
- `Device Memory: ${navigator.deviceMemory}`,
- `Hardware Concurrency: ${navigator.hardwareConcurrency}`,
- ]
- }
- const useCurrentTime = () => {
- const [, setTime] = React.useState(new Date())
- useEffect(() => {
- const interval = setInterval(() => setTime(new Date()), 1000)
- return () => clearInterval(interval)
- }, [])
- }
- type DiagnosticProps = {
- icon: string
- label: string
- text: string[]
- type?: 'success' | 'danger'
- }
- export const SocketDiagnostics = () => {
- const {
- socketState,
- debugInfo,
- disconnectSocket,
- forceReconnect,
- socket,
- autoping,
- setAutoping,
- } = useSocketManager()
- useCurrentTime()
- const now = new Date()
- const getConnectionState = (): ConnectionStatus => {
- if (socketState.connected) return 'connected'
- if (socketState.connecting) return 'connecting'
- return 'disconnected'
- }
- const lastReceivedS = debugInfo.lastReceived
- ? Math.round((now.getTime() - debugInfo.lastReceived) / 1000)
- : null
- const isLate =
- !!debugInfo.unansweredSince &&
- now.getTime() - debugInfo.unansweredSince >= 3000
- const diagnosticProps: DiagnosticProps[] = [
- {
- icon: 'network_ping',
- label: 'Ping Count',
- text: [
- `${debugInfo.received} / ${debugInfo.sent}`,
- lastReceivedS !== null ? `Last received ${lastReceivedS}s ago` : null,
- ].filter(Boolean) as string[],
- type: isLate === null ? undefined : isLate ? 'danger' : 'success',
- },
- {
- icon: 'schedule',
- label: 'Latency',
- text: [
- debugInfo.latency
- ? `${debugInfo.latency} ms\nMax: ${debugInfo.maxLatency} ms`
- : '-',
- ],
- type: debugInfo.latency && debugInfo.latency < 450 ? 'success' : 'danger',
- },
- {
- icon: 'difference',
- label: 'Clock Delta',
- text: [
- debugInfo.clockDelta === null
- ? '-'
- : `${Math.round(debugInfo.clockDelta / 1000)}s`,
- ],
- type:
- debugInfo.clockDelta !== null && Math.abs(debugInfo.clockDelta) < 1500
- ? 'success'
- : 'danger',
- },
- {
- icon: 'signal_cellular_alt',
- label: 'Online',
- text: [debugInfo.onLine?.toString() ?? '-'],
- type: debugInfo.onLine ? 'success' : 'danger',
- },
- {
- icon: 'schedule',
- label: 'Current time',
- text: [now.toUTCString()],
- },
- {
- icon: 'hourglass',
- label: 'Connection time',
- text: [
- debugInfo.client?.connectedAt
- ? `${new Date(debugInfo.client.connectedAt).toUTCString()} (${Math.round(
- (Date.now() - debugInfo.client.connectedAt) / 1000
- )}s)`
- : '-',
- ],
- },
- {
- icon: 'local_shipping',
- label: 'Transport',
- text: [socket?.socket.transport?.name ?? '-'],
- },
- {
- icon: 'badge',
- label: 'Client Public ID',
- text: [debugInfo.client?.publicId ?? '-'],
- },
- {
- icon: 'pin',
- label: 'IP Address',
- text: [debugInfo.client?.remoteIp ?? '-'],
- },
- {
- icon: 'web',
- label: 'User agent',
- text: [debugInfo.client?.userAgent ?? '-'],
- },
- {
- icon: 'directions_boat',
- label: 'Navigator info',
- text: navigatorInfo(),
- },
- ]
- const diagnosticItems = diagnosticProps.map(item => (
- <DiagnosticItem
- key={item.label}
- icon={item.icon}
- label={item.label}
- value={item.text.map((t, i) => (
- <div key={i}>{t}</div>
- ))}
- type={item.type}
- />
- ))
- const cutAtIndex = 7
- const leftItems = diagnosticItems.slice(0, cutAtIndex)
- const rightItems = diagnosticItems.slice(cutAtIndex)
- return (
- <Container>
- <h1>Socket Diagnostics</h1>
- <ConnectionBadge state={getConnectionState()} />
- <div className="d-flex flex-wrap gap-4 mt-3 align-items-center">
- <ActionButton
- label="Reconnect"
- icon="refresh"
- onClick={forceReconnect}
- />
- <ActionButton
- label="Disconnect"
- icon="close"
- onClick={disconnectSocket}
- disabled={!socketState.connected}
- />
- <OLFormCheckbox
- label="Auto ping"
- id="autoping"
- checked={autoping}
- onChange={e => setAutoping(e.target.checked)}
- />
- </div>
- {socketState.lastError && <ErrorAlert message={socketState.lastError} />}
- <div className="card p-4 mt-3">
- <div className="d-flex flex-wrap gap-4 row-gap-1 justify-content-between align-items-center">
- <h3 className="text-lg">
- <MaterialIcon type="speed" /> Connection Stats
- </h3>
- <div className="ms-auto">
- <CopyToClipboard
- content={diagnosticProps
- .map(item => [`${item.label}:`, ...item.text].join('\n'))
- .join('\n\n')}
- tooltipId="copy-debug-info"
- kind="text"
- />
- </div>
- </div>
- <Row>
- <Col md={6}>{leftItems}</Col>
- <Col md={6}>{rightItems}</Col>
- </Row>
- </div>
- </Container>
- )
- }
|