use-socket-manager.ts 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import { useState, useEffect, useCallback } from 'react'
  2. import SocketIoShim from '@/ide/connection/SocketIoShim'
  3. import type { Socket } from '@/features/ide-react/connection/types/socket'
  4. import type { DebugInfo, SocketState } from './types'
  5. export function useSocketManager() {
  6. const [socket, setSocket] = useState<Socket | null>(null)
  7. const [autoping, setAutoping] = useState<boolean>(false)
  8. const [socketState, setSocketState] = useState<SocketState>({
  9. connected: false,
  10. connecting: false,
  11. lastError: '',
  12. })
  13. const [debugInfo, setDebugInfo] = useState<DebugInfo>({
  14. sent: 0,
  15. received: 0,
  16. latency: null,
  17. maxLatency: null,
  18. onLine: null,
  19. clockDelta: null,
  20. client: null,
  21. unansweredSince: null,
  22. lastReceived: null,
  23. })
  24. const connectSocket = useCallback(() => {
  25. const parsedURL = new URL('/socket.io', window.origin)
  26. setSocketState(prev => ({
  27. ...prev,
  28. connecting: true,
  29. lastAttempt: Date.now(),
  30. }))
  31. const newSocket = SocketIoShim.connect(parsedURL.origin, {
  32. resource: parsedURL.pathname.slice(1),
  33. 'auto connect': false,
  34. 'connect timeout': 30 * 1000,
  35. 'force new connection': true,
  36. query: new URLSearchParams({ debugging: 'true' }).toString(),
  37. reconnect: false,
  38. }) as unknown as Socket
  39. setSocket(newSocket)
  40. return newSocket
  41. }, [])
  42. const disconnectSocket = useCallback(() => {
  43. socket?.disconnect()
  44. setSocket(null)
  45. setSocketState(prev => ({
  46. ...prev,
  47. connected: false,
  48. connecting: false,
  49. lastError: 'Manually disconnected',
  50. }))
  51. }, [socket])
  52. const forceReconnect = useCallback(() => {
  53. disconnectSocket()
  54. setTimeout(connectSocket, 1000)
  55. }, [disconnectSocket, connectSocket])
  56. useEffect(() => {
  57. connectSocket()
  58. }, [connectSocket])
  59. const sendPing = useCallback(() => {
  60. if (socket?.socket.connected) {
  61. const time = Date.now()
  62. setDebugInfo(prev => ({
  63. ...prev,
  64. sent: prev.sent + 1,
  65. unansweredSince: prev.unansweredSince ?? time,
  66. }))
  67. socket.emit('debug', { time }, (info: any) => {
  68. const beforeTime = info.data.time
  69. const now = Date.now()
  70. const latency = now - beforeTime
  71. const clockDelta = (beforeTime + now) / 2 - info.serverTime
  72. setDebugInfo(prev => ({
  73. ...prev,
  74. received: prev.received + 1,
  75. latency,
  76. maxLatency: Math.max(prev.maxLatency ?? 0, latency),
  77. clockDelta,
  78. client: info.client,
  79. lastReceived: now,
  80. unansweredSince: null,
  81. }))
  82. })
  83. }
  84. }, [socket])
  85. useEffect(() => {
  86. if (!socket || !autoping) return
  87. const statsInterval = setInterval(sendPing, 2000)
  88. return () => {
  89. clearInterval(statsInterval)
  90. }
  91. }, [socket, autoping, sendPing])
  92. useEffect(() => {
  93. if (!socket) return
  94. socket.on('connect', () => {
  95. setSocketState(prev => ({
  96. ...prev,
  97. connected: true,
  98. connecting: false,
  99. lastSuccess: Date.now(),
  100. lastError: '',
  101. }))
  102. sendPing()
  103. })
  104. socket.on('disconnect', (reason: string) => {
  105. setSocketState(prev => ({
  106. ...prev,
  107. connected: false,
  108. connecting: false,
  109. lastError: `Disconnected: ${reason}`,
  110. }))
  111. })
  112. socket.on('connect_error', (error: Error) => {
  113. setSocketState(prev => ({
  114. ...prev,
  115. connecting: false,
  116. lastError: `Connection error: ${error?.message || 'Unknown'}`,
  117. }))
  118. })
  119. socket.socket.connect()
  120. return () => {
  121. socket.disconnect()
  122. }
  123. }, [sendPing, socket])
  124. useEffect(() => {
  125. const updateNetworkInfo = () => {
  126. setDebugInfo(prev => ({ ...prev, onLine: navigator.onLine }))
  127. }
  128. window.addEventListener('online', updateNetworkInfo)
  129. window.addEventListener('offline', updateNetworkInfo)
  130. updateNetworkInfo()
  131. return () => {
  132. window.removeEventListener('online', updateNetworkInfo)
  133. window.removeEventListener('offline', updateNetworkInfo)
  134. }
  135. }, [])
  136. return {
  137. socketState,
  138. debugInfo,
  139. connectSocket,
  140. disconnectSocket,
  141. forceReconnect,
  142. socket,
  143. autoping,
  144. setAutoping,
  145. }
  146. }