Revert "Add ping pong health, remove a timeout interval, fix up netwo… (#1771)
Revert "Add ping pong health, remove a timeout interval, fix up network events (#1555)"
This reverts commit 61d7950ca3
.
Co-authored-by: Kurt Hutten <k.hutten@protonmail.ch>
This commit is contained in:
@ -30,7 +30,7 @@ describe('NetworkHealthIndicator tests', () => {
|
|||||||
fireEvent.click(screen.getByTestId('network-toggle'))
|
fireEvent.click(screen.getByTestId('network-toggle'))
|
||||||
|
|
||||||
expect(screen.getByTestId('network')).toHaveTextContent(
|
expect(screen.getByTestId('network')).toHaveTextContent(
|
||||||
NETWORK_HEALTH_TEXT[NetworkHealthState.Issue]
|
NETWORK_HEALTH_TEXT[NetworkHealthState.Ok]
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -6,8 +6,7 @@ import {
|
|||||||
ConnectingTypeGroup,
|
ConnectingTypeGroup,
|
||||||
DisconnectingType,
|
DisconnectingType,
|
||||||
engineCommandManager,
|
engineCommandManager,
|
||||||
EngineCommandManagerEvents,
|
EngineConnectionState,
|
||||||
EngineConnectionEvents,
|
|
||||||
EngineConnectionStateType,
|
EngineConnectionStateType,
|
||||||
ErrorType,
|
ErrorType,
|
||||||
initialConnectingTypeGroupState,
|
initialConnectingTypeGroupState,
|
||||||
@ -82,35 +81,37 @@ const overallConnectionStateIcon: Record<
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useNetworkStatus() {
|
export function useNetworkStatus() {
|
||||||
const [steps, setSteps] = useState(
|
const [steps, setSteps] = useState(initialConnectingTypeGroupState)
|
||||||
structuredClone(initialConnectingTypeGroupState)
|
|
||||||
)
|
|
||||||
const [internetConnected, setInternetConnected] = useState<boolean>(true)
|
const [internetConnected, setInternetConnected] = useState<boolean>(true)
|
||||||
const [overallState, setOverallState] = useState<NetworkHealthState>(
|
const [overallState, setOverallState] = useState<NetworkHealthState>(
|
||||||
NetworkHealthState.Disconnected
|
NetworkHealthState.Ok
|
||||||
)
|
)
|
||||||
const [pingPongHealth, setPingPongHealth] = useState<'OK' | 'BAD'>('BAD')
|
|
||||||
const [hasCopied, setHasCopied] = useState<boolean>(false)
|
const [hasCopied, setHasCopied] = useState<boolean>(false)
|
||||||
|
|
||||||
const [error, setError] = useState<ErrorType | undefined>(undefined)
|
const [error, setError] = useState<ErrorType | undefined>(undefined)
|
||||||
|
|
||||||
const hasIssue = (i: [ConnectingType, boolean | undefined]) =>
|
const issues: Record<ConnectingTypeGroup, boolean> = {
|
||||||
i[1] === undefined ? i[1] : !i[1]
|
[ConnectingTypeGroup.WebSocket]: steps[ConnectingTypeGroup.WebSocket].some(
|
||||||
|
(a: [ConnectingType, boolean | undefined]) => a[1] === false
|
||||||
|
),
|
||||||
|
[ConnectingTypeGroup.ICE]: steps[ConnectingTypeGroup.ICE].some(
|
||||||
|
(a: [ConnectingType, boolean | undefined]) => a[1] === false
|
||||||
|
),
|
||||||
|
[ConnectingTypeGroup.WebRTC]: steps[ConnectingTypeGroup.WebRTC].some(
|
||||||
|
(a: [ConnectingType, boolean | undefined]) => a[1] === false
|
||||||
|
),
|
||||||
|
}
|
||||||
|
|
||||||
const [issues, setIssues] = useState<
|
const hasIssues: boolean =
|
||||||
Record<ConnectingTypeGroup, boolean | undefined>
|
issues[ConnectingTypeGroup.WebSocket] ||
|
||||||
>({
|
issues[ConnectingTypeGroup.ICE] ||
|
||||||
[ConnectingTypeGroup.WebSocket]: undefined,
|
issues[ConnectingTypeGroup.WebRTC]
|
||||||
[ConnectingTypeGroup.ICE]: undefined,
|
|
||||||
[ConnectingTypeGroup.WebRTC]: undefined,
|
|
||||||
})
|
|
||||||
|
|
||||||
const [hasIssues, setHasIssues] = useState<boolean | undefined>(undefined)
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setOverallState(
|
setOverallState(
|
||||||
!internetConnected
|
!internetConnected
|
||||||
? NetworkHealthState.Disconnected
|
? NetworkHealthState.Disconnected
|
||||||
: hasIssues || hasIssues === undefined
|
: hasIssues
|
||||||
? NetworkHealthState.Issue
|
? NetworkHealthState.Issue
|
||||||
: NetworkHealthState.Ok
|
: NetworkHealthState.Ok
|
||||||
)
|
)
|
||||||
@ -133,59 +134,19 @@ export function useNetworkStatus() {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(pingPongHealth)
|
engineCommandManager.onConnectionStateChange(
|
||||||
}, [pingPongHealth])
|
(engineConnectionState: EngineConnectionState) => {
|
||||||
|
let hasSetAStep = false
|
||||||
useEffect(() => {
|
|
||||||
const issues = {
|
|
||||||
[ConnectingTypeGroup.WebSocket]: steps[
|
|
||||||
ConnectingTypeGroup.WebSocket
|
|
||||||
].reduce(
|
|
||||||
(acc: boolean | undefined, a) =>
|
|
||||||
acc === true || acc === undefined ? acc : hasIssue(a),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
[ConnectingTypeGroup.ICE]: steps[ConnectingTypeGroup.ICE].reduce(
|
|
||||||
(acc: boolean | undefined, a) =>
|
|
||||||
acc === true || acc === undefined ? acc : hasIssue(a),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
[ConnectingTypeGroup.WebRTC]: steps[ConnectingTypeGroup.WebRTC].reduce(
|
|
||||||
(acc: boolean | undefined, a) =>
|
|
||||||
acc === true || acc === undefined ? acc : hasIssue(a),
|
|
||||||
false
|
|
||||||
),
|
|
||||||
}
|
|
||||||
setIssues(issues)
|
|
||||||
}, [steps])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setHasIssues(
|
|
||||||
issues[ConnectingTypeGroup.WebSocket] ||
|
|
||||||
issues[ConnectingTypeGroup.ICE] ||
|
|
||||||
issues[ConnectingTypeGroup.WebRTC]
|
|
||||||
)
|
|
||||||
}, [issues])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const onPingPongChange = ({ detail: state }: CustomEvent) => {
|
|
||||||
setPingPongHealth(state)
|
|
||||||
}
|
|
||||||
|
|
||||||
const onConnectionStateChange = ({
|
|
||||||
detail: engineConnectionState,
|
|
||||||
}: CustomEvent) => {
|
|
||||||
setSteps((steps) => {
|
|
||||||
let nextSteps = structuredClone(steps)
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
engineConnectionState.type === EngineConnectionStateType.Connecting
|
engineConnectionState.type === EngineConnectionStateType.Connecting
|
||||||
) {
|
) {
|
||||||
const groups = Object.values(nextSteps)
|
const groups = Object.values(steps)
|
||||||
for (let group of groups) {
|
for (let group of groups) {
|
||||||
for (let step of group) {
|
for (let step of group) {
|
||||||
if (step[0] !== engineConnectionState.value.type) continue
|
if (step[0] !== engineConnectionState.value.type) continue
|
||||||
step[1] = true
|
step[1] = true
|
||||||
|
hasSetAStep = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -193,7 +154,7 @@ export function useNetworkStatus() {
|
|||||||
if (
|
if (
|
||||||
engineConnectionState.type === EngineConnectionStateType.Disconnecting
|
engineConnectionState.type === EngineConnectionStateType.Disconnecting
|
||||||
) {
|
) {
|
||||||
const groups = Object.values(nextSteps)
|
const groups = Object.values(steps)
|
||||||
for (let group of groups) {
|
for (let group of groups) {
|
||||||
for (let step of group) {
|
for (let step of group) {
|
||||||
if (
|
if (
|
||||||
@ -204,6 +165,7 @@ export function useNetworkStatus() {
|
|||||||
?.type === step[0]
|
?.type === step[0]
|
||||||
) {
|
) {
|
||||||
step[1] = false
|
step[1] = false
|
||||||
|
hasSetAStep = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,50 +176,11 @@ export function useNetworkStatus() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Reset the state of all steps if we have disconnected.
|
if (hasSetAStep) {
|
||||||
if (
|
setSteps(steps)
|
||||||
engineConnectionState.type === EngineConnectionStateType.Disconnected
|
|
||||||
) {
|
|
||||||
return structuredClone(initialConnectingTypeGroupState)
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return nextSteps
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const onEngineAvailable = ({ detail: engineConnection }: CustomEvent) => {
|
|
||||||
engineConnection.addEventListener(
|
|
||||||
EngineConnectionEvents.PingPongChanged,
|
|
||||||
onPingPongChange as EventListener
|
|
||||||
)
|
|
||||||
engineConnection.addEventListener(
|
|
||||||
EngineConnectionEvents.ConnectionStateChanged,
|
|
||||||
onConnectionStateChange as EventListener
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
engineCommandManager.addEventListener(
|
|
||||||
EngineCommandManagerEvents.EngineAvailable,
|
|
||||||
onEngineAvailable as EventListener
|
|
||||||
)
|
)
|
||||||
|
|
||||||
return () => {
|
|
||||||
engineCommandManager.removeEventListener(
|
|
||||||
EngineCommandManagerEvents.EngineAvailable,
|
|
||||||
onEngineAvailable as EventListener
|
|
||||||
)
|
|
||||||
|
|
||||||
// When the component is unmounted these should be assigned, but it's possible
|
|
||||||
// the component mounts and unmounts before engine is available.
|
|
||||||
engineCommandManager.engineConnection?.addEventListener(
|
|
||||||
EngineConnectionEvents.PingPongChanged,
|
|
||||||
onPingPongChange as EventListener
|
|
||||||
)
|
|
||||||
engineCommandManager.engineConnection?.addEventListener(
|
|
||||||
EngineConnectionEvents.ConnectionStateChanged,
|
|
||||||
onConnectionStateChange as EventListener
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -269,7 +192,6 @@ export function useNetworkStatus() {
|
|||||||
error,
|
error,
|
||||||
setHasCopied,
|
setHasCopied,
|
||||||
hasCopied,
|
hasCopied,
|
||||||
pingPongHealth,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -334,18 +256,18 @@ export const NetworkHealthIndicator = () => {
|
|||||||
size="lg"
|
size="lg"
|
||||||
icon={
|
icon={
|
||||||
hasIssueToIcon[
|
hasIssueToIcon[
|
||||||
String(issues[name as ConnectingTypeGroup])
|
issues[name as ConnectingTypeGroup].toString()
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
iconClassName={
|
iconClassName={
|
||||||
hasIssueToIconColors[
|
hasIssueToIconColors[
|
||||||
String(issues[name as ConnectingTypeGroup])
|
issues[name as ConnectingTypeGroup].toString()
|
||||||
].icon
|
].icon
|
||||||
}
|
}
|
||||||
bgClassName={
|
bgClassName={
|
||||||
'rounded-sm ' +
|
'rounded-sm ' +
|
||||||
hasIssueToIconColors[
|
hasIssueToIconColors[
|
||||||
String(issues[name as ConnectingTypeGroup])
|
issues[name as ConnectingTypeGroup].toString()
|
||||||
].bg
|
].bg
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
|
@ -32,7 +32,6 @@ export const Stream = ({ className = '' }: { className?: string }) => {
|
|||||||
const { state } = useModelingContext()
|
const { state } = useModelingContext()
|
||||||
const { isExecuting } = useKclContext()
|
const { isExecuting } = useKclContext()
|
||||||
const { overallState } = useNetworkStatus()
|
const { overallState } = useNetworkStatus()
|
||||||
|
|
||||||
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
const isNetworkOkay = overallState === NetworkHealthState.Ok
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { PathToNode, Program, SourceRange } from 'lang/wasm'
|
import { PathToNode, Program, SourceRange } from 'lang/wasm'
|
||||||
import { VITE_KC_API_WS_MODELING_URL } from 'env'
|
import { VITE_KC_API_WS_MODELING_URL, VITE_KC_CONNECTION_TIMEOUT_MS } from 'env'
|
||||||
import { Models } from '@kittycad/lib'
|
import { Models } from '@kittycad/lib'
|
||||||
import { exportSave } from 'lib/exportSave'
|
import { exportSave } from 'lib/exportSave'
|
||||||
import { v4 as uuidv4 } from 'uuid'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
@ -8,9 +8,6 @@ import { sceneInfra } from 'clientSideScene/sceneInfra'
|
|||||||
|
|
||||||
let lastMessage = ''
|
let lastMessage = ''
|
||||||
|
|
||||||
// TODO(paultag): This ought to be tweakable.
|
|
||||||
const pingIntervalMs = 10000
|
|
||||||
|
|
||||||
interface CommandInfo {
|
interface CommandInfo {
|
||||||
commandType: CommandTypes
|
commandType: CommandTypes
|
||||||
range: SourceRange
|
range: SourceRange
|
||||||
@ -40,6 +37,11 @@ export interface ArtifactMap {
|
|||||||
[key: string]: ResultCommand | PendingCommand | FailedCommand
|
[key: string]: ResultCommand | PendingCommand | FailedCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface NewTrackArgs {
|
||||||
|
conn: EngineConnection
|
||||||
|
mediaStream: MediaStream
|
||||||
|
}
|
||||||
|
|
||||||
// This looks funny, I know. This is needed because node and the browser
|
// This looks funny, I know. This is needed because node and the browser
|
||||||
// disagree as to the type. In a browser it's a number, but in node it's a
|
// disagree as to the type. In a browser it's a number, but in node it's a
|
||||||
// "Timeout".
|
// "Timeout".
|
||||||
@ -156,28 +158,10 @@ export type EngineConnectionState =
|
|||||||
| State<EngineConnectionStateType.Disconnecting, DisconnectingValue>
|
| State<EngineConnectionStateType.Disconnecting, DisconnectingValue>
|
||||||
| State<EngineConnectionStateType.Disconnected, void>
|
| State<EngineConnectionStateType.Disconnected, void>
|
||||||
|
|
||||||
export type PingPongState = 'OK' | 'BAD'
|
|
||||||
|
|
||||||
export enum EngineConnectionEvents {
|
|
||||||
// Fires for each ping-pong success or failure.
|
|
||||||
PingPongChanged = 'ping-pong-changed', // (state: PingPongState) => void
|
|
||||||
|
|
||||||
// For now, this is only used by the NetworkHealthIndicator.
|
|
||||||
// We can eventually use it for more, but one step at a time.
|
|
||||||
ConnectionStateChanged = 'connection-state-changed', // (state: EngineConnectionState) => void
|
|
||||||
|
|
||||||
// These are used for the EngineCommandManager and were created
|
|
||||||
// before onConnectionStateChange existed.
|
|
||||||
ConnectionStarted = 'connection-started', // (engineConnection: EngineConnection) => void
|
|
||||||
Opened = 'opened', // (engineConnection: EngineConnection) => void
|
|
||||||
Closed = 'closed', // (engineConnection: EngineConnection) => void
|
|
||||||
NewTrack = 'new-track', // (track: NewTrackArgs) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
// EngineConnection encapsulates the connection(s) to the Engine
|
// EngineConnection encapsulates the connection(s) to the Engine
|
||||||
// for the EngineCommandManager; namely, the underlying WebSocket
|
// for the EngineCommandManager; namely, the underlying WebSocket
|
||||||
// and WebRTC connections.
|
// and WebRTC connections.
|
||||||
class EngineConnection extends EventTarget {
|
class EngineConnection {
|
||||||
websocket?: WebSocket
|
websocket?: WebSocket
|
||||||
pc?: RTCPeerConnection
|
pc?: RTCPeerConnection
|
||||||
unreliableDataChannel?: RTCDataChannel
|
unreliableDataChannel?: RTCDataChannel
|
||||||
@ -211,12 +195,7 @@ class EngineConnection extends EventTarget {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
this._state = next
|
this._state = next
|
||||||
|
this.onConnectionStateChange(this._state)
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EngineConnectionEvents.ConnectionStateChanged, {
|
|
||||||
detail: this._state,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private failedConnTimeout: Timeout | null
|
private failedConnTimeout: Timeout | null
|
||||||
@ -224,39 +203,74 @@ class EngineConnection extends EventTarget {
|
|||||||
readonly url: string
|
readonly url: string
|
||||||
private readonly token?: string
|
private readonly token?: string
|
||||||
|
|
||||||
|
// For now, this is only used by the NetworkHealthIndicator.
|
||||||
|
// We can eventually use it for more, but one step at a time.
|
||||||
|
private onConnectionStateChange: (state: EngineConnectionState) => void
|
||||||
|
|
||||||
|
// These are used for the EngineCommandManager and were created
|
||||||
|
// before onConnectionStateChange existed.
|
||||||
|
private onEngineConnectionOpen: (engineConnection: EngineConnection) => void
|
||||||
|
private onConnectionStarted: (engineConnection: EngineConnection) => void
|
||||||
|
private onClose: (engineConnection: EngineConnection) => void
|
||||||
|
private onNewTrack: (track: NewTrackArgs) => void
|
||||||
|
|
||||||
// TODO: actual type is ClientMetrics
|
// TODO: actual type is ClientMetrics
|
||||||
private webrtcStatsCollector?: () => Promise<ClientMetrics>
|
private webrtcStatsCollector?: () => Promise<ClientMetrics>
|
||||||
|
|
||||||
private pingPongSpan: { ping?: Date; pong?: Date }
|
constructor({
|
||||||
|
url,
|
||||||
constructor({ url, token }: { url: string; token?: string }) {
|
token,
|
||||||
super()
|
onConnectionStateChange = () => {},
|
||||||
|
onNewTrack = () => {},
|
||||||
|
onEngineConnectionOpen = () => {},
|
||||||
|
onConnectionStarted = () => {},
|
||||||
|
onClose = () => {},
|
||||||
|
}: {
|
||||||
|
url: string
|
||||||
|
token?: string
|
||||||
|
onConnectionStateChange?: (state: EngineConnectionState) => void
|
||||||
|
onEngineConnectionOpen?: (engineConnection: EngineConnection) => void
|
||||||
|
onConnectionStarted?: (engineConnection: EngineConnection) => void
|
||||||
|
onClose?: (engineConnection: EngineConnection) => void
|
||||||
|
onNewTrack?: (track: NewTrackArgs) => void
|
||||||
|
}) {
|
||||||
this.url = url
|
this.url = url
|
||||||
this.token = token
|
this.token = token
|
||||||
this.failedConnTimeout = null
|
this.failedConnTimeout = null
|
||||||
|
this.onConnectionStateChange = onConnectionStateChange
|
||||||
|
this.onEngineConnectionOpen = onEngineConnectionOpen
|
||||||
|
this.onConnectionStarted = onConnectionStarted
|
||||||
|
|
||||||
this.pingPongSpan = { ping: undefined, pong: undefined }
|
this.onClose = onClose
|
||||||
|
this.onNewTrack = onNewTrack
|
||||||
|
|
||||||
|
// TODO(paultag): This ought to be tweakable.
|
||||||
|
const pingIntervalMs = 10000
|
||||||
|
|
||||||
// Without an interval ping, our connection will timeout.
|
// Without an interval ping, our connection will timeout.
|
||||||
setInterval(() => {
|
let pingInterval = setInterval(() => {
|
||||||
switch (this.state.type as EngineConnectionStateType) {
|
switch (this.state.type as EngineConnectionStateType) {
|
||||||
case EngineConnectionStateType.ConnectionEstablished:
|
case EngineConnectionStateType.ConnectionEstablished:
|
||||||
this.send({ type: 'ping' })
|
this.send({ type: 'ping' })
|
||||||
this.pingPongSpan.ping = new Date()
|
|
||||||
break
|
break
|
||||||
case EngineConnectionStateType.Disconnecting:
|
case EngineConnectionStateType.Disconnecting:
|
||||||
case EngineConnectionStateType.Disconnected:
|
case EngineConnectionStateType.Disconnected:
|
||||||
// Reconnect if we have disconnected.
|
clearInterval(pingInterval)
|
||||||
if (!this.isConnecting()) this.connect()
|
|
||||||
break
|
break
|
||||||
default:
|
default:
|
||||||
if (this.isConnecting()) break
|
|
||||||
// Means we never could do an initial connection. Reconnect everything.
|
|
||||||
if (!this.pingPongSpan.ping) this.connect()
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}, pingIntervalMs)
|
}, pingIntervalMs)
|
||||||
|
|
||||||
|
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
|
let connectRetryInterval = setInterval(() => {
|
||||||
|
if (this.state.type !== EngineConnectionStateType.Disconnected) return
|
||||||
|
|
||||||
|
// Only try reconnecting when completely disconnected.
|
||||||
|
clearInterval(connectRetryInterval)
|
||||||
|
console.log('Trying to reconnect')
|
||||||
|
this.connect()
|
||||||
|
}, connectionTimeoutMs)
|
||||||
}
|
}
|
||||||
|
|
||||||
isConnecting() {
|
isConnecting() {
|
||||||
@ -338,11 +352,7 @@ class EngineConnection extends EventTarget {
|
|||||||
// dance is it safest to connect the video tracks / stream
|
// dance is it safest to connect the video tracks / stream
|
||||||
case 'connected':
|
case 'connected':
|
||||||
// Let the browser attach to the video stream now
|
// Let the browser attach to the video stream now
|
||||||
this.dispatchEvent(
|
this.onNewTrack({ conn: this, mediaStream: this.mediaStream! })
|
||||||
new CustomEvent(EngineConnectionEvents.NewTrack, {
|
|
||||||
detail: { conn: this, mediaStream: this.mediaStream! },
|
|
||||||
})
|
|
||||||
)
|
|
||||||
break
|
break
|
||||||
case 'failed':
|
case 'failed':
|
||||||
this.disconnectAll()
|
this.disconnectAll()
|
||||||
@ -458,9 +468,7 @@ class EngineConnection extends EventTarget {
|
|||||||
// Everything is now connected.
|
// Everything is now connected.
|
||||||
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
|
this.state = { type: EngineConnectionStateType.ConnectionEstablished }
|
||||||
|
|
||||||
this.dispatchEvent(
|
this.onEngineConnectionOpen(this)
|
||||||
new CustomEvent(EngineConnectionEvents.Opened, { detail: this })
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.unreliableDataChannel.addEventListener('close', (event) => {
|
this.unreliableDataChannel.addEventListener('close', (event) => {
|
||||||
@ -502,10 +510,6 @@ class EngineConnection extends EventTarget {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// Send an initial ping
|
|
||||||
this.send({ type: 'ping' })
|
|
||||||
this.pingPongSpan.ping = new Date()
|
|
||||||
|
|
||||||
// This is required for when KCMA is running stand-alone / within Tauri.
|
// This is required for when KCMA is running stand-alone / within Tauri.
|
||||||
// Otherwise when run in a browser, the token is sent implicitly via
|
// Otherwise when run in a browser, the token is sent implicitly via
|
||||||
// the Cookie header.
|
// the Cookie header.
|
||||||
@ -571,34 +575,12 @@ failed cmd type was ${artifactThatFailed?.commandType}`
|
|||||||
let resp = message.resp
|
let resp = message.resp
|
||||||
|
|
||||||
// If there's no body to the response, we can bail here.
|
// If there's no body to the response, we can bail here.
|
||||||
|
// !resp.type is usually "pong" response for our "ping"
|
||||||
if (!resp || !resp.type) {
|
if (!resp || !resp.type) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (resp.type) {
|
switch (resp.type) {
|
||||||
case 'pong':
|
|
||||||
this.pingPongSpan.pong = new Date()
|
|
||||||
if (this.pingPongSpan.ping && this.pingPongSpan.pong) {
|
|
||||||
if (
|
|
||||||
Math.abs(
|
|
||||||
this.pingPongSpan.pong.valueOf() -
|
|
||||||
this.pingPongSpan.ping.valueOf()
|
|
||||||
) >= pingIntervalMs
|
|
||||||
) {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
|
||||||
detail: 'BAD',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
this.dispatchEvent(
|
|
||||||
new CustomEvent(EngineConnectionEvents.PingPongChanged, {
|
|
||||||
detail: 'OK',
|
|
||||||
})
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break
|
|
||||||
case 'ice_server_info':
|
case 'ice_server_info':
|
||||||
let ice_servers = resp.data?.ice_servers
|
let ice_servers = resp.data?.ice_servers
|
||||||
|
|
||||||
@ -745,11 +727,27 @@ failed cmd type was ${artifactThatFailed?.commandType}`
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
this.dispatchEvent(
|
const connectionTimeoutMs = VITE_KC_CONNECTION_TIMEOUT_MS
|
||||||
new CustomEvent(EngineConnectionEvents.ConnectionStarted, {
|
if (this.failedConnTimeout) {
|
||||||
detail: this,
|
clearTimeout(this.failedConnTimeout)
|
||||||
})
|
this.failedConnTimeout = null
|
||||||
)
|
}
|
||||||
|
this.failedConnTimeout = setTimeout(() => {
|
||||||
|
if (this.isReady()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.failedConnTimeout = null
|
||||||
|
this.state = {
|
||||||
|
type: EngineConnectionStateType.Disconnecting,
|
||||||
|
value: {
|
||||||
|
type: DisconnectingType.Timeout,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
this.disconnectAll()
|
||||||
|
this.finalizeIfAllConnectionsClosed()
|
||||||
|
}, connectionTimeoutMs)
|
||||||
|
|
||||||
|
this.onConnectionStarted(this)
|
||||||
}
|
}
|
||||||
unreliableSend(message: object | string) {
|
unreliableSend(message: object | string) {
|
||||||
// TODO(paultag): Add in logic to determine the connection state and
|
// TODO(paultag): Add in logic to determine the connection state and
|
||||||
@ -798,8 +796,6 @@ interface UnreliableSubscription<T extends UnreliableResponses['type']> {
|
|||||||
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
|
callback: (data: Extract<UnreliableResponses, { type: T }>) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should eventually be replaced with native EventTarget event system,
|
|
||||||
// as it manages events in a more familiar way to other developers.
|
|
||||||
export interface Subscription<T extends ModelTypes> {
|
export interface Subscription<T extends ModelTypes> {
|
||||||
event: T
|
event: T
|
||||||
callback: (
|
callback: (
|
||||||
@ -827,11 +823,7 @@ export type CommandLog =
|
|||||||
data: null
|
data: null
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum EngineCommandManagerEvents {
|
export class EngineCommandManager {
|
||||||
EngineAvailable = 'engine-available',
|
|
||||||
}
|
|
||||||
|
|
||||||
export class EngineCommandManager extends EventTarget {
|
|
||||||
artifactMap: ArtifactMap = {}
|
artifactMap: ArtifactMap = {}
|
||||||
lastArtifactMap: ArtifactMap = {}
|
lastArtifactMap: ArtifactMap = {}
|
||||||
sceneCommandArtifacts: ArtifactMap = {}
|
sceneCommandArtifacts: ArtifactMap = {}
|
||||||
@ -865,9 +857,10 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
}
|
}
|
||||||
} = {} as any
|
} = {} as any
|
||||||
|
|
||||||
constructor() {
|
callbacksEngineStateConnection: ((state: EngineConnectionState) => void)[] =
|
||||||
super()
|
[]
|
||||||
|
|
||||||
|
constructor() {
|
||||||
this.engineConnection = undefined
|
this.engineConnection = undefined
|
||||||
;(async () => {
|
;(async () => {
|
||||||
// circular dependency needs one to be lazy loaded
|
// circular dependency needs one to be lazy loaded
|
||||||
@ -908,17 +901,12 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
this.engineConnection = new EngineConnection({
|
this.engineConnection = new EngineConnection({
|
||||||
url,
|
url,
|
||||||
token,
|
token,
|
||||||
})
|
onConnectionStateChange: (state: EngineConnectionState) => {
|
||||||
|
for (let cb of this.callbacksEngineStateConnection) {
|
||||||
this.dispatchEvent(
|
cb(state)
|
||||||
new CustomEvent(EngineCommandManagerEvents.EngineAvailable, {
|
}
|
||||||
detail: this.engineConnection,
|
},
|
||||||
})
|
onEngineConnectionOpen: () => {
|
||||||
)
|
|
||||||
|
|
||||||
this.engineConnection.addEventListener(
|
|
||||||
EngineConnectionEvents.Opened,
|
|
||||||
() => {
|
|
||||||
// Make the axis gizmo.
|
// Make the axis gizmo.
|
||||||
// We do this after the connection opened to avoid a race condition.
|
// We do this after the connection opened to avoid a race condition.
|
||||||
// Connected opened is the last thing that happens when the stream
|
// Connected opened is the last thing that happens when the stream
|
||||||
@ -953,98 +941,78 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
setIsStreamReady(true)
|
setIsStreamReady(true)
|
||||||
executeCode(undefined, true)
|
executeCode(undefined, true)
|
||||||
})
|
})
|
||||||
}
|
},
|
||||||
)
|
onClose: () => {
|
||||||
|
|
||||||
this.engineConnection.addEventListener(
|
|
||||||
EngineConnectionEvents.Closed,
|
|
||||||
() => {
|
|
||||||
setIsStreamReady(false)
|
setIsStreamReady(false)
|
||||||
}
|
},
|
||||||
)
|
onConnectionStarted: (engineConnection) => {
|
||||||
|
engineConnection?.pc?.addEventListener('datachannel', (event) => {
|
||||||
|
let unreliableDataChannel = event.channel
|
||||||
|
|
||||||
this.engineConnection.addEventListener(
|
unreliableDataChannel.addEventListener('message', (event) => {
|
||||||
EngineConnectionEvents.ConnectionStarted,
|
const result: UnreliableResponses = JSON.parse(event.data)
|
||||||
(({ detail: engineConnection }: CustomEvent) => {
|
Object.values(
|
||||||
engineConnection?.pc?.addEventListener(
|
this.unreliableSubscriptions[result.type] || {}
|
||||||
'datachannel',
|
).forEach(
|
||||||
(event: RTCDataChannelEvent) => {
|
// TODO: There is only one response that uses the unreliable channel atm,
|
||||||
let unreliableDataChannel = event.channel
|
// highlight_set_entity, if there are more it's likely they will all have the same
|
||||||
|
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
||||||
unreliableDataChannel.addEventListener(
|
// per unreliable subscription.
|
||||||
'message',
|
(callback) => {
|
||||||
(event: MessageEvent) => {
|
if (
|
||||||
const result: UnreliableResponses = JSON.parse(event.data)
|
result?.data?.sequence &&
|
||||||
Object.values(
|
result?.data.sequence > this.inSequence &&
|
||||||
this.unreliableSubscriptions[result.type] || {}
|
result.type === 'highlight_set_entity'
|
||||||
).forEach(
|
) {
|
||||||
// TODO: There is only one response that uses the unreliable channel atm,
|
this.inSequence = result.data.sequence
|
||||||
// highlight_set_entity, if there are more it's likely they will all have the same
|
callback(result)
|
||||||
// sequence logic, but I'm not sure if we use a single global sequence or a sequence
|
}
|
||||||
// per unreliable subscription.
|
|
||||||
(callback) => {
|
|
||||||
if (
|
|
||||||
result?.data?.sequence &&
|
|
||||||
result?.data.sequence > this.inSequence &&
|
|
||||||
result.type === 'highlight_set_entity'
|
|
||||||
) {
|
|
||||||
this.inSequence = result.data.sequence
|
|
||||||
callback(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
})
|
||||||
)
|
})
|
||||||
|
|
||||||
// When the EngineConnection starts a connection, we want to register
|
// When the EngineConnection starts a connection, we want to register
|
||||||
// callbacks into the WebSocket/PeerConnection.
|
// callbacks into the WebSocket/PeerConnection.
|
||||||
engineConnection.websocket?.addEventListener(
|
engineConnection.websocket?.addEventListener('message', (event) => {
|
||||||
'message',
|
if (event.data instanceof ArrayBuffer) {
|
||||||
(event: MessageEvent) => {
|
// If the data is an ArrayBuffer, it's the result of an export command,
|
||||||
if (event.data instanceof ArrayBuffer) {
|
// because in all other cases we send JSON strings. But in the case of
|
||||||
// If the data is an ArrayBuffer, it's the result of an export command,
|
// export we send a binary blob.
|
||||||
// because in all other cases we send JSON strings. But in the case of
|
// Pass this to our export function.
|
||||||
// export we send a binary blob.
|
void exportSave(event.data)
|
||||||
// Pass this to our export function.
|
} else {
|
||||||
void exportSave(event.data)
|
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
||||||
} else {
|
event.data
|
||||||
const message: Models['WebSocketResponse_type'] = JSON.parse(
|
)
|
||||||
event.data
|
if (
|
||||||
)
|
message.success &&
|
||||||
if (
|
message.resp.type === 'modeling' &&
|
||||||
message.success &&
|
message.request_id
|
||||||
message.resp.type === 'modeling' &&
|
) {
|
||||||
message.request_id
|
this.handleModelingCommand(message.resp, message.request_id)
|
||||||
) {
|
} else if (
|
||||||
this.handleModelingCommand(message.resp, message.request_id)
|
!message.success &&
|
||||||
} else if (
|
message.request_id &&
|
||||||
!message.success &&
|
this.artifactMap[message.request_id]
|
||||||
message.request_id &&
|
) {
|
||||||
this.artifactMap[message.request_id]
|
this.handleFailedModelingCommand(message)
|
||||||
) {
|
|
||||||
this.handleFailedModelingCommand(message)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
})
|
||||||
}) as EventListener
|
},
|
||||||
)
|
onNewTrack: ({ mediaStream }) => {
|
||||||
|
console.log('received track', mediaStream)
|
||||||
|
|
||||||
this.engineConnection.addEventListener(EngineConnectionEvents.NewTrack, (({
|
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
||||||
detail: { mediaStream },
|
console.log('peer is not sending video to us')
|
||||||
}: CustomEvent) => {
|
// this.engineConnection?.close()
|
||||||
console.log('received track', mediaStream)
|
// this.engineConnection?.connect()
|
||||||
|
})
|
||||||
|
|
||||||
mediaStream.getVideoTracks()[0].addEventListener('mute', () => {
|
setMediaStream(mediaStream)
|
||||||
console.log('peer is not sending video to us')
|
},
|
||||||
// this.engineConnection?.close()
|
})
|
||||||
// this.engineConnection?.connect()
|
|
||||||
})
|
|
||||||
|
|
||||||
setMediaStream(mediaStream)
|
|
||||||
}) as EventListener)
|
|
||||||
|
|
||||||
this.engineConnection?.connect()
|
this.engineConnection?.connect()
|
||||||
}
|
}
|
||||||
@ -1234,6 +1202,9 @@ export class EngineCommandManager extends EventTarget {
|
|||||||
) {
|
) {
|
||||||
delete this.unreliableSubscriptions[event][id]
|
delete this.unreliableSubscriptions[event][id]
|
||||||
}
|
}
|
||||||
|
onConnectionStateChange(callback: (state: EngineConnectionState) => void) {
|
||||||
|
this.callbacksEngineStateConnection.push(callback)
|
||||||
|
}
|
||||||
endSession() {
|
endSession() {
|
||||||
// TODO: instead of sending a single command with `object_ids: Object.keys(this.artifactMap)`
|
// TODO: instead of sending a single command with `object_ids: Object.keys(this.artifactMap)`
|
||||||
// we need to loop over them each individually because if the engine doesn't recognise a single
|
// we need to loop over them each individually because if the engine doesn't recognise a single
|
||||||
|
Reference in New Issue
Block a user