connection-context.tsx 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136
  1. import {
  2. createContext,
  3. useContext,
  4. useEffect,
  5. useState,
  6. FC,
  7. useCallback,
  8. useMemo,
  9. } from 'react'
  10. import { ConnectionState } from '../connection/types/connection-state'
  11. import {
  12. ConnectionManager,
  13. StateChangeEvent,
  14. } from '@/features/ide-react/connection/connection-manager'
  15. import { Socket } from '@/features/ide-react/connection/types/socket'
  16. import { secondsUntil } from '@/features/ide-react/connection/utils'
  17. import { useLocation } from '@/shared/hooks/use-location'
  18. type ConnectionContextValue = {
  19. socket: Socket
  20. connectionState: ConnectionState
  21. isConnected: boolean
  22. isStillReconnecting: boolean
  23. secondsUntilReconnect: () => number
  24. tryReconnectNow: () => void
  25. registerUserActivity: () => void
  26. disconnect: () => void
  27. }
  28. export const ConnectionContext = createContext<
  29. ConnectionContextValue | undefined
  30. >(undefined)
  31. export const ConnectionProvider: FC = ({ children }) => {
  32. const location = useLocation()
  33. const [connectionManager] = useState(() => new ConnectionManager())
  34. const [connectionState, setConnectionState] = useState(
  35. connectionManager.state
  36. )
  37. useEffect(() => {
  38. const handleStateChange = ((event: StateChangeEvent) => {
  39. setConnectionState(event.detail.state)
  40. }) as EventListener
  41. connectionManager.addEventListener('statechange', handleStateChange)
  42. return () => {
  43. connectionManager.removeEventListener('statechange', handleStateChange)
  44. }
  45. }, [connectionManager])
  46. const isConnected = connectionState.readyState === WebSocket.OPEN
  47. const isStillReconnecting =
  48. connectionState.readyState === WebSocket.CONNECTING &&
  49. performance.now() - connectionState.lastConnectionAttempt > 1000
  50. const secondsUntilReconnect = useCallback(
  51. () => secondsUntil(connectionState.reconnectAt),
  52. [connectionState.reconnectAt]
  53. )
  54. const tryReconnectNow = useCallback(
  55. () => connectionManager.tryReconnectNow(),
  56. [connectionManager]
  57. )
  58. const registerUserActivity = useCallback(
  59. () => connectionManager.registerUserActivity(),
  60. [connectionManager]
  61. )
  62. const disconnect = useCallback(() => {
  63. connectionManager.disconnect()
  64. }, [connectionManager])
  65. // Reload the page on force disconnect. Doing this in React-land means that we
  66. // can use useLocation(), which provides mockable location methods
  67. useEffect(() => {
  68. if (connectionState.forceDisconnected) {
  69. const timer = window.setTimeout(
  70. () => location.reload(),
  71. connectionState.forcedDisconnectDelay * 1000
  72. )
  73. return () => {
  74. window.clearTimeout(timer)
  75. }
  76. }
  77. }, [
  78. connectionState.forceDisconnected,
  79. connectionState.forcedDisconnectDelay,
  80. location,
  81. ])
  82. const value = useMemo<ConnectionContextValue>(
  83. () => ({
  84. socket: connectionManager.socket,
  85. connectionState,
  86. isConnected,
  87. isStillReconnecting,
  88. secondsUntilReconnect,
  89. tryReconnectNow,
  90. registerUserActivity,
  91. disconnect,
  92. }),
  93. [
  94. connectionManager.socket,
  95. connectionState,
  96. isConnected,
  97. isStillReconnecting,
  98. registerUserActivity,
  99. secondsUntilReconnect,
  100. tryReconnectNow,
  101. disconnect,
  102. ]
  103. )
  104. return (
  105. <ConnectionContext.Provider value={value}>
  106. {children}
  107. </ConnectionContext.Provider>
  108. )
  109. }
  110. export function useConnectionContext(): ConnectionContextValue {
  111. const context = useContext(ConnectionContext)
  112. if (!context) {
  113. throw new Error(
  114. 'useConnectionContext is only available inside ConnectionProvider'
  115. )
  116. }
  117. return context
  118. }