[web] add external 15s heartbeat to socket.io connection (#22853)

* [web] add external 15s heartbeat to socket.io connection

* [web] extend debugging context for client errors

- include performance.now() timestamp for correlating other timings
- include connectionState, especially for new externalHeartbeat detail
- include spellCheckLanguage to check on client-side spelling impact

* [web] remove unnecessary hook dependency

Co-authored-by: Alf Eaton <alf.eaton@overleaf.com>

* Refactor externalHeartbeat

* Add connectionManager to context

* Clear the interval earlier, and on connect

* [web] refactor handling of socket debugging info

* [web] add split-test for external socket heartbeat

* [web] fully remove connectionManager from connection context

---------

Co-authored-by: Alf Eaton <alf.eaton@overleaf.com>
GitOrigin-RevId: fbebe64f8aa207eb4fd4a8f27d522d1cac35f9d4
This commit is contained in:
Jakob Ackermann
2025-01-28 09:05:12 +00:00
committed by Copybot
co-authored by Alf Eaton
parent 342bbe5f0b
commit 45a5d090d9
6 changed files with 125 additions and 20 deletions
@@ -7,7 +7,10 @@ import {
useCallback,
useMemo,
} from 'react'
import { ConnectionState } from '../connection/types/connection-state'
import {
ConnectionState,
SocketDebuggingInfo,
} from '../connection/types/connection-state'
import {
ConnectionManager,
StateChangeEvent,
@@ -25,6 +28,7 @@ type ConnectionContextValue = {
tryReconnectNow: () => void
registerUserActivity: () => void
disconnect: () => void
getSocketDebuggingInfo: () => SocketDebuggingInfo
}
export const ConnectionContext = createContext<
@@ -75,6 +79,11 @@ export const ConnectionProvider: FC = ({ children }) => {
connectionManager.disconnect()
}, [connectionManager])
const getSocketDebuggingInfo = useCallback(
() => connectionManager.getSocketDebuggingInfo(),
[connectionManager]
)
// Reload the page on force disconnect. Doing this in React-land means that we
// can use useLocation(), which provides mockable location methods
useEffect(() => {
@@ -103,6 +112,7 @@ export const ConnectionProvider: FC = ({ children }) => {
tryReconnectNow,
registerUserActivity,
disconnect,
getSocketDebuggingInfo,
}),
[
connectionManager.socket,
@@ -113,6 +123,7 @@ export const ConnectionProvider: FC = ({ children }) => {
secondsUntilReconnect,
tryReconnectNow,
disconnect,
getSocketDebuggingInfo,
]
)
@@ -100,7 +100,7 @@ export const IdeReactProvider: FC = ({ children }) => {
// been called
const [projectJoined, setProjectJoined] = useState(false)
const { socket } = useConnectionContext()
const { socket, getSocketDebuggingInfo } = useConnectionContext()
const reportError = useCallback(
(error: any, meta?: Record<string, any>) => {
@@ -108,11 +108,12 @@ export const IdeReactProvider: FC = ({ children }) => {
...meta,
user_id: getMeta('ol-user_id'),
project_id: projectId,
client_id: socket.socket?.sessionid,
transport: socket.socket?.transport?.name,
client_now: new Date(),
performance_now: performance.now(),
release,
client_load: LOADED_AT,
spellCheckLanguage: scopeStore.get('project.spellCheckLanguage'),
...getSocketDebuggingInfo(),
}
const errorObj: Record<string, any> = {}
@@ -130,7 +131,7 @@ export const IdeReactProvider: FC = ({ children }) => {
},
})
},
[socket.socket, release, projectId]
[release, projectId, getSocketDebuggingInfo, scopeStore]
)
// Populate scope values when joining project, then fire project:joined event