|
import {useContext, useEffect, useMemo, useRef, useState} from 'react'; |
|
import socketIOClient, {Socket} from 'socket.io-client'; |
|
import useStable from './useStable'; |
|
import {v4 as uuidv4} from 'uuid'; |
|
import {SocketContext} from './useSocket'; |
|
import {AppResetKeyContext} from './App'; |
|
import Backdrop from '@mui/material/Backdrop'; |
|
import CircularProgress from '@mui/material/CircularProgress'; |
|
import Typography from '@mui/material/Typography'; |
|
import {getURLParams} from './URLParams'; |
|
|
|
|
|
const INITIAL_DISCONNECT_SCREEN_DELAY = 2000; |
|
const SERVER_URL_DEFAULT = `${window.location.protocol === "https:" ? "wss" : "ws" |
|
}://${window.location.host}`; |
|
|
|
export default function SocketWrapper({children}) { |
|
const [socket, setSocket] = useState<Socket | null>(null); |
|
const [connected, setConnected] = useState<boolean | null>(null); |
|
|
|
const [willAttemptReconnect] = useState<boolean>(true); |
|
const serverIDRef = useRef<string | null>(null); |
|
|
|
const setAppResetKey = useContext(AppResetKeyContext); |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const clientID = useStable<string>(() => { |
|
const newID = uuidv4(); |
|
|
|
|
|
return newID; |
|
}); |
|
|
|
const socketObject = useMemo( |
|
() => ({socket, clientID, connected: connected ?? false}), |
|
[socket, clientID, connected], |
|
); |
|
|
|
useEffect(() => { |
|
const queryParams = { |
|
clientID: clientID, |
|
}; |
|
|
|
const serverURLFromParams = getURLParams().serverURL; |
|
const serverURL = serverURLFromParams ?? SERVER_URL_DEFAULT; |
|
|
|
console.log( |
|
`Opening socket connection to ${ |
|
serverURL?.length === 0 ? 'window.location.host' : serverURL |
|
} with query params:`, |
|
queryParams, |
|
); |
|
|
|
const newSocket: Socket = socketIOClient(serverURL, { |
|
query: queryParams, |
|
|
|
|
|
|
|
transports: ['websocket'], |
|
path: '/ws/socket.io' |
|
}); |
|
|
|
const onServerID = (serverID: string) => { |
|
console.debug('Received server ID:', serverID); |
|
if (serverIDRef.current != null) { |
|
if (serverIDRef.current !== serverID) { |
|
console.error( |
|
'Server ID changed. Resetting the app using the app key', |
|
); |
|
setAppResetKey(serverID); |
|
} |
|
} |
|
serverIDRef.current = serverID; |
|
}; |
|
|
|
newSocket.on('server_id', onServerID); |
|
|
|
setSocket(newSocket); |
|
|
|
return () => { |
|
newSocket.off('server_id', onServerID); |
|
console.log( |
|
'Closing socket connection in the useEffect cleanup function...', |
|
); |
|
newSocket.disconnect(); |
|
setSocket(null); |
|
}; |
|
}, [clientID, setAppResetKey]); |
|
|
|
useEffect(() => { |
|
if (socket != null) { |
|
const onAny = (eventName: string, ...args) => { |
|
console.debug(`[event: ${eventName}] args:`, ...args); |
|
}; |
|
|
|
socket.onAny(onAny); |
|
|
|
return () => { |
|
socket.offAny(onAny); |
|
}; |
|
} |
|
return () => {}; |
|
}, [socket]); |
|
|
|
useEffect(() => { |
|
if (socket != null) { |
|
const onConnect = (...args) => { |
|
console.debug('Connected to server with args:', ...args); |
|
setConnected(true); |
|
}; |
|
|
|
const onConnectError = (err) => { |
|
console.error(`Connection error due to ${err.message}`); |
|
}; |
|
|
|
const onDisconnect = (reason) => { |
|
setConnected(false); |
|
console.log(`Disconnected due to ${reason}`); |
|
}; |
|
|
|
socket.on('connect', onConnect); |
|
socket.on('connect_error', onConnectError); |
|
socket.on('disconnect', onDisconnect); |
|
|
|
return () => { |
|
socket.off('connect', onConnect); |
|
socket.off('connect_error', onConnectError); |
|
socket.off('disconnect', onDisconnect); |
|
}; |
|
} |
|
}, [socket]); |
|
|
|
useEffect(() => { |
|
if (socket != null) { |
|
const onReconnectError = (err) => { |
|
console.log(`Reconnect error due to ${err.message}`); |
|
}; |
|
|
|
socket.io.on('reconnect_error', onReconnectError); |
|
|
|
const onError = (err) => { |
|
console.log(`General socket error with message ${err.message}`); |
|
}; |
|
socket.io.on('error', onError); |
|
|
|
const onReconnect = (attempt) => { |
|
console.log(`Reconnected after ${attempt} attempt(s)`); |
|
}; |
|
socket.io.on('reconnect', onReconnect); |
|
|
|
const disconnectOnBeforeUnload = () => { |
|
console.log('Disconnecting due to beforeunload event...'); |
|
socket.disconnect(); |
|
setSocket(null); |
|
}; |
|
window.addEventListener('beforeunload', disconnectOnBeforeUnload); |
|
|
|
return () => { |
|
socket.io.off('reconnect_error', onReconnectError); |
|
socket.io.off('error', onError); |
|
socket.io.off('reconnect', onReconnect); |
|
window.removeEventListener('beforeunload', disconnectOnBeforeUnload); |
|
}; |
|
} |
|
}, [clientID, setAppResetKey, socket]); |
|
|
|
|
|
|
|
|
|
useEffect(() => { |
|
window.setTimeout(() => { |
|
setConnected((prev) => { |
|
if (prev === null) { |
|
return false; |
|
} |
|
return prev; |
|
}); |
|
}, INITIAL_DISCONNECT_SCREEN_DELAY); |
|
}, []); |
|
|
|
return ( |
|
<SocketContext.Provider value={socketObject}> |
|
{children} |
|
|
|
<Backdrop |
|
open={connected === false && willAttemptReconnect === true} |
|
sx={{ |
|
color: '#fff', |
|
zIndex: (theme) => theme.zIndex.drawer + 1, |
|
}}> |
|
<div |
|
style={{ |
|
alignItems: 'center', |
|
flexDirection: 'column', |
|
textAlign: 'center', |
|
}}> |
|
<CircularProgress color="inherit" /> |
|
<Typography |
|
align="center" |
|
fontSize={{sm: 18, xs: 16}} |
|
sx={{ |
|
fontFamily: |
|
'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', |
|
fontWeight: 'bold', |
|
}}> |
|
{'Disconnected. Attempting to reconnect...'} |
|
</Typography> |
|
</div> |
|
</Backdrop> |
|
</SocketContext.Provider> |
|
); |
|
} |
|
|