socket-diagnostics.tsx 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. import React, { useEffect } from 'react'
  2. import type { ConnectionStatus } from './types'
  3. import { useSocketManager } from './use-socket-manager'
  4. import {
  5. ActionButton,
  6. ConnectionBadge,
  7. DiagnosticItem,
  8. ErrorAlert,
  9. } from './diagnostic-component'
  10. import { Container } from 'react-bootstrap-5'
  11. import MaterialIcon from '@/shared/components/material-icon'
  12. import OLFormCheckbox from '@/features/ui/components/ol/ol-form-checkbox'
  13. type NetworkInformation = {
  14. downlink: number
  15. effectiveType: string
  16. rtt: number
  17. saveData: boolean
  18. type: string
  19. }
  20. const NavigatorInfo = () => {
  21. if (!('connection' in navigator)) {
  22. return <div>Network Information API not supported</div>
  23. }
  24. const connection = navigator.connection as NetworkInformation
  25. return (
  26. <>
  27. <div>Downlink: {connection.downlink} Mbps</div>
  28. <div>Effective Type: {connection.effectiveType}</div>
  29. <div>Round Trip Time: {connection.rtt} ms</div>
  30. <div>Save Data: {connection.saveData ? 'Enabled' : 'Disabled'}</div>
  31. <div>Platform: {navigator.platform}</div>
  32. {/* @ts-ignore */}
  33. <div>Device Memory: {navigator.deviceMemory}</div>
  34. <div>Hardware Concurrency: {navigator.hardwareConcurrency}</div>
  35. </>
  36. )
  37. }
  38. const useCurrentTime = () => {
  39. const [time, setTime] = React.useState(new Date())
  40. useEffect(() => {
  41. const interval = setInterval(() => setTime(new Date()), 1000)
  42. return () => clearInterval(interval)
  43. }, [])
  44. return time
  45. }
  46. export const SocketDiagnostics = () => {
  47. const {
  48. socketState,
  49. debugInfo,
  50. disconnectSocket,
  51. forceReconnect,
  52. socket,
  53. autoping,
  54. setAutoping,
  55. } = useSocketManager()
  56. const now = useCurrentTime()
  57. const getConnectionState = (): ConnectionStatus => {
  58. if (socketState.connected) return 'connected'
  59. if (socketState.connecting) return 'connecting'
  60. return 'disconnected'
  61. }
  62. const lastReceivedS = debugInfo.lastReceived
  63. ? Math.round((now.getTime() - debugInfo.lastReceived) / 1000)
  64. : null
  65. const isLate =
  66. !!debugInfo.unansweredSince &&
  67. now.getTime() - debugInfo.unansweredSince >= 3000
  68. return (
  69. <Container>
  70. <h1>Socket Diagnostics</h1>
  71. <ConnectionBadge state={getConnectionState()} />
  72. <div className="d-flex gap-2 mt-3">
  73. <ActionButton
  74. label="Reconnect"
  75. icon="refresh"
  76. onClick={forceReconnect}
  77. />
  78. <ActionButton
  79. label="Disconnect"
  80. icon="close"
  81. onClick={disconnectSocket}
  82. disabled={!socketState.connected}
  83. />
  84. </div>
  85. {socketState.lastError && <ErrorAlert message={socketState.lastError} />}
  86. <div className="card p-4 mt-3">
  87. <h3 className="text-lg">
  88. <MaterialIcon type="speed" /> Connection Stats
  89. </h3>
  90. <div className="space-y-2">
  91. <OLFormCheckbox
  92. label="Auto ping"
  93. id="autoping"
  94. checked={autoping}
  95. onChange={e => setAutoping(e.target.checked)}
  96. />
  97. <DiagnosticItem
  98. icon="network_ping"
  99. label="Ping Count"
  100. value={
  101. <>
  102. {debugInfo.received} / {debugInfo.sent}
  103. {lastReceivedS !== null && (
  104. <>
  105. <br />
  106. Last received {lastReceivedS}s ago
  107. </>
  108. )}
  109. </>
  110. }
  111. type={isLate === null ? undefined : isLate ? 'danger' : 'success'}
  112. />
  113. <DiagnosticItem
  114. icon="schedule"
  115. label="Latency"
  116. value={
  117. debugInfo.latency ? (
  118. <>
  119. {debugInfo.latency} ms
  120. <br />
  121. Max: {debugInfo.maxLatency} ms
  122. </>
  123. ) : (
  124. '-'
  125. )
  126. }
  127. type={
  128. debugInfo.latency
  129. ? debugInfo.latency < 450
  130. ? 'success'
  131. : 'danger'
  132. : undefined
  133. }
  134. />
  135. <DiagnosticItem
  136. icon="difference"
  137. label="Clock Delta"
  138. value={
  139. debugInfo.clockDelta === null
  140. ? '-'
  141. : `${Math.round(debugInfo.clockDelta / 1000)}s`
  142. }
  143. type={
  144. debugInfo.clockDelta !== null
  145. ? Math.abs(debugInfo.clockDelta) < 1500
  146. ? 'success'
  147. : 'danger'
  148. : undefined
  149. }
  150. />
  151. <DiagnosticItem
  152. icon="signal_cellular_alt"
  153. label="Online"
  154. value={debugInfo.onLine?.toString() ?? '-'}
  155. type={debugInfo.onLine ? 'success' : 'danger'}
  156. />
  157. <DiagnosticItem
  158. icon="schedule"
  159. label="Current time"
  160. value={now.toUTCString()}
  161. />
  162. <DiagnosticItem
  163. icon="hourglass"
  164. label="Connection time"
  165. value={
  166. debugInfo.client?.connectedAt ? (
  167. <>
  168. {new Date(debugInfo.client.connectedAt).toUTCString()} (
  169. {Math.round(
  170. (Date.now() - debugInfo.client.connectedAt) / 1000
  171. )}
  172. s)
  173. </>
  174. ) : (
  175. '-'
  176. )
  177. }
  178. />
  179. <DiagnosticItem
  180. icon="local_shipping"
  181. label="Transport"
  182. value={socket?.socket.transport?.name ?? '-'}
  183. />
  184. <DiagnosticItem
  185. icon="badge"
  186. label="Client Public ID"
  187. value={debugInfo.client?.publicId ?? '-'}
  188. />
  189. <DiagnosticItem
  190. icon="pin"
  191. label="IP Address"
  192. value={debugInfo.client?.remoteIp ?? '-'}
  193. />
  194. <DiagnosticItem
  195. icon="web"
  196. label="User agent"
  197. value={debugInfo.client?.userAgent ?? '-'}
  198. />
  199. <DiagnosticItem
  200. icon="directions_boat"
  201. label="Navigator info"
  202. value={<NavigatorInfo />}
  203. />
  204. </div>
  205. </div>
  206. </Container>
  207. )
  208. }