Skip to content

Commit 853ae28

Browse files
authored
fix: prevent WebRTC InvalidStateError when setting remote description (#131)
1 parent 2300688 commit 853ae28

1 file changed

Lines changed: 71 additions & 11 deletions

File tree

src/hooks/useWebRTC.ts

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export function useWebRTC({ entityId, enabled = true }: UseWebRTCOptions): UseWe
3939
const peerConnectionRef = useRef<RTCPeerConnection | null>(null)
4040
const initializingRef = useRef<boolean>(false)
4141
const pendingInitRef = useRef<boolean>(false)
42+
const pendingCandidatesRef = useRef<RTCIceCandidateInit[]>([])
4243
const [isStreaming, setIsStreaming] = useState(false)
4344
const [error, setError] = useState<string | null>(null)
4445
const [retryCount, setRetryCount] = useState(0)
@@ -51,6 +52,7 @@ export function useWebRTC({ entityId, enabled = true }: UseWebRTCOptions): UseWe
5152
peerConnectionRef.current = null
5253
initializingRef.current = false
5354
pendingInitRef.current = false
55+
pendingCandidatesRef.current = []
5456

5557
if (pc) {
5658
// Unsubscribe from WebSocket messages if available
@@ -84,11 +86,20 @@ export function useWebRTC({ entityId, enabled = true }: UseWebRTCOptions): UseWe
8486

8587
// Prevent multiple initializations
8688
if (peerConnectionRef.current || initializingRef.current) {
89+
// If we have an existing connection that's still active, don't reinitialize
90+
if (
91+
peerConnectionRef.current &&
92+
(peerConnectionRef.current.connectionState === 'connected' ||
93+
peerConnectionRef.current.connectionState === 'connecting')
94+
) {
95+
return
96+
}
8797
return
8898
}
8999

90100
initializingRef.current = true
91101
setError(null)
102+
pendingCandidatesRef.current = []
92103

93104
try {
94105
// Create peer connection with STUN servers
@@ -114,9 +125,27 @@ export function useWebRTC({ entityId, enabled = true }: UseWebRTCOptions): UseWe
114125

115126
// Handle connection state changes
116127
pc.onconnectionstatechange = () => {
117-
if (pc.connectionState === 'failed' || pc.connectionState === 'disconnected') {
118-
setError('Connection lost')
119-
setIsStreaming(false)
128+
console.log(`[WebRTC] Connection state changed to: ${pc.connectionState}`)
129+
130+
switch (pc.connectionState) {
131+
case 'failed':
132+
setError('Connection failed')
133+
setIsStreaming(false)
134+
// Clean up the connection
135+
cleanup()
136+
break
137+
case 'disconnected':
138+
setError('Connection lost')
139+
setIsStreaming(false)
140+
break
141+
case 'connected':
142+
// Clear any previous errors when successfully connected
143+
setError(null)
144+
break
145+
case 'closed':
146+
// Connection was closed, ensure we're cleaned up
147+
setIsStreaming(false)
148+
break
120149
}
121150
}
122151

@@ -166,18 +195,49 @@ export function useWebRTC({ entityId, enabled = true }: UseWebRTCOptions): UseWe
166195
if (!message.answer) {
167196
return
168197
}
169-
// Set the remote description
170-
pc.setRemoteDescription({ type: 'answer', sdp: message.answer }).catch((err) => {
171-
console.error('[WebRTC] Failed to set remote description:', err)
172-
setError('Failed to set remote description')
173-
})
198+
// Check if we're in the correct state to set remote description
199+
if (pc.signalingState === 'have-local-offer') {
200+
// Set the remote description
201+
pc.setRemoteDescription({ type: 'answer', sdp: message.answer })
202+
.then(() => {
203+
// Process any pending ICE candidates
204+
if (pendingCandidatesRef.current.length > 0) {
205+
console.log(
206+
`[WebRTC] Processing ${pendingCandidatesRef.current.length} pending ICE candidates`
207+
)
208+
pendingCandidatesRef.current.forEach((candidate) => {
209+
pc.addIceCandidate(candidate).catch((err) =>
210+
console.error('[WebRTC] Failed to add pending ICE candidate:', err)
211+
)
212+
})
213+
pendingCandidatesRef.current = []
214+
}
215+
})
216+
.catch((err) => {
217+
console.error('[WebRTC] Failed to set remote description:', err)
218+
setError('Failed to set remote description')
219+
})
220+
} else {
221+
console.warn(
222+
`[WebRTC] Ignoring answer in wrong state: ${pc.signalingState}. This may indicate duplicate messages or timing issues.`
223+
)
224+
}
174225
break
175226

176227
case 'candidate':
177228
if (message.candidate) {
178-
pc.addIceCandidate(message.candidate).catch((err) =>
179-
console.error('[WebRTC] Failed to add ICE candidate:', err)
180-
)
229+
// Only add ICE candidates if we have a remote description
230+
if (pc.remoteDescription) {
231+
pc.addIceCandidate(message.candidate).catch((err) =>
232+
console.error('[WebRTC] Failed to add ICE candidate:', err)
233+
)
234+
} else {
235+
// Queue the candidate to be added later when remote description is set
236+
pendingCandidatesRef.current.push(message.candidate)
237+
console.log(
238+
`[WebRTC] Queuing ICE candidate (${pendingCandidatesRef.current.length} pending)`
239+
)
240+
}
181241
}
182242
break
183243

0 commit comments

Comments
 (0)