2023-10-16 07:10:43 -04:00
|
|
|
import {
|
|
|
|
createContext,
|
|
|
|
useContext,
|
|
|
|
useEffect,
|
|
|
|
useState,
|
|
|
|
FC,
|
|
|
|
useCallback,
|
|
|
|
useMemo,
|
|
|
|
} from 'react'
|
|
|
|
import { ConnectionState } from '../connection/types/connection-state'
|
|
|
|
import { ConnectionManager } from '@/features/ide-react/connection/connection-manager'
|
|
|
|
import { Socket } from '@/features/ide-react/connection/types/socket'
|
|
|
|
import { secondsUntil } from '@/features/ide-react/connection/utils'
|
2023-11-06 08:11:06 -05:00
|
|
|
import { useLocation } from '@/shared/hooks/use-location'
|
2023-10-16 07:10:43 -04:00
|
|
|
|
|
|
|
type ConnectionContextValue = {
|
|
|
|
socket: Socket
|
|
|
|
connectionState: ConnectionState
|
|
|
|
isConnected: boolean
|
|
|
|
isStillReconnecting: boolean
|
|
|
|
secondsUntilReconnect: () => number
|
|
|
|
tryReconnectNow: () => void
|
|
|
|
registerUserActivity: () => void
|
2023-10-26 04:57:00 -04:00
|
|
|
disconnect: () => void
|
2023-10-16 07:10:43 -04:00
|
|
|
}
|
|
|
|
|
2023-10-18 05:14:13 -04:00
|
|
|
const ConnectionContext = createContext<ConnectionContextValue | undefined>(
|
|
|
|
undefined
|
|
|
|
)
|
2023-10-16 07:10:43 -04:00
|
|
|
|
|
|
|
export const ConnectionProvider: FC = ({ children }) => {
|
2023-11-06 08:11:06 -05:00
|
|
|
const location = useLocation()
|
|
|
|
|
2023-10-16 07:10:43 -04:00
|
|
|
const [connectionManager] = useState(() => new ConnectionManager())
|
|
|
|
const [connectionState, setConnectionState] = useState(
|
|
|
|
connectionManager.state
|
|
|
|
)
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
function handleStateChange(event: { state: ConnectionState }) {
|
|
|
|
setConnectionState(event.state)
|
|
|
|
}
|
|
|
|
connectionManager.on('statechange', handleStateChange)
|
|
|
|
|
|
|
|
return () => {
|
|
|
|
connectionManager.off('statechange', handleStateChange)
|
|
|
|
}
|
|
|
|
}, [connectionManager])
|
|
|
|
|
|
|
|
const isConnected = connectionState.readyState === WebSocket.OPEN
|
|
|
|
|
|
|
|
const isStillReconnecting =
|
|
|
|
connectionState.readyState === WebSocket.CONNECTING &&
|
|
|
|
performance.now() - connectionState.lastConnectionAttempt > 1000
|
|
|
|
|
|
|
|
const secondsUntilReconnect = useCallback(
|
|
|
|
() => secondsUntil(connectionState.reconnectAt),
|
|
|
|
[connectionState.reconnectAt]
|
|
|
|
)
|
|
|
|
|
|
|
|
const tryReconnectNow = useCallback(
|
|
|
|
() => connectionManager.tryReconnectNow(),
|
|
|
|
[connectionManager]
|
|
|
|
)
|
|
|
|
|
|
|
|
const registerUserActivity = useCallback(
|
|
|
|
() => connectionManager.registerUserActivity(),
|
|
|
|
[connectionManager]
|
|
|
|
)
|
|
|
|
|
2023-10-26 04:57:00 -04:00
|
|
|
const disconnect = useCallback(() => {
|
|
|
|
connectionManager.disconnect()
|
|
|
|
}, [connectionManager])
|
|
|
|
|
2023-11-06 08:11:06 -05:00
|
|
|
// Reload the page on force disconnect. Doing this in React-land means that we
|
|
|
|
// can use useLocation(), which provides mockable location methods
|
|
|
|
useEffect(() => {
|
|
|
|
if (connectionState.forceDisconnected) {
|
|
|
|
const timer = window.setTimeout(
|
|
|
|
() => location.reload(),
|
|
|
|
connectionState.forcedDisconnectDelay * 1000
|
|
|
|
)
|
|
|
|
return () => {
|
|
|
|
window.clearTimeout(timer)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}, [
|
|
|
|
connectionState.forceDisconnected,
|
|
|
|
connectionState.forcedDisconnectDelay,
|
|
|
|
location,
|
|
|
|
])
|
|
|
|
|
2023-10-16 07:10:43 -04:00
|
|
|
const value = useMemo<ConnectionContextValue>(
|
|
|
|
() => ({
|
|
|
|
socket: connectionManager.socket,
|
|
|
|
connectionState,
|
|
|
|
isConnected,
|
|
|
|
isStillReconnecting,
|
|
|
|
secondsUntilReconnect,
|
|
|
|
tryReconnectNow,
|
|
|
|
registerUserActivity,
|
2023-10-26 04:57:00 -04:00
|
|
|
disconnect,
|
2023-10-16 07:10:43 -04:00
|
|
|
}),
|
|
|
|
[
|
|
|
|
connectionManager.socket,
|
|
|
|
connectionState,
|
|
|
|
isConnected,
|
|
|
|
isStillReconnecting,
|
|
|
|
registerUserActivity,
|
|
|
|
secondsUntilReconnect,
|
|
|
|
tryReconnectNow,
|
2023-10-26 04:57:00 -04:00
|
|
|
disconnect,
|
2023-10-16 07:10:43 -04:00
|
|
|
]
|
|
|
|
)
|
|
|
|
|
|
|
|
return (
|
|
|
|
<ConnectionContext.Provider value={value}>
|
|
|
|
{children}
|
|
|
|
</ConnectionContext.Provider>
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
export function useConnectionContext(): ConnectionContextValue {
|
|
|
|
const context = useContext(ConnectionContext)
|
|
|
|
|
|
|
|
if (!context) {
|
|
|
|
throw new Error(
|
|
|
|
'useConnectionContext is only available inside ConnectionProvider'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
return context
|
|
|
|
}
|