connection-context.tsx 3.4 KB

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