From 67665dde563526656d3e9cd9975e9a558b8c49fb Mon Sep 17 00:00:00 2001
From: Audric Ackermann <audric@loki.network>
Date: Mon, 15 Nov 2021 16:44:19 +1100
Subject: [PATCH] handle network switching with webrtc

dirty hack from
https://bugs.chromium.org/p/chromium/issues/detail?id=982793
---
 ts/receiver/callMessage.ts         |  2 +-
 ts/session/utils/CallManager.ts    | 92 +++++++++++++++++++-----------
 ts/session/utils/RingingManager.ts |  2 +
 3 files changed, 61 insertions(+), 35 deletions(-)

diff --git a/ts/receiver/callMessage.ts b/ts/receiver/callMessage.ts
index 5999937e0..47fc25600 100644
--- a/ts/receiver/callMessage.ts
+++ b/ts/receiver/callMessage.ts
@@ -50,7 +50,7 @@ export async function handleCallMessage(
   if (type === SignalService.CallMessage.Type.END_CALL) {
     await removeFromCache(envelope);
 
-    CallManager.handleCallTypeEndCall(sender);
+    CallManager.handleCallTypeEndCall(sender, callMessage.uuid);
 
     return;
   }
diff --git a/ts/session/utils/CallManager.ts b/ts/session/utils/CallManager.ts
index cbb1483b2..56f613ed6 100644
--- a/ts/session/utils/CallManager.ts
+++ b/ts/session/utils/CallManager.ts
@@ -469,12 +469,12 @@ const findLastMessageTypeFromSender = (sender: string, msgType: SignalService.Ca
   // FIXME this does not sort by timestamp as we do not have a timestamp stored in the SignalService.CallMessage object...
   const allMsg = _.flattenDeep([...msgCacheFromSenderWithDevices.values()]);
   const allMsgFromType = allMsg.filter(m => m.type === msgType);
-  const lastOfferMessage = _.last(allMsgFromType);
+  const lastMessageOfType = _.last(allMsgFromType);
 
-  if (!lastOfferMessage) {
+  if (!lastMessageOfType) {
     return undefined;
   }
-  return lastOfferMessage;
+  return lastMessageOfType;
 };
 
 function handleSignalingStateChangeEvent() {
@@ -486,7 +486,7 @@ function handleSignalingStateChangeEvent() {
 function handleConnectionStateChanged(pubkey: string) {
   window.log.info('handleConnectionStateChanged :', peerConnection?.connectionState);
 
-  if (peerConnection?.signalingState === 'closed') {
+  if (peerConnection?.signalingState === 'closed' || peerConnection?.connectionState === 'failed') {
     closeVideoCall();
   } else if (peerConnection?.connectionState === 'connected') {
     setIsRinging(false);
@@ -496,6 +496,7 @@ function handleConnectionStateChanged(pubkey: string) {
 
 function closeVideoCall() {
   window.log.info('closingVideoCall ');
+  setIsRinging(false);
   if (peerConnection) {
     peerConnection.ontrack = null;
     peerConnection.onicecandidate = null;
@@ -530,8 +531,14 @@ function closeVideoCall() {
   selectedCameraId = DEVICE_DISABLED_DEVICE_ID;
   selectedAudioInputId = DEVICE_DISABLED_DEVICE_ID;
   currentCallUUID = undefined;
+
   window.inboxStore?.dispatch(setFullScreenCall(false));
+  remoteVideoStreamIsMuted = true;
 
+  makingOffer = false;
+  ignoreOffer = false;
+  isSettingRemoteAnswerPending = false;
+  lastOutgoingOfferTimestamp = -Infinity;
   callVideoListeners();
 }
 
@@ -552,7 +559,7 @@ function onDataChannelReceivedMessage(ev: MessageEvent<string>) {
       if (!foundEntry || !foundEntry.id) {
         return;
       }
-      handleCallTypeEndCall(foundEntry.id);
+      handleCallTypeEndCall(foundEntry.id, currentCallUUID);
 
       return;
     }
@@ -612,6 +619,24 @@ function createOrGetPeerConnection(withPubkey: string, isAcceptingCall = false)
     handleIceCandidates(event, withPubkey);
   };
 
+  peerConnection.oniceconnectionstatechange = () => {
+    window.log.info(
+      'oniceconnectionstatechange peerConnection.iceConnectionState: ',
+      peerConnection?.iceConnectionState
+    );
+
+    if (peerConnection && peerConnection?.iceConnectionState === 'disconnected') {
+      //this will trigger a negotation event with iceRestart set to true in the createOffer options set
+      global.setTimeout(() => {
+        window.log.info('onconnectionstatechange disconnected: restartIce()');
+
+        if (peerConnection?.iceConnectionState === 'disconnected') {
+          (peerConnection as any).restartIce();
+        }
+      }, 2000);
+    }
+  };
+
   return peerConnection;
 }
 
@@ -689,22 +714,31 @@ export async function USER_acceptIncomingCallRequest(fromSender: string) {
 // tslint:disable-next-line: function-name
 export async function USER_rejectIncomingCallRequest(fromSender: string) {
   setIsRinging(false);
-  const endCallMessage = new CallMessage({
-    type: SignalService.CallMessage.Type.END_CALL,
-    timestamp: Date.now(),
-    uuid: uuidv4(), // just send a random thing, we just want to reject the call
-  });
-  // delete all msg not from that uuid only but from that sender pubkey
+
+  const lastOfferMessage = findLastMessageTypeFromSender(
+    fromSender,
+    SignalService.CallMessage.Type.OFFER
+  );
+
+  const lastCallUUID = lastOfferMessage?.uuid;
+  window.log.info(`USER_rejectIncomingCallRequest ${ed25519Str(fromSender)}: ${lastCallUUID}`);
+  if (lastCallUUID) {
+    const endCallMessage = new CallMessage({
+      type: SignalService.CallMessage.Type.END_CALL,
+      timestamp: Date.now(),
+      uuid: lastCallUUID,
+    });
+    await getMessageQueue().sendToPubKeyNonDurably(PubKey.cast(fromSender), endCallMessage);
+
+    // delete all msg not from that uuid only but from that sender pubkey
+    clearCallCacheFromPubkeyAndUUID(fromSender, lastCallUUID);
+  }
 
   window.inboxStore?.dispatch(
     endCall({
       pubkey: fromSender,
     })
   );
-  window.log.info('USER_rejectIncomingCallRequest');
-  clearCallCacheFromPubkey(fromSender);
-
-  await getMessageQueue().sendToPubKeyNonDurably(PubKey.cast(fromSender), endCallMessage);
 
   const convos = getConversationController().getConversations();
   const callingConvos = convos.filter(convo => convo.callState !== undefined);
@@ -737,28 +771,18 @@ export async function USER_hangup(fromSender: string) {
 
   sendHangupViaDataChannel();
 
-  clearCallCacheFromPubkey(fromSender);
+  clearCallCacheFromPubkeyAndUUID(fromSender, currentCallUUID);
 
-  const convos = getConversationController().getConversations();
-  const callingConvos = convos.filter(convo => convo.callState !== undefined);
-  if (callingConvos.length > 0) {
-    // we just got a new offer from someone we are already in a call with
-    if (callingConvos.length === 1 && callingConvos[0].id === fromSender) {
-      closeVideoCall();
-    }
-  }
+  closeVideoCall();
 }
 
-export function handleCallTypeEndCall(sender: string) {
-  clearCallCacheFromPubkey(sender);
+export function handleCallTypeEndCall(sender: string, aboutCallUUID?: string) {
+  window.log.info('handling callMessage END_CALL:', aboutCallUUID);
 
-  window.log.info('handling callMessage END_CALL');
+  if (aboutCallUUID) {
+    clearCallCacheFromPubkeyAndUUID(sender, aboutCallUUID);
 
-  const convos = getConversationController().getConversations();
-  const callingConvos = convos.filter(convo => convo.callState !== undefined);
-  if (callingConvos.length > 0) {
-    // we just got a end call event from whoever we are in a call with
-    if (callingConvos.length === 1 && callingConvos[0].id === sender) {
+    if (aboutCallUUID === currentCallUUID) {
       closeVideoCall();
 
       window.inboxStore?.dispatch(endCall({ pubkey: sender }));
@@ -983,8 +1007,8 @@ export async function handleOtherCallTypes(sender: string, callMessage: SignalSe
   pushCallMessageToCallCache(sender, remoteCallUUID, callMessage);
 }
 
-function clearCallCacheFromPubkey(sender: string) {
-  callCache.delete(sender);
+function clearCallCacheFromPubkeyAndUUID(sender: string, callUUID: string) {
+  callCache.get(sender)?.delete(callUUID);
 }
 
 function createCallCacheForPubkeyAndUUID(sender: string, uuid: string) {
diff --git a/ts/session/utils/RingingManager.ts b/ts/session/utils/RingingManager.ts
index 5e9d88f61..05c9fcc87 100644
--- a/ts/session/utils/RingingManager.ts
+++ b/ts/session/utils/RingingManager.ts
@@ -7,6 +7,7 @@ let ringingAudio: HTMLAudioElement | undefined;
 function stopRinging() {
   if (ringingAudio) {
     ringingAudio.pause();
+    ringingAudio.srcObject = null;
   }
 }
 
@@ -14,6 +15,7 @@ function startRinging() {
   if (!ringingAudio) {
     ringingAudio = new Audio(sound);
     ringingAudio.loop = true;
+    ringingAudio.volume = 0.6;
   }
   void ringingAudio.play();
 }
