Skip to content

Commit 6e898d2

Browse files
authored
Implement engine-disconnect client reporting (#12489)
1 parent fe545eb commit 6e898d2

6 files changed

Lines changed: 98 additions & 14 deletions

File tree

src/components/ConnectionStream.tsx

Lines changed: 77 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,18 @@ import { useOnPageExit } from '@src/hooks/network/useOnPageExit'
77
import { useOnPageIdle } from '@src/hooks/network/useOnPageIdle'
88
import { useOnPageMounted } from '@src/hooks/network/useOnPageMounted'
99
import { useOnPageResize } from '@src/hooks/network/useOnPageResize'
10-
import { useOnPeerConnectionClose } from '@src/hooks/network/useOnPeerConnectionClose'
10+
import {
11+
type EngineDisconnectEvent,
12+
useOnPeerConnectionClose,
13+
} from '@src/hooks/network/useOnPeerConnectionClose'
1114
import { useOnVitestEngineOnline } from '@src/hooks/network/useOnVitestEngineOnline'
1215
import { useOnWebsocketClose } from '@src/hooks/network/useOnWebsocketClose'
1316
import { useOnWindowOnlineOffline } from '@src/hooks/network/useOnWindowOnlineOffline'
1417
import { useTryConnect } from '@src/hooks/network/useTryConnect'
1518
import { useModelingContext } from '@src/hooks/useModelingContext'
1619
import { useNetworkContext } from '@src/hooks/useNetworkContext'
1720
import { NetworkHealthState } from '@src/hooks/useNetworkStatus'
21+
import { ClientErrorCode, reportClientError } from '@src/lib/clientErrors'
1822
import { findOperationForArtifact } from '@src/lang/queryAst'
1923
import {
2024
getArtifactOfTypes,
@@ -32,6 +36,7 @@ import {
3236
} from '@src/lib/selections'
3337
import { Themes, getResolvedTheme } from '@src/lib/theme'
3438
import { err, reportRejection } from '@src/lib/trap'
39+
import { EngineCommandManagerEvents } from '@src/network/utils'
3540
import type {
3641
EngineSceneExtensionContext,
3742
EngineSceneStreamLayer,
@@ -41,6 +46,14 @@ import { use, useCallback, useMemo, useRef, useState } from 'react'
4146

4247
const TIME_TO_CONNECT = 30_000
4348

49+
const stringHash = (value: string) => {
50+
let hash = 0
51+
for (let i = 0; i < value.length; i++) {
52+
hash = (hash * 31 + value.charCodeAt(i)) | 0
53+
}
54+
return hash.toString(36)
55+
}
56+
4457
interface ConnectionStreamProps {
4558
authToken: string | undefined
4659
sketchSolveStreamDimming?: number
@@ -81,6 +94,45 @@ export const ConnectionStream = (props: ConnectionStreamProps) => {
8194
return isSafari ? ' object-fill' : ''
8295
}, [])
8396

97+
const reportEngineDisconnect = useCallback(
98+
(eventType: EngineDisconnectEvent, extra?: Record<string, unknown>) => {
99+
const kclSource = kclManager.code
100+
const connection = engineCommandManager.connection
101+
102+
void reportClientError({
103+
code: ClientErrorCode.EngineDisconnect,
104+
message: `Engine disconnected: ${eventType}`,
105+
dedupeKey: `ConnectionStream:engine-disconnect:${eventType}:${kclManager.currentFileName ?? 'unknown'}:${stringHash(kclSource)}`,
106+
extra: {
107+
source: 'ConnectionStream',
108+
eventType,
109+
projectName: project?.name,
110+
currentFileName: kclManager.currentFileName,
111+
isSceneReady,
112+
numberOfConnectionAttempts,
113+
pendingCommandCount: Object.keys(engineCommandManager.pendingCommands)
114+
.length,
115+
hasConnection: Boolean(connection),
116+
connectionId: connection?.id,
117+
connectionConnected: connection?.connected,
118+
peerConnectionState: connection?.peerConnection?.connectionState,
119+
iceConnectionState: connection?.peerConnection?.iceConnectionState,
120+
dataChannelReadyState: connection?.unreliableDataChannel?.readyState,
121+
...extra,
122+
kclSourceLength: kclSource.length,
123+
kclSource,
124+
},
125+
})
126+
},
127+
[
128+
engineCommandManager,
129+
isSceneReady,
130+
kclManager,
131+
numberOfConnectionAttempts,
132+
project?.name,
133+
]
134+
)
135+
84136
const handleMouseUp: MouseEventHandler<HTMLDivElement> = useCallback(
85137
(e) => {
86138
if (!isNetworkOkay) return
@@ -337,7 +389,10 @@ export const ConnectionStream = (props: ConnectionStreamProps) => {
337389

338390
const onWebSocketCloseParams = useMemo(
339391
() => ({
340-
callback: () => {
392+
callback: (code: string | undefined) => {
393+
reportEngineDisconnect(EngineCommandManagerEvents.WebsocketClosed, {
394+
websocketCloseCode: code,
395+
})
341396
setShowManualConnect(false)
342397
tryConnecting({
343398
authToken: props.authToken || '',
@@ -356,13 +411,22 @@ export const ConnectionStream = (props: ConnectionStreamProps) => {
356411
setShowManualConnect(true)
357412
})
358413
},
359-
infiniteDetectionLoopCallback: () => {
414+
infiniteDetectionLoopCallback: (code: string | undefined) => {
415+
reportEngineDisconnect(EngineCommandManagerEvents.WebsocketClosed, {
416+
websocketCloseCode: code,
417+
})
360418
setShowManualConnect(true)
361419
},
362420
engineCommandManager,
363421
}),
364422
// eslint-disable-next-line react-hooks/exhaustive-deps
365-
[isConnecting, numberOfConnectionAttempts, props.authToken, settings]
423+
[
424+
isConnecting,
425+
numberOfConnectionAttempts,
426+
props.authToken,
427+
reportEngineDisconnect,
428+
settings,
429+
]
366430
)
367431
useOnWebsocketClose(onWebSocketCloseParams)
368432

@@ -396,7 +460,8 @@ export const ConnectionStream = (props: ConnectionStreamProps) => {
396460

397461
const onPeerConnectionCloseParams = useMemo(
398462
() => ({
399-
callback: () => {
463+
callback: (eventType: EngineDisconnectEvent) => {
464+
reportEngineDisconnect(eventType)
400465
setShowManualConnect(false)
401466
tryConnecting({
402467
authToken: props.authToken || '',
@@ -418,7 +483,13 @@ export const ConnectionStream = (props: ConnectionStreamProps) => {
418483
engineCommandManager,
419484
}),
420485
// eslint-disable-next-line react-hooks/exhaustive-deps
421-
[isConnecting, numberOfConnectionAttempts, props.authToken, settings]
486+
[
487+
isConnecting,
488+
numberOfConnectionAttempts,
489+
props.authToken,
490+
reportEngineDisconnect,
491+
settings,
492+
]
422493
)
423494
useOnPeerConnectionClose(onPeerConnectionCloseParams)
424495

src/hooks/network/useOnPeerConnectionClose.spec.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,9 @@ describe('useOnPeerConnectionClose', () => {
163163
)
164164
unmount()
165165
expect(callback).toHaveBeenCalledTimes(1)
166+
expect(callback).toHaveBeenCalledWith(
167+
EngineCommandManagerEvents.peerConnectionDisconnected
168+
)
166169
})
167170
test('dispatched peerConnectionFailed', async () => {
168171
const callback = vi.fn(() => 1)

src/hooks/network/useOnPeerConnectionClose.tsx

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,15 @@ import type { ConnectionManager } from '@src/network/connectionManager'
22
import { EngineCommandManagerEvents } from '@src/network/utils'
33
import { useEffect } from 'react'
44

5+
export type EngineDisconnectEvent =
6+
| EngineCommandManagerEvents.WebsocketClosed
7+
| EngineCommandManagerEvents.peerConnectionClosed
8+
| EngineCommandManagerEvents.peerConnectionDisconnected
9+
| EngineCommandManagerEvents.peerConnectionFailed
10+
| EngineCommandManagerEvents.dataChannelClose
11+
512
export interface IUseOnPeerConnectionClose {
6-
callback: () => void
13+
callback: (eventType: EngineDisconnectEvent) => void
714
engineCommandManager: ConnectionManager
815
}
916
/**
@@ -20,8 +27,8 @@ export function useOnPeerConnectionClose({
2027
}: IUseOnPeerConnectionClose) {
2128
useEffect(() => {
2229
// same failure handler for all for now
23-
const onFailure: EventListener = () => {
24-
callback()
30+
const onFailure: EventListener = (event) => {
31+
callback(event.type as EngineDisconnectEvent)
2532
}
2633

2734
engineCommandManager.addEventListener(

src/hooks/network/useOnWebsocketClose.spec.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ describe('useOnWebsocketClose', () => {
5757
)
5858
unmount()
5959
expect(callback).toHaveBeenCalledTimes(1)
60+
expect(callback).toHaveBeenCalledWith(undefined)
6061
})
6162
test('should call infinite detection loop callback on close event', async () => {
6263
const callback = vi.fn(() => 1)
@@ -82,6 +83,7 @@ describe('useOnWebsocketClose', () => {
8283
unmount()
8384
expect(callback).toHaveBeenCalledTimes(0)
8485
expect(infiniteLoopCallback).toHaveBeenCalledTimes(1)
86+
expect(infiniteLoopCallback).toHaveBeenCalledWith('1006')
8587
})
8688
})
8789
})

src/hooks/network/useOnWebsocketClose.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { EngineCommandManagerEvents } from '@src/network/utils'
44
import { useEffect } from 'react'
55

66
export interface IUseOnWebsocketClose {
7-
callback: () => void
8-
infiniteDetectionLoopCallback: () => void
7+
callback: (code: string | undefined) => void
8+
infiniteDetectionLoopCallback: (code: string | undefined) => void
99
engineCommandManager: ConnectionManager
1010
}
1111

@@ -20,7 +20,7 @@ export function useOnWebsocketClose({
2020
engineCommandManager,
2121
}: IUseOnWebsocketClose) {
2222
useEffect(() => {
23-
const onWebsocketClose = (event: CustomEvent) => {
23+
const onWebsocketClose = (event: CustomEvent<{ code?: string }>) => {
2424
if (event?.detail?.code === '1006') {
2525
// Most likely your internet is out. Do not try to auto reconnect
2626
// This will result in an infinite loop
@@ -32,11 +32,11 @@ export function useOnWebsocketClose({
3232
},
3333
})
3434

35-
infiniteDetectionLoopCallback()
35+
infiniteDetectionLoopCallback(event.detail.code)
3636
return
3737
}
3838

39-
callback()
39+
callback(event?.detail?.code)
4040
}
4141

4242
engineCommandManager.addEventListener(

src/lib/clientErrors.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ type ReportClientErrorParams = {
1313
}
1414

1515
export enum ClientErrorCode {
16+
EngineDisconnect = 'engine_disconnect',
1617
UserFeaturesFetchError = 'user_features_fetch_error',
1718
ZookeeperActorError = 'zookeeper_actor_error',
1819
ZookeeperSetupError = 'zookeeper_setup_error',

0 commit comments

Comments
 (0)