From 36d571c84ab626b93d68b2542ae243858947ce6c Mon Sep 17 00:00:00 2001 From: "Evgeny @ SimpleX Chat" <259188159+evgeny-simplex@users.noreply.github.com> Date: Sun, 13 Sep 2026 19:09:50 +0000 Subject: [PATCH] refactor --- packages/simplex-chat-webrtc/src/call.ts | 160 +++++++---------------- 1 file changed, 49 insertions(+), 111 deletions(-) diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index 8637ad5c89..f193195f45 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -306,7 +306,6 @@ let inactiveCallMediaSources: CallMediaSources = { let activeCall: Call | undefined let notConnectedCall: NotConnectedCall | undefined let answerTimeout = 30_000 -// How long a call is allowed to stay disconnected before it is ended let reconnectTimeout = 30_000 var useWorker = false var isDesktop = false @@ -492,8 +491,7 @@ const processCommand = (function () { localOrPeerMediaSourcesChanged(call) await setupMediaStreams(call) let connectionTimeout: number | undefined = setTimeout(connectionHandler, answerTimeout) - // disconnect call if it does not reconnect in time - let disconnectedTimeout: number | undefined + let reconnectingTimeout: number | undefined let wasConnected = false if (pc.connectionState) { pc.addEventListener("connectionstatechange", connectionStateChange) @@ -505,44 +503,59 @@ const processCommand = (function () { async function connectionStateChange() { // "failed" means the second party did not answer in time (15 sec timeout in Chrome WebView) // See https://source.chromium.org/chromium/chromium/src/+/main:third_party/webrtc/p2p/base/p2p_constants.cc;l=70) - // Once the call is connected failed needs to work otherwise the call would go on forever if (pc.connectionState !== "failed" || wasConnected) connectionHandler() } - async function connectionHandler() { - if (reconnecting()) { - // The call was connected and lost connection should be reconnected without any action as long as connection issue is fixed - // call only ends if it doesn't reconnect within reconnectTimeout timeframe. - // "reconnecting" is reported instead of "disconnected"/"failed" - if (disconnectedTimeout === undefined) { - sendConnectionState("reconnecting") - disconnectedTimeout = setTimeout(() => { - disconnectedTimeout = undefined - if (activeCall === call && !connected()) { - // call ends because call did not reconnect - // makes sure to set call to disconnected - sendConnectionState("disconnected") - terminateCall() - } - }, reconnectTimeout) - } - return - } - if (wasConnected && connecting()) { - return - } - if (connected()) { - const restored = wasConnected - wasConnected = true - clearDisconnectedTimeout() - clearConnectionTimeout() - // reconnecting turns back to connected - // client keeps original call duration - if (restored) { - sendConnectionState() + async function connectionHandler(reconnectTimedOut = false) { + let state: string = + pc.connectionState ?? + (pc.iceConnectionState != "completed" && pc.iceConnectionState != "checking" + ? pc.iceConnectionState + : pc.iceConnectionState == "completed" + ? "connected" + : "connecting") /* webView 69-70 doesn't have connectionState yet */ + if (wasConnected) { + if (reconnectTimedOut) { + reconnectingTimeout = undefined + if (activeCall !== call || state == "connected") return + state = "disconnected" + } else if (state == "disconnected" || state == "failed") { + if (reconnectingTimeout !== undefined) return + reconnectingTimeout = setTimeout(() => connectionHandler(true), reconnectTimeout) + state = "reconnecting" + } else if (state == "connecting" || state == "new") { return } - sendConnectionState() + } + sendMessageToNative({ + resp: { + type: "connection", + state: { + connectionState: state, + iceConnectionState: pc.iceConnectionState, + iceGatheringState: pc.iceGatheringState, + signalingState: pc.signalingState, + }, + }, + }) + if (state == "disconnected" || state == "failed") { + clearConnectionTimeout() + clearTimeout(reconnectingTimeout) + if (pc.connectionState) { + pc.removeEventListener("connectionstatechange", connectionStateChange) + } else { + pc.removeEventListener("iceconnectionstatechange", connectionStateChange) + } + if (activeCall) { + setTimeout(() => sendMessageToNative({resp: {type: "ended"}}), 0) + } + endCall() + } else if (state == "connected") { + clearConnectionTimeout() + clearTimeout(reconnectingTimeout) + reconnectingTimeout = undefined + if (wasConnected) return + wasConnected = true const stats = (await pc.getStats()) as Map for (const stat of stats.values()) { const {type, state} = stat @@ -560,90 +573,15 @@ const processCommand = (function () { break } } - return - } - sendConnectionState() - if (disconnected() || failed()) { - // the call was not connected - terminateCall() } } - function sendConnectionState(state?: string) { - sendMessageToNative({ - resp: { - type: "connection", - state: { - connectionState: - state ?? - pc.connectionState ?? - (pc.iceConnectionState != "completed" && pc.iceConnectionState != "checking" - ? pc.iceConnectionState - : pc.iceConnectionState == "completed" - ? "connected" - : "connecting") /* webView 69-70 doesn't have connectionState yet */, - iceConnectionState: pc.iceConnectionState, - iceGatheringState: pc.iceGatheringState, - signalingState: pc.signalingState, - }, - }, - }) - } - - // webView 69-70 has no connectionState, and it reports a connected call as "completed" as well - function connected(): boolean { - return ( - pc.connectionState == "connected" || - (!pc.connectionState && (pc.iceConnectionState == "connected" || pc.iceConnectionState == "completed")) - ) - } - function connecting(): boolean { - return ( - pc.connectionState == "connecting" || - pc.connectionState == "new" || - (!pc.connectionState && (pc.iceConnectionState == "checking" || pc.iceConnectionState == "new")) - ) - } - - function disconnected(): boolean { - return pc.connectionState == "disconnected" || (!pc.connectionState && pc.iceConnectionState == "disconnected") - } - - function failed(): boolean { - return pc.connectionState == "failed" || (!pc.connectionState && pc.iceConnectionState == "failed") - } - - // the connected call lost connection and could reconnect - function reconnecting(): boolean { - return wasConnected && (disconnected() || failed()) - } - - function terminateCall() { - clearConnectionTimeout() - clearDisconnectedTimeout() - if (pc.connectionState) { - pc.removeEventListener("connectionstatechange", connectionStateChange) - } else { - pc.removeEventListener("iceconnectionstatechange", connectionStateChange) - } - if (activeCall) { - setTimeout(() => sendMessageToNative({resp: {type: "ended"}}), 0) - } - endCall() - } function clearConnectionTimeout() { if (connectionTimeout) { clearTimeout(connectionTimeout) connectionTimeout = undefined } } - - function clearDisconnectedTimeout() { - if (disconnectedTimeout !== undefined) { - clearTimeout(disconnectedTimeout) - disconnectedTimeout = undefined - } - } } function serialize(x: T): string {