|
|
@@ -7,9 +7,10 @@ import {
|
|
|
DiagnosticItem,
|
|
|
ErrorAlert,
|
|
|
} from './diagnostic-component'
|
|
|
-import { Container } from 'react-bootstrap-5'
|
|
|
+import { Col, Container, Row } from 'react-bootstrap-5'
|
|
|
import MaterialIcon from '@/shared/components/material-icon'
|
|
|
import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
|
|
|
+import { CopyToClipboard } from '@/shared/components/copy-to-clipboard'
|
|
|
|
|
|
type NetworkInformation = {
|
|
|
downlink: number
|
|
|
@@ -19,33 +20,38 @@ type NetworkInformation = {
|
|
|
type: string
|
|
|
}
|
|
|
|
|
|
-const NavigatorInfo = () => {
|
|
|
+const navigatorInfo = (): string[] => {
|
|
|
if (!('connection' in navigator)) {
|
|
|
- return <div>Network Information API not supported</div>
|
|
|
+ return ['Network Information API not supported']
|
|
|
}
|
|
|
|
|
|
const connection = navigator.connection as NetworkInformation
|
|
|
- return (
|
|
|
- <>
|
|
|
- <div>Downlink: {connection.downlink} Mbps</div>
|
|
|
- <div>Effective Type: {connection.effectiveType}</div>
|
|
|
- <div>Round Trip Time: {connection.rtt} ms</div>
|
|
|
- <div>Save Data: {connection.saveData ? 'Enabled' : 'Disabled'}</div>
|
|
|
- <div>Platform: {navigator.platform}</div>
|
|
|
- {/* @ts-ignore */}
|
|
|
- <div>Device Memory: {navigator.deviceMemory}</div>
|
|
|
- <div>Hardware Concurrency: {navigator.hardwareConcurrency}</div>
|
|
|
- </>
|
|
|
- )
|
|
|
+
|
|
|
+ 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 [time, setTime] = React.useState(new Date())
|
|
|
+ const [, setTime] = React.useState(new Date())
|
|
|
useEffect(() => {
|
|
|
const interval = setInterval(() => setTime(new Date()), 1000)
|
|
|
return () => clearInterval(interval)
|
|
|
}, [])
|
|
|
- return time
|
|
|
+}
|
|
|
+
|
|
|
+type DiagnosticProps = {
|
|
|
+ icon: string
|
|
|
+ label: string
|
|
|
+ text: string[]
|
|
|
+ type?: 'success' | 'danger'
|
|
|
}
|
|
|
|
|
|
export const SocketDiagnostics = () => {
|
|
|
@@ -58,7 +64,9 @@ export const SocketDiagnostics = () => {
|
|
|
autoping,
|
|
|
setAutoping,
|
|
|
} = useSocketManager()
|
|
|
- const now = useCurrentTime()
|
|
|
+ useCurrentTime()
|
|
|
+
|
|
|
+ const now = new Date()
|
|
|
|
|
|
const getConnectionState = (): ConnectionStatus => {
|
|
|
if (socketState.connected) return 'connected'
|
|
|
@@ -74,12 +82,108 @@ export const SocketDiagnostics = () => {
|
|
|
!!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 gap-2 mt-3">
|
|
|
+ <div className="d-flex flex-wrap gap-4 mt-3 align-items-center">
|
|
|
<ActionButton
|
|
|
label="Reconnect"
|
|
|
icon="refresh"
|
|
|
@@ -91,132 +195,35 @@ export const SocketDiagnostics = () => {
|
|
|
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">
|
|
|
- <h3 className="text-lg">
|
|
|
- <MaterialIcon type="speed" /> Connection Stats
|
|
|
- </h3>
|
|
|
- <div className="space-y-2">
|
|
|
- <OLFormCheckbox
|
|
|
- label="Auto ping"
|
|
|
- id="autoping"
|
|
|
- checked={autoping}
|
|
|
- onChange={e => setAutoping(e.target.checked)}
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="network_ping"
|
|
|
- label="Ping Count"
|
|
|
- value={
|
|
|
- <>
|
|
|
- {debugInfo.received} / {debugInfo.sent}
|
|
|
- {lastReceivedS !== null && (
|
|
|
- <>
|
|
|
- <br />
|
|
|
- Last received {lastReceivedS}s ago
|
|
|
- </>
|
|
|
- )}
|
|
|
- </>
|
|
|
- }
|
|
|
- type={isLate === null ? undefined : isLate ? 'danger' : 'success'}
|
|
|
- />
|
|
|
-
|
|
|
- <DiagnosticItem
|
|
|
- icon="schedule"
|
|
|
- label="Latency"
|
|
|
- value={
|
|
|
- debugInfo.latency ? (
|
|
|
- <>
|
|
|
- {debugInfo.latency} ms
|
|
|
- <br />
|
|
|
- Max: {debugInfo.maxLatency} ms
|
|
|
- </>
|
|
|
- ) : (
|
|
|
- '-'
|
|
|
- )
|
|
|
- }
|
|
|
- type={
|
|
|
- debugInfo.latency
|
|
|
- ? debugInfo.latency < 450
|
|
|
- ? 'success'
|
|
|
- : 'danger'
|
|
|
- : undefined
|
|
|
- }
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="difference"
|
|
|
- label="Clock Delta"
|
|
|
- value={
|
|
|
- debugInfo.clockDelta === null
|
|
|
- ? '-'
|
|
|
- : `${Math.round(debugInfo.clockDelta / 1000)}s`
|
|
|
- }
|
|
|
- type={
|
|
|
- debugInfo.clockDelta !== null
|
|
|
- ? Math.abs(debugInfo.clockDelta) < 1500
|
|
|
- ? 'success'
|
|
|
- : 'danger'
|
|
|
- : undefined
|
|
|
- }
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="signal_cellular_alt"
|
|
|
- label="Online"
|
|
|
- value={debugInfo.onLine?.toString() ?? '-'}
|
|
|
- type={debugInfo.onLine ? 'success' : 'danger'}
|
|
|
- />
|
|
|
-
|
|
|
- <DiagnosticItem
|
|
|
- icon="schedule"
|
|
|
- label="Current time"
|
|
|
- value={now.toUTCString()}
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="hourglass"
|
|
|
- label="Connection time"
|
|
|
- value={
|
|
|
- debugInfo.client?.connectedAt ? (
|
|
|
- <>
|
|
|
- {new Date(debugInfo.client.connectedAt).toUTCString()} (
|
|
|
- {Math.round(
|
|
|
- (Date.now() - debugInfo.client.connectedAt) / 1000
|
|
|
- )}
|
|
|
- s)
|
|
|
- </>
|
|
|
- ) : (
|
|
|
- '-'
|
|
|
- )
|
|
|
- }
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="local_shipping"
|
|
|
- label="Transport"
|
|
|
- value={socket?.socket.transport?.name ?? '-'}
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="badge"
|
|
|
- label="Client Public ID"
|
|
|
- value={debugInfo.client?.publicId ?? '-'}
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="pin"
|
|
|
- label="IP Address"
|
|
|
- value={debugInfo.client?.remoteIp ?? '-'}
|
|
|
- />
|
|
|
- <DiagnosticItem
|
|
|
- icon="web"
|
|
|
- label="User agent"
|
|
|
- value={debugInfo.client?.userAgent ?? '-'}
|
|
|
- />
|
|
|
-
|
|
|
- <DiagnosticItem
|
|
|
- icon="directions_boat"
|
|
|
- label="Navigator info"
|
|
|
- value={<NavigatorInfo />}
|
|
|
- />
|
|
|
+ <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>
|
|
|
)
|