| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  | // Copyright 2020-2021 Signal Messenger, LLC
 | 
					
						
							| 
									
										
										
										
											2020-10-30 15:34:04 -05:00
										 |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  | /* eslint-disable class-methods-use-this */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  | import { desktopCapturer, ipcRenderer } from 'electron'; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | import { | 
					
						
							|  |  |  |   Call, | 
					
						
							|  |  |  |   CallEndedReason, | 
					
						
							|  |  |  |   CallId, | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |   CallingMessage, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   CallLogLevel, | 
					
						
							|  |  |  |   CallSettings, | 
					
						
							|  |  |  |   CallState, | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   CanvasVideoRenderer, | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   ConnectionState, | 
					
						
							|  |  |  |   JoinState, | 
					
						
							|  |  |  |   HttpMethod, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   DeviceId, | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   GroupCall, | 
					
						
							|  |  |  |   GroupMemberInfo, | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   GumVideoCapturer, | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |   HangupMessage, | 
					
						
							|  |  |  |   HangupType, | 
					
						
							|  |  |  |   OfferType, | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   OpaqueMessage, | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  |   PeekInfo, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   RingRTC, | 
					
						
							|  |  |  |   UserId, | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   VideoFrameSource, | 
					
						
							| 
									
										
										
										
											2020-12-01 19:52:01 -06:00
										 |  |  |   VideoRequest, | 
					
						
							| 
									
										
										
										
											2021-02-04 11:54:18 -08:00
										 |  |  |   BandwidthMode, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | } from 'ringrtc'; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | import { uniqBy, noop } from 'lodash'; | 
					
						
							| 
									
										
										
										
											2020-09-11 17:53:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  | import { | 
					
						
							|  |  |  |   ActionsType as UxActionsType, | 
					
						
							|  |  |  |   GroupCallPeekInfoType, | 
					
						
							|  |  |  | } from '../state/ducks/calling'; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | import { getConversationCallMode } from '../state/ducks/conversations'; | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  | import { EnvelopeClass } from '../textsecure.d'; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | import { | 
					
						
							|  |  |  |   CallMode, | 
					
						
							|  |  |  |   AudioDevice, | 
					
						
							|  |  |  |   MediaDeviceSettings, | 
					
						
							|  |  |  |   GroupCallConnectionState, | 
					
						
							|  |  |  |   GroupCallJoinState, | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |   PresentableSource, | 
					
						
							|  |  |  |   PresentedSource, | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | } from '../types/Calling'; | 
					
						
							| 
									
										
										
										
											2021-06-03 11:42:30 -07:00
										 |  |  | import { LocalizerType } from '../types/Util'; | 
					
						
							| 
									
										
										
										
											2020-09-24 13:57:54 -07:00
										 |  |  | import { ConversationModel } from '../models/conversations'; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | import { | 
					
						
							|  |  |  |   base64ToArrayBuffer, | 
					
						
							|  |  |  |   uuidToArrayBuffer, | 
					
						
							|  |  |  |   arrayBufferToUuid, | 
					
						
							|  |  |  | } from '../Crypto'; | 
					
						
							|  |  |  | import { getOwn } from '../util/getOwn'; | 
					
						
							| 
									
										
										
										
											2020-12-09 16:02:50 -06:00
										 |  |  | import { | 
					
						
							|  |  |  |   fetchMembershipProof, | 
					
						
							|  |  |  |   getMembershipList, | 
					
						
							|  |  |  |   wrapWithSyncMessageSend, | 
					
						
							|  |  |  | } from '../groups'; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | import { missingCaseError } from '../util/missingCaseError'; | 
					
						
							| 
									
										
										
										
											2020-12-01 17:52:09 -06:00
										 |  |  | import { normalizeGroupCallTimestamp } from '../util/ringrtc/normalizeGroupCallTimestamp'; | 
					
						
							| 
									
										
										
										
											2020-12-11 18:44:07 -06:00
										 |  |  | import { | 
					
						
							|  |  |  |   REQUESTED_VIDEO_WIDTH, | 
					
						
							|  |  |  |   REQUESTED_VIDEO_HEIGHT, | 
					
						
							|  |  |  |   REQUESTED_VIDEO_FRAMERATE, | 
					
						
							|  |  |  | } from '../calling/constants'; | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  | import { notify } from './notify'; | 
					
						
							| 
									
										
										
										
											2021-06-07 12:39:13 -04:00
										 |  |  | import { getSendOptions } from '../util/getSendOptions'; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  | const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map< | 
					
						
							|  |  |  |   HttpMethod, | 
					
						
							| 
									
										
										
										
											2020-11-17 13:49:48 -06:00
										 |  |  |   'GET' | 'PUT' | 'POST' | 'DELETE' | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | > = new Map([ | 
					
						
							|  |  |  |   [HttpMethod.Get, 'GET'], | 
					
						
							|  |  |  |   [HttpMethod.Put, 'PUT'], | 
					
						
							|  |  |  |   [HttpMethod.Post, 'POST'], | 
					
						
							| 
									
										
										
										
											2020-11-17 13:49:48 -06:00
										 |  |  |   [HttpMethod.Delete, 'DELETE'], | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | ]); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  | // We send group call update messages to tell other clients to peek, which triggers
 | 
					
						
							|  |  |  | //   notifications, timeline messages, big green "Join" buttons, and so on. This enum
 | 
					
						
							|  |  |  | //   represents the three possible states we can be in. This helps ensure that we don't
 | 
					
						
							|  |  |  | //   send an update on disconnect if we never sent one when we joined.
 | 
					
						
							|  |  |  | enum GroupCallUpdateMessageState { | 
					
						
							|  |  |  |   SentNothing, | 
					
						
							|  |  |  |   SentJoin, | 
					
						
							|  |  |  |   SentLeft, | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-03 12:52:11 -07:00
										 |  |  | function isScreenSource(source: PresentedSource): boolean { | 
					
						
							| 
									
										
										
										
											2021-06-03 11:42:30 -07:00
										 |  |  |   return source.id.startsWith('screen'); | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-06-03 12:52:11 -07:00
										 |  |  | function translateSourceName( | 
					
						
							| 
									
										
										
										
											2021-06-03 11:42:30 -07:00
										 |  |  |   i18n: LocalizerType, | 
					
						
							|  |  |  |   source: PresentedSource | 
					
						
							|  |  |  | ): string { | 
					
						
							|  |  |  |   const { name } = source; | 
					
						
							|  |  |  |   if (!isScreenSource(source)) { | 
					
						
							|  |  |  |     return name; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   if (name === 'Entire Screen') { | 
					
						
							|  |  |  |     return i18n('calling__SelectPresentingSourcesModal--entireScreen'); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const match = name.match(/^Screen (\d+)$/); | 
					
						
							|  |  |  |   if (match) { | 
					
						
							|  |  |  |     return i18n('calling__SelectPresentingSourcesModal--screen', { | 
					
						
							|  |  |  |       id: match[1], | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   return name; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | export class CallingClass { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   readonly videoCapturer: GumVideoCapturer; | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   readonly videoRenderer: CanvasVideoRenderer; | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   private uxActions?: UxActionsType; | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |   private sfuUrl?: string; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   private lastMediaDeviceSettings?: MediaDeviceSettings; | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   private deviceReselectionTimer?: NodeJS.Timeout; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   private callsByConversation: { [conversationId: string]: Call | GroupCall }; | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |   private hadLocalVideoBeforePresenting?: boolean; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   constructor() { | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |     this.videoCapturer = new GumVideoCapturer({ | 
					
						
							|  |  |  |       maxWidth: REQUESTED_VIDEO_WIDTH, | 
					
						
							|  |  |  |       maxHeight: REQUESTED_VIDEO_HEIGHT, | 
					
						
							|  |  |  |       maxFramerate: REQUESTED_VIDEO_FRAMERATE, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     this.videoRenderer = new CanvasVideoRenderer(); | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.callsByConversation = {}; | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |   initialize(uxActions: UxActionsType, sfuUrl: string): void { | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     this.uxActions = uxActions; | 
					
						
							|  |  |  |     if (!uxActions) { | 
					
						
							|  |  |  |       throw new Error('CallingClass.initialize: Invalid uxActions.'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.sfuUrl = sfuUrl; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     RingRTC.handleOutgoingSignaling = this.handleOutgoingSignaling.bind(this); | 
					
						
							|  |  |  |     RingRTC.handleIncomingCall = this.handleIncomingCall.bind(this); | 
					
						
							|  |  |  |     RingRTC.handleAutoEndedIncomingCallRequest = this.handleAutoEndedIncomingCallRequest.bind( | 
					
						
							|  |  |  |       this | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     RingRTC.handleLogMessage = this.handleLogMessage.bind(this); | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     RingRTC.handleSendHttpRequest = this.handleSendHttpRequest.bind(this); | 
					
						
							|  |  |  |     RingRTC.handleSendCallMessage = this.handleSendCallMessage.bind(this); | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     ipcRenderer.on('stop-screen-share', () => { | 
					
						
							|  |  |  |       uxActions.setPresenting(); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |   async startCallingLobby( | 
					
						
							| 
									
										
										
										
											2020-12-07 14:43:19 -06:00
										 |  |  |     conversationId: string, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     isVideoCall: boolean | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |     window.log.info('CallingClass.startCallingLobby()'); | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 14:43:19 -06:00
										 |  |  |     const conversation = window.ConversationController.get(conversationId); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       window.log.error('Could not find conversation, cannot start call lobby'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     const conversationProps = conversation.format(); | 
					
						
							|  |  |  |     const callMode = getConversationCallMode(conversationProps); | 
					
						
							|  |  |  |     switch (callMode) { | 
					
						
							|  |  |  |       case CallMode.None: | 
					
						
							|  |  |  |         window.log.error( | 
					
						
							|  |  |  |           'Conversation does not support calls, new call not allowed.' | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       case CallMode.Direct: | 
					
						
							|  |  |  |         if (!this.getRemoteUserIdFromConversation(conversation)) { | 
					
						
							|  |  |  |           window.log.error( | 
					
						
							|  |  |  |             'Missing remote user identifier, new call not allowed.' | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case CallMode.Group: | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         throw missingCaseError(callMode); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     if (!this.uxActions) { | 
					
						
							|  |  |  |       window.log.error('Missing uxActions, new call not allowed.'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     if (!this.localDeviceId) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'Missing local device identifier, new call not allowed.' | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const haveMediaPermissions = await this.requestPermissions(isVideoCall); | 
					
						
							|  |  |  |     if (!haveMediaPermissions) { | 
					
						
							|  |  |  |       window.log.info('Permissions were denied, new call not allowed.'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     window.log.info('CallingClass.startCallingLobby(): Starting lobby'); | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-10 12:39:39 -06:00
										 |  |  |     // It's important that this function comes before any calls to
 | 
					
						
							|  |  |  |     //   `videoCapturer.enableCapture` or `videoCapturer.enableCaptureAndSend` because of
 | 
					
						
							|  |  |  |     //   a small RingRTC bug.
 | 
					
						
							|  |  |  |     //
 | 
					
						
							|  |  |  |     // If we tell RingRTC to start capturing video (with those methods or with
 | 
					
						
							|  |  |  |     //   `RingRTC.setPreferredDevice`, which also captures video) multiple times in quick
 | 
					
						
							|  |  |  |     //   succession, it will call the asynchronous `getUserMedia` twice. It'll save the
 | 
					
						
							|  |  |  |     //   results in the same variable, which means the first call can be overridden.
 | 
					
						
							|  |  |  |     //   Later, when we try to turn the camera off, we'll only disable the *second* result
 | 
					
						
							|  |  |  |     //   of `getUserMedia` and the camera will stay on.
 | 
					
						
							|  |  |  |     //
 | 
					
						
							|  |  |  |     // We get around this by `await`ing, making sure we're all done with `getUserMedia`,
 | 
					
						
							|  |  |  |     //   and then continuing.
 | 
					
						
							|  |  |  |     //
 | 
					
						
							|  |  |  |     // We should be able to move this below `this.connectGroupCall` once that RingRTC bug
 | 
					
						
							|  |  |  |     //   is fixed. See DESKTOP-1032.
 | 
					
						
							|  |  |  |     await this.startDeviceReselectionTimer(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     switch (callMode) { | 
					
						
							|  |  |  |       case CallMode.Direct: | 
					
						
							|  |  |  |         this.uxActions.showCallLobby({ | 
					
						
							|  |  |  |           callMode: CallMode.Direct, | 
					
						
							|  |  |  |           conversationId: conversationProps.id, | 
					
						
							|  |  |  |           hasLocalAudio: true, | 
					
						
							|  |  |  |           hasLocalVideo: isVideoCall, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case CallMode.Group: { | 
					
						
							|  |  |  |         if ( | 
					
						
							|  |  |  |           !conversationProps.groupId || | 
					
						
							|  |  |  |           !conversationProps.publicParams || | 
					
						
							|  |  |  |           !conversationProps.secretParams | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |           window.log.error( | 
					
						
							|  |  |  |             'Conversation is missing required parameters. Cannot connect group call' | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         const groupCall = this.connectGroupCall(conversationProps.id, { | 
					
						
							|  |  |  |           groupId: conversationProps.groupId, | 
					
						
							|  |  |  |           publicParams: conversationProps.publicParams, | 
					
						
							|  |  |  |           secretParams: conversationProps.secretParams, | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         groupCall.setOutgoingAudioMuted(false); | 
					
						
							|  |  |  |         groupCall.setOutgoingVideoMuted(!isVideoCall); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.uxActions.showCallLobby({ | 
					
						
							|  |  |  |           callMode: CallMode.Group, | 
					
						
							|  |  |  |           conversationId: conversationProps.id, | 
					
						
							|  |  |  |           ...this.formatGroupCallForRedux(groupCall), | 
					
						
							|  |  |  |         }); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         throw missingCaseError(callMode); | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-02 19:47:18 -05:00
										 |  |  |     if (isVideoCall) { | 
					
						
							|  |  |  |       this.enableLocalCamera(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   stopCallingLobby(conversationId?: string): void { | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |     this.disableLocalVideo(); | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |     this.stopDeviceReselectionTimer(); | 
					
						
							|  |  |  |     this.lastMediaDeviceSettings = undefined; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (conversationId) { | 
					
						
							|  |  |  |       this.getGroupCall(conversationId)?.disconnect(); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   async startOutgoingDirectCall( | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |     conversationId: string, | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |     hasLocalAudio: boolean, | 
					
						
							|  |  |  |     hasLocalVideo: boolean | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     window.log.info('CallingClass.startOutgoingDirectCall()'); | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!this.uxActions) { | 
					
						
							|  |  |  |       throw new Error('Redux actions not available'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const conversation = window.ConversationController.get(conversationId); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       window.log.error('Could not find conversation, cannot start call'); | 
					
						
							|  |  |  |       this.stopCallingLobby(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const remoteUserId = this.getRemoteUserIdFromConversation(conversation); | 
					
						
							|  |  |  |     if (!remoteUserId || !this.localDeviceId) { | 
					
						
							|  |  |  |       window.log.error('Missing identifier, new call not allowed.'); | 
					
						
							|  |  |  |       this.stopCallingLobby(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |     const haveMediaPermissions = await this.requestPermissions(hasLocalVideo); | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |     if (!haveMediaPermissions) { | 
					
						
							|  |  |  |       window.log.info('Permissions were denied, new call not allowed.'); | 
					
						
							|  |  |  |       this.stopCallingLobby(); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     window.log.info( | 
					
						
							|  |  |  |       'CallingClass.startOutgoingDirectCall(): Getting call settings' | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     const callSettings = await this.getCallSettings(conversation); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // Check state after awaiting to debounce call button.
 | 
					
						
							|  |  |  |     if (RingRTC.call && RingRTC.call.state !== CallState.Ended) { | 
					
						
							|  |  |  |       window.log.info('Call already in progress, new call not allowed.'); | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |       this.stopCallingLobby(); | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     window.log.info( | 
					
						
							|  |  |  |       'CallingClass.startOutgoingDirectCall(): Starting in RingRTC' | 
					
						
							|  |  |  |     ); | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     // We could make this faster by getting the call object
 | 
					
						
							|  |  |  |     // from the RingRTC before we lookup the ICE servers.
 | 
					
						
							|  |  |  |     const call = RingRTC.startOutgoingCall( | 
					
						
							|  |  |  |       remoteUserId, | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |       hasLocalVideo, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       this.localDeviceId, | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       callSettings | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |     RingRTC.setOutgoingAudio(call.callId, hasLocalAudio); | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     RingRTC.setVideoCapturer(call.callId, this.videoCapturer); | 
					
						
							|  |  |  |     RingRTC.setVideoRenderer(call.callId, this.videoRenderer); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     this.attachToCall(conversation, call); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.uxActions.outgoingCall({ | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |       conversationId: conversation.id, | 
					
						
							|  |  |  |       hasLocalAudio, | 
					
						
							|  |  |  |       hasLocalVideo, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     await this.startDeviceReselectionTimer(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   private getDirectCall(conversationId: string): undefined | Call { | 
					
						
							|  |  |  |     const call = getOwn(this.callsByConversation, conversationId); | 
					
						
							|  |  |  |     return call instanceof Call ? call : undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private getGroupCall(conversationId: string): undefined | GroupCall { | 
					
						
							|  |  |  |     const call = getOwn(this.callsByConversation, conversationId); | 
					
						
							|  |  |  |     return call instanceof GroupCall ? call : undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  |   private getGroupCallMembers(conversationId: string) { | 
					
						
							|  |  |  |     return getMembershipList(conversationId).map( | 
					
						
							|  |  |  |       member => | 
					
						
							|  |  |  |         new GroupMemberInfo( | 
					
						
							|  |  |  |           uuidToArrayBuffer(member.uuid), | 
					
						
							|  |  |  |           member.uuidCiphertext | 
					
						
							|  |  |  |         ) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public async peekGroupCall(conversationId: string): Promise<PeekInfo> { | 
					
						
							|  |  |  |     // This can be undefined in two cases:
 | 
					
						
							|  |  |  |     //
 | 
					
						
							|  |  |  |     // 1. There is no group call instance. This is "stateless peeking", and is expected
 | 
					
						
							|  |  |  |     //    when we want to peek on a call that we've never connected to.
 | 
					
						
							|  |  |  |     // 2. There is a group call instance but RingRTC doesn't have the peek info yet. This
 | 
					
						
							|  |  |  |     //    should only happen for a brief period as you connect to the call. (You probably
 | 
					
						
							|  |  |  |     //    don't want to call this function while a group call is connected—you should
 | 
					
						
							|  |  |  |     //    instead be grabbing the peek info off of the instance—but we handle it here
 | 
					
						
							|  |  |  |     //    to avoid possible race conditions.)
 | 
					
						
							|  |  |  |     const statefulPeekInfo = this.getGroupCall(conversationId)?.getPeekInfo(); | 
					
						
							|  |  |  |     if (statefulPeekInfo) { | 
					
						
							|  |  |  |       return statefulPeekInfo; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |     if (!this.sfuUrl) { | 
					
						
							|  |  |  |       throw new Error('Missing SFU URL; not peeking group call'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  |     const conversation = window.ConversationController.get(conversationId); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       throw new Error('Missing conversation; not peeking group call'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const publicParams = conversation.get('publicParams'); | 
					
						
							|  |  |  |     const secretParams = conversation.get('secretParams'); | 
					
						
							|  |  |  |     if (!publicParams || !secretParams) { | 
					
						
							|  |  |  |       throw new Error( | 
					
						
							|  |  |  |         'Conversation is missing required parameters. Cannot peek group call' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const proof = await fetchMembershipProof({ publicParams, secretParams }); | 
					
						
							|  |  |  |     if (!proof) { | 
					
						
							|  |  |  |       throw new Error('No membership proof. Cannot peek group call'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const membershipProof = new TextEncoder().encode(proof).buffer; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return RingRTC.peekGroupCall( | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |       this.sfuUrl, | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  |       membershipProof, | 
					
						
							|  |  |  |       this.getGroupCallMembers(conversationId) | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   /** | 
					
						
							|  |  |  |    * Connect to a conversation's group call and connect it to Redux. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * Should only be called with group call-compatible conversations. | 
					
						
							|  |  |  |    * | 
					
						
							|  |  |  |    * Idempotent. | 
					
						
							|  |  |  |    */ | 
					
						
							|  |  |  |   connectGroupCall( | 
					
						
							|  |  |  |     conversationId: string, | 
					
						
							|  |  |  |     { | 
					
						
							|  |  |  |       groupId, | 
					
						
							|  |  |  |       publicParams, | 
					
						
							|  |  |  |       secretParams, | 
					
						
							|  |  |  |     }: { | 
					
						
							|  |  |  |       groupId: string; | 
					
						
							|  |  |  |       publicParams: string; | 
					
						
							|  |  |  |       secretParams: string; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   ): GroupCall { | 
					
						
							|  |  |  |     const existing = this.getGroupCall(conversationId); | 
					
						
							|  |  |  |     if (existing) { | 
					
						
							|  |  |  |       const isExistingCallNotConnected = | 
					
						
							|  |  |  |         existing.getLocalDeviceState().connectionState === | 
					
						
							|  |  |  |         ConnectionState.NotConnected; | 
					
						
							|  |  |  |       if (isExistingCallNotConnected) { | 
					
						
							|  |  |  |         existing.connect(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return existing; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |     if (!this.sfuUrl) { | 
					
						
							|  |  |  |       throw new Error('Missing SFU URL; not connecting group call'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     const groupIdBuffer = base64ToArrayBuffer(groupId); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  |     let updateMessageState = GroupCallUpdateMessageState.SentNothing; | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     let isRequestingMembershipProof = false; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |     const outerGroupCall = RingRTC.getGroupCall(groupIdBuffer, this.sfuUrl, { | 
					
						
							|  |  |  |       onLocalDeviceStateChanged: groupCall => { | 
					
						
							|  |  |  |         const localDeviceState = groupCall.getLocalDeviceState(); | 
					
						
							|  |  |  |         const { eraId } = groupCall.getPeekInfo() || {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         if (localDeviceState.connectionState === ConnectionState.NotConnected) { | 
					
						
							|  |  |  |           // NOTE: This assumes that only one call is active at a time. For example, if
 | 
					
						
							|  |  |  |           //   there are two calls using the camera, this will disable both of them.
 | 
					
						
							|  |  |  |           //   That's fine for now, but this will break if that assumption changes.
 | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |           this.disableLocalVideo(); | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |           delete this.callsByConversation[conversationId]; | 
					
						
							| 
									
										
										
										
											2020-11-17 13:49:48 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |           if ( | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |             updateMessageState === GroupCallUpdateMessageState.SentJoin && | 
					
						
							|  |  |  |             eraId | 
					
						
							| 
									
										
										
										
											2020-11-17 13:49:48 -06:00
										 |  |  |           ) { | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |             updateMessageState = GroupCallUpdateMessageState.SentLeft; | 
					
						
							|  |  |  |             this.sendGroupCallUpdateMessage(conversationId, eraId); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           this.callsByConversation[conversationId] = groupCall; | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |           // NOTE: This assumes only one active call at a time. See comment above.
 | 
					
						
							|  |  |  |           if (localDeviceState.videoMuted) { | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |             this.disableLocalVideo(); | 
					
						
							| 
									
										
										
										
											2020-11-17 13:49:48 -06:00
										 |  |  |           } else { | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |             this.videoCapturer.enableCaptureAndSend(groupCall); | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |           } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |           if ( | 
					
						
							|  |  |  |             updateMessageState === GroupCallUpdateMessageState.SentNothing && | 
					
						
							|  |  |  |             localDeviceState.joinState === JoinState.Joined && | 
					
						
							|  |  |  |             eraId | 
					
						
							|  |  |  |           ) { | 
					
						
							|  |  |  |             updateMessageState = GroupCallUpdateMessageState.SentJoin; | 
					
						
							|  |  |  |             this.sendGroupCallUpdateMessage(conversationId, eraId); | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.syncGroupCallToRedux(conversationId, groupCall); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onRemoteDeviceStatesChanged: groupCall => { | 
					
						
							|  |  |  |         this.syncGroupCallToRedux(conversationId, groupCall); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onPeekChanged: groupCall => { | 
					
						
							| 
									
										
										
										
											2021-04-02 22:32:04 +02:00
										 |  |  |         const localDeviceState = groupCall.getLocalDeviceState(); | 
					
						
							|  |  |  |         const { eraId } = groupCall.getPeekInfo() || {}; | 
					
						
							|  |  |  |         if ( | 
					
						
							|  |  |  |           updateMessageState === GroupCallUpdateMessageState.SentNothing && | 
					
						
							|  |  |  |           localDeviceState.connectionState !== ConnectionState.NotConnected && | 
					
						
							|  |  |  |           localDeviceState.joinState === JoinState.Joined && | 
					
						
							|  |  |  |           eraId | 
					
						
							|  |  |  |         ) { | 
					
						
							|  |  |  |           updateMessageState = GroupCallUpdateMessageState.SentJoin; | 
					
						
							|  |  |  |           this.sendGroupCallUpdateMessage(conversationId, eraId); | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-07 14:43:19 -06:00
										 |  |  |         this.updateCallHistoryForGroupCall( | 
					
						
							|  |  |  |           conversationId, | 
					
						
							|  |  |  |           groupCall.getPeekInfo() | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |         this.syncGroupCallToRedux(conversationId, groupCall); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       async requestMembershipProof(groupCall) { | 
					
						
							|  |  |  |         if (isRequestingMembershipProof) { | 
					
						
							|  |  |  |           return; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         isRequestingMembershipProof = true; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           const proof = await fetchMembershipProof({ | 
					
						
							|  |  |  |             publicParams, | 
					
						
							|  |  |  |             secretParams, | 
					
						
							|  |  |  |           }); | 
					
						
							|  |  |  |           if (proof) { | 
					
						
							|  |  |  |             const proofArray = new TextEncoder().encode(proof); | 
					
						
							|  |  |  |             groupCall.setMembershipProof(proofArray.buffer); | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |           } | 
					
						
							| 
									
										
										
										
											2020-12-07 13:40:11 -06:00
										 |  |  |         } catch (err) { | 
					
						
							|  |  |  |           window.log.error('Failed to fetch membership proof', err); | 
					
						
							|  |  |  |         } finally { | 
					
						
							|  |  |  |           isRequestingMembershipProof = false; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       requestGroupMembers: groupCall => { | 
					
						
							|  |  |  |         groupCall.setGroupMembers(this.getGroupCallMembers(conversationId)); | 
					
						
							|  |  |  |       }, | 
					
						
							|  |  |  |       onEnded: noop, | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     if (!outerGroupCall) { | 
					
						
							|  |  |  |       // This should be very rare, likely due to RingRTC not being able to get a lock
 | 
					
						
							|  |  |  |       //   or memory or something like that.
 | 
					
						
							|  |  |  |       throw new Error('Failed to get a group call instance; cannot start call'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     outerGroupCall.connect(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.syncGroupCallToRedux(conversationId, outerGroupCall); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return outerGroupCall; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public joinGroupCall( | 
					
						
							|  |  |  |     conversationId: string, | 
					
						
							|  |  |  |     hasLocalAudio: boolean, | 
					
						
							|  |  |  |     hasLocalVideo: boolean | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     const conversation = window.ConversationController.get( | 
					
						
							|  |  |  |       conversationId | 
					
						
							|  |  |  |     )?.format(); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       window.log.error('Missing conversation; not joining group call'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       !conversation.groupId || | 
					
						
							|  |  |  |       !conversation.publicParams || | 
					
						
							|  |  |  |       !conversation.secretParams | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'Conversation is missing required parameters. Cannot join group call' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const groupCall = this.connectGroupCall(conversationId, { | 
					
						
							|  |  |  |       groupId: conversation.groupId, | 
					
						
							|  |  |  |       publicParams: conversation.publicParams, | 
					
						
							|  |  |  |       secretParams: conversation.secretParams, | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     groupCall.setOutgoingAudioMuted(!hasLocalAudio); | 
					
						
							|  |  |  |     groupCall.setOutgoingVideoMuted(!hasLocalVideo); | 
					
						
							|  |  |  |     this.videoCapturer.enableCaptureAndSend(groupCall); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     groupCall.join(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |   private getCallIdForConversation(conversationId: string): undefined | CallId { | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     return this.getDirectCall(conversationId)?.callId; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-01 19:52:01 -06:00
										 |  |  |   public setGroupCallVideoRequest( | 
					
						
							|  |  |  |     conversationId: string, | 
					
						
							|  |  |  |     resolutions: Array<VideoRequest> | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     this.getGroupCall(conversationId)?.requestVideo(resolutions); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-09 17:21:34 -06:00
										 |  |  |   public groupMembersChanged(conversationId: string): void { | 
					
						
							|  |  |  |     // This will be called for any conversation change, so it's likely that there won't
 | 
					
						
							|  |  |  |     //   be a group call available; that's fine.
 | 
					
						
							|  |  |  |     const groupCall = this.getGroupCall(conversationId); | 
					
						
							|  |  |  |     if (!groupCall) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     groupCall.setGroupMembers(this.getGroupCallMembers(conversationId)); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   // See the comment in types/Calling.ts to explain why we have to do this conversion.
 | 
					
						
							|  |  |  |   private convertRingRtcConnectionState( | 
					
						
							|  |  |  |     connectionState: ConnectionState | 
					
						
							|  |  |  |   ): GroupCallConnectionState { | 
					
						
							|  |  |  |     switch (connectionState) { | 
					
						
							|  |  |  |       case ConnectionState.NotConnected: | 
					
						
							|  |  |  |         return GroupCallConnectionState.NotConnected; | 
					
						
							|  |  |  |       case ConnectionState.Connecting: | 
					
						
							|  |  |  |         return GroupCallConnectionState.Connecting; | 
					
						
							|  |  |  |       case ConnectionState.Connected: | 
					
						
							|  |  |  |         return GroupCallConnectionState.Connected; | 
					
						
							|  |  |  |       case ConnectionState.Reconnecting: | 
					
						
							|  |  |  |         return GroupCallConnectionState.Reconnecting; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         throw missingCaseError(connectionState); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   // See the comment in types/Calling.ts to explain why we have to do this conversion.
 | 
					
						
							|  |  |  |   private convertRingRtcJoinState(joinState: JoinState): GroupCallJoinState { | 
					
						
							|  |  |  |     switch (joinState) { | 
					
						
							|  |  |  |       case JoinState.NotJoined: | 
					
						
							|  |  |  |         return GroupCallJoinState.NotJoined; | 
					
						
							|  |  |  |       case JoinState.Joining: | 
					
						
							|  |  |  |         return GroupCallJoinState.Joining; | 
					
						
							|  |  |  |       case JoinState.Joined: | 
					
						
							|  |  |  |         return GroupCallJoinState.Joined; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         throw missingCaseError(joinState); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  |   public formatGroupCallPeekInfoForRedux( | 
					
						
							| 
									
										
										
										
											2020-12-02 12:14:03 -06:00
										 |  |  |     peekInfo: PeekInfo | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  |   ): GroupCallPeekInfoType { | 
					
						
							|  |  |  |     return { | 
					
						
							| 
									
										
										
										
											2020-12-02 12:14:03 -06:00
										 |  |  |       uuids: peekInfo.joinedMembers.map(uuidBuffer => { | 
					
						
							|  |  |  |         let uuid = arrayBufferToUuid(uuidBuffer); | 
					
						
							|  |  |  |         if (!uuid) { | 
					
						
							|  |  |  |           window.log.error( | 
					
						
							|  |  |  |             'Calling.formatGroupCallPeekInfoForRedux: could not convert peek UUID ArrayBuffer to string; using fallback UUID' | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           uuid = '00000000-0000-0000-0000-000000000000'; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |         return uuid; | 
					
						
							|  |  |  |       }), | 
					
						
							|  |  |  |       creatorUuid: peekInfo.creator && arrayBufferToUuid(peekInfo.creator), | 
					
						
							| 
									
										
										
										
											2020-11-20 11:19:28 -06:00
										 |  |  |       eraId: peekInfo.eraId, | 
					
						
							|  |  |  |       maxDevices: peekInfo.maxDevices ?? Infinity, | 
					
						
							|  |  |  |       deviceCount: peekInfo.deviceCount, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   private formatGroupCallForRedux(groupCall: GroupCall) { | 
					
						
							|  |  |  |     const localDeviceState = groupCall.getLocalDeviceState(); | 
					
						
							| 
									
										
										
										
											2020-12-02 12:14:03 -06:00
										 |  |  |     const peekInfo = groupCall.getPeekInfo(); | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // RingRTC doesn't ensure that the demux ID is unique. This can happen if someone
 | 
					
						
							|  |  |  |     //   leaves the call and quickly rejoins; RingRTC will tell us that there are two
 | 
					
						
							| 
									
										
										
										
											2020-12-02 12:14:03 -06:00
										 |  |  |     //   participants with the same demux ID in the call. This should be rare.
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     const remoteDeviceStates = uniqBy( | 
					
						
							|  |  |  |       groupCall.getRemoteDeviceStates() || [], | 
					
						
							|  |  |  |       remoteDeviceState => remoteDeviceState.demuxId | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // It should be impossible to be disconnected and Joining or Joined. Just in case, we
 | 
					
						
							|  |  |  |     //   try to handle that case.
 | 
					
						
							|  |  |  |     const joinState: GroupCallJoinState = | 
					
						
							|  |  |  |       localDeviceState.connectionState === ConnectionState.NotConnected | 
					
						
							|  |  |  |         ? GroupCallJoinState.NotJoined | 
					
						
							|  |  |  |         : this.convertRingRtcJoinState(localDeviceState.joinState); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       connectionState: this.convertRingRtcConnectionState( | 
					
						
							|  |  |  |         localDeviceState.connectionState | 
					
						
							|  |  |  |       ), | 
					
						
							|  |  |  |       joinState, | 
					
						
							|  |  |  |       hasLocalAudio: !localDeviceState.audioMuted, | 
					
						
							|  |  |  |       hasLocalVideo: !localDeviceState.videoMuted, | 
					
						
							| 
									
										
										
										
											2020-12-02 12:14:03 -06:00
										 |  |  |       peekInfo: peekInfo | 
					
						
							|  |  |  |         ? this.formatGroupCallPeekInfoForRedux(peekInfo) | 
					
						
							|  |  |  |         : undefined, | 
					
						
							| 
									
										
										
										
											2020-11-17 10:07:53 -05:00
										 |  |  |       remoteParticipants: remoteDeviceStates.map(remoteDeviceState => { | 
					
						
							| 
									
										
										
										
											2020-12-02 12:14:03 -06:00
										 |  |  |         let uuid = arrayBufferToUuid(remoteDeviceState.userId); | 
					
						
							|  |  |  |         if (!uuid) { | 
					
						
							|  |  |  |           window.log.error( | 
					
						
							|  |  |  |             'Calling.formatGroupCallForRedux: could not convert remote participant UUID ArrayBuffer to string; using fallback UUID' | 
					
						
							|  |  |  |           ); | 
					
						
							|  |  |  |           uuid = '00000000-0000-0000-0000-000000000000'; | 
					
						
							|  |  |  |         } | 
					
						
							| 
									
										
										
										
											2020-11-17 10:07:53 -05:00
										 |  |  |         return { | 
					
						
							| 
									
										
										
										
											2020-12-02 12:14:03 -06:00
										 |  |  |           uuid, | 
					
						
							| 
									
										
										
										
											2020-11-17 10:07:53 -05:00
										 |  |  |           demuxId: remoteDeviceState.demuxId, | 
					
						
							|  |  |  |           hasRemoteAudio: !remoteDeviceState.audioMuted, | 
					
						
							|  |  |  |           hasRemoteVideo: !remoteDeviceState.videoMuted, | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |           presenting: Boolean(remoteDeviceState.presenting), | 
					
						
							|  |  |  |           sharingScreen: Boolean(remoteDeviceState.sharingScreen), | 
					
						
							| 
									
										
										
										
											2020-12-01 17:52:09 -06:00
										 |  |  |           speakerTime: normalizeGroupCallTimestamp( | 
					
						
							|  |  |  |             remoteDeviceState.speakerTime | 
					
						
							|  |  |  |           ), | 
					
						
							| 
									
										
										
										
											2020-11-17 10:07:53 -05:00
										 |  |  |           // If RingRTC doesn't send us an aspect ratio, we make a guess.
 | 
					
						
							|  |  |  |           videoAspectRatio: | 
					
						
							|  |  |  |             remoteDeviceState.videoAspectRatio || | 
					
						
							|  |  |  |             (remoteDeviceState.videoMuted ? 1 : 4 / 3), | 
					
						
							|  |  |  |         }; | 
					
						
							|  |  |  |       }), | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   public getGroupCallVideoFrameSource( | 
					
						
							|  |  |  |     conversationId: string, | 
					
						
							|  |  |  |     demuxId: number | 
					
						
							|  |  |  |   ): VideoFrameSource { | 
					
						
							|  |  |  |     const groupCall = this.getGroupCall(conversationId); | 
					
						
							|  |  |  |     if (!groupCall) { | 
					
						
							|  |  |  |       throw new Error('Could not find matching call'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return groupCall.getVideoSource(demuxId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-08 14:37:04 -05:00
										 |  |  |   public resendGroupCallMediaKeys(conversationId: string): void { | 
					
						
							|  |  |  |     const groupCall = this.getGroupCall(conversationId); | 
					
						
							|  |  |  |     if (!groupCall) { | 
					
						
							|  |  |  |       throw new Error('Could not find matching call'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     groupCall.resendMediaKeys(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   private syncGroupCallToRedux( | 
					
						
							|  |  |  |     conversationId: string, | 
					
						
							|  |  |  |     groupCall: GroupCall | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     this.uxActions?.groupCallStateChange({ | 
					
						
							|  |  |  |       conversationId, | 
					
						
							|  |  |  |       ...this.formatGroupCallForRedux(groupCall), | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-04-08 11:24:21 -05:00
										 |  |  |   private async sendGroupCallUpdateMessage( | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  |     conversationId: string, | 
					
						
							|  |  |  |     eraId: string | 
					
						
							| 
									
										
										
										
											2021-04-08 11:24:21 -05:00
										 |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  |     const conversation = window.ConversationController.get(conversationId); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'Unable to send group call update message for non-existent conversation' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const groupV2 = conversation.getGroupV2Info(); | 
					
						
							| 
									
										
										
										
											2021-06-07 12:39:13 -04:00
										 |  |  |     const sendOptions = await getSendOptions(conversation.attributes); | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  |     if (!groupV2) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'Unable to send group call update message for conversation that lacks groupV2 info' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-09 16:02:50 -06:00
										 |  |  |     const timestamp = Date.now(); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  |     // We "fire and forget" because sending this message is non-essential.
 | 
					
						
							| 
									
										
										
										
											2021-05-28 12:11:19 -07:00
										 |  |  |     const { | 
					
						
							|  |  |  |       ContentHint, | 
					
						
							|  |  |  |     } = window.textsecure.protobuf.UnidentifiedSenderMessage.Message; | 
					
						
							| 
									
										
										
										
											2020-12-09 16:02:50 -06:00
										 |  |  |     wrapWithSyncMessageSend({ | 
					
						
							|  |  |  |       conversation, | 
					
						
							| 
									
										
										
										
											2021-05-25 15:40:04 -07:00
										 |  |  |       logId: `sendToGroup/groupCallUpdate/${conversationId}-${eraId}`, | 
					
						
							|  |  |  |       send: () => | 
					
						
							|  |  |  |         window.Signal.Util.sendToGroup( | 
					
						
							|  |  |  |           { groupCallUpdate: { eraId }, groupV2, timestamp }, | 
					
						
							|  |  |  |           conversation, | 
					
						
							| 
									
										
										
										
											2021-05-28 12:11:19 -07:00
										 |  |  |           ContentHint.SUPPLEMENTARY, | 
					
						
							| 
									
										
										
										
											2021-05-25 15:40:04 -07:00
										 |  |  |           sendOptions | 
					
						
							|  |  |  |         ), | 
					
						
							| 
									
										
										
										
											2020-12-09 16:02:50 -06:00
										 |  |  |       timestamp, | 
					
						
							|  |  |  |     }).catch(err => { | 
					
						
							| 
									
										
										
										
											2021-05-25 15:40:04 -07:00
										 |  |  |       window.log.error( | 
					
						
							|  |  |  |         'Failed to send group call update:', | 
					
						
							|  |  |  |         err && err.stack ? err.stack : err | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2020-12-09 16:02:50 -06:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-12-01 19:49:08 -06:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |   async accept(conversationId: string, asVideoCall: boolean): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  |     window.log.info('CallingClass.accept()'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |     const callId = this.getCallIdForConversation(conversationId); | 
					
						
							|  |  |  |     if (!callId) { | 
					
						
							|  |  |  |       window.log.warn('Trying to accept a non-existent call'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const haveMediaPermissions = await this.requestPermissions(asVideoCall); | 
					
						
							|  |  |  |     if (haveMediaPermissions) { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       await this.startDeviceReselectionTimer(); | 
					
						
							|  |  |  |       RingRTC.setVideoCapturer(callId, this.videoCapturer); | 
					
						
							|  |  |  |       RingRTC.setVideoRenderer(callId, this.videoRenderer); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       RingRTC.accept(callId, asVideoCall); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       window.log.info('Permissions were denied, call not allowed, hanging up.'); | 
					
						
							|  |  |  |       RingRTC.hangup(callId); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |   decline(conversationId: string): void { | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  |     window.log.info('CallingClass.decline()'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |     const callId = this.getCallIdForConversation(conversationId); | 
					
						
							|  |  |  |     if (!callId) { | 
					
						
							|  |  |  |       window.log.warn('Trying to decline a non-existent call'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     RingRTC.decline(callId); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |   hangup(conversationId: string): void { | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  |     window.log.info('CallingClass.hangup()'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     const call = getOwn(this.callsByConversation, conversationId); | 
					
						
							|  |  |  |     if (!call) { | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |       window.log.warn('Trying to hang up a non-existent call'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |     ipcRenderer.send('close-screen-share-controller'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     if (call instanceof Call) { | 
					
						
							|  |  |  |       RingRTC.hangup(call.callId); | 
					
						
							|  |  |  |     } else if (call instanceof GroupCall) { | 
					
						
							|  |  |  |       // This ensures that we turn off our devices.
 | 
					
						
							|  |  |  |       call.setOutgoingAudioMuted(true); | 
					
						
							|  |  |  |       call.setOutgoingVideoMuted(true); | 
					
						
							|  |  |  |       call.disconnect(); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       throw missingCaseError(call); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |   setOutgoingAudio(conversationId: string, enabled: boolean): void { | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     const call = getOwn(this.callsByConversation, conversationId); | 
					
						
							|  |  |  |     if (!call) { | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |       window.log.warn('Trying to set outgoing audio for a non-existent call'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     if (call instanceof Call) { | 
					
						
							|  |  |  |       RingRTC.setOutgoingAudio(call.callId, enabled); | 
					
						
							|  |  |  |     } else if (call instanceof GroupCall) { | 
					
						
							|  |  |  |       call.setOutgoingAudioMuted(!enabled); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       throw missingCaseError(call); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |   setOutgoingVideo(conversationId: string, enabled: boolean): void { | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     const call = getOwn(this.callsByConversation, conversationId); | 
					
						
							|  |  |  |     if (!call) { | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |       window.log.warn('Trying to set outgoing video for a non-existent call'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     if (call instanceof Call) { | 
					
						
							|  |  |  |       RingRTC.setOutgoingVideo(call.callId, enabled); | 
					
						
							|  |  |  |     } else if (call instanceof GroupCall) { | 
					
						
							|  |  |  |       call.setOutgoingVideoMuted(!enabled); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       throw missingCaseError(call); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |   private setOutgoingVideoIsScreenShare( | 
					
						
							|  |  |  |     call: Call | GroupCall, | 
					
						
							|  |  |  |     enabled: boolean | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     if (call instanceof Call) { | 
					
						
							|  |  |  |       RingRTC.setOutgoingVideoIsScreenShare(call.callId, enabled); | 
					
						
							|  |  |  |       // Note: there is no "presenting" API for direct calls.
 | 
					
						
							|  |  |  |     } else if (call instanceof GroupCall) { | 
					
						
							|  |  |  |       call.setOutgoingVideoIsScreenShare(enabled); | 
					
						
							|  |  |  |       call.setPresenting(enabled); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       throw missingCaseError(call); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getPresentingSources(): Promise<Array<PresentableSource>> { | 
					
						
							|  |  |  |     const sources = await desktopCapturer.getSources({ | 
					
						
							|  |  |  |       fetchWindowIcons: true, | 
					
						
							|  |  |  |       thumbnailSize: { height: 102, width: 184 }, | 
					
						
							|  |  |  |       types: ['window', 'screen'], | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const presentableSources: Array<PresentableSource> = []; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sources.forEach(source => { | 
					
						
							|  |  |  |       // If electron can't retrieve a thumbnail then it won't be able to
 | 
					
						
							|  |  |  |       // present this source so we filter these out.
 | 
					
						
							|  |  |  |       if (source.thumbnail.isEmpty()) { | 
					
						
							|  |  |  |         return; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       presentableSources.push({ | 
					
						
							|  |  |  |         appIcon: | 
					
						
							|  |  |  |           source.appIcon && !source.appIcon.isEmpty() | 
					
						
							|  |  |  |             ? source.appIcon.toDataURL() | 
					
						
							|  |  |  |             : undefined, | 
					
						
							|  |  |  |         id: source.id, | 
					
						
							| 
									
										
										
										
											2021-06-03 12:52:11 -07:00
										 |  |  |         name: translateSourceName(window.i18n, source), | 
					
						
							|  |  |  |         isScreen: isScreenSource(source), | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |         thumbnail: source.thumbnail.toDataURL(), | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return presentableSources; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   setPresenting( | 
					
						
							|  |  |  |     conversationId: string, | 
					
						
							|  |  |  |     hasLocalVideo: boolean, | 
					
						
							|  |  |  |     source?: PresentedSource | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     const call = getOwn(this.callsByConversation, conversationId); | 
					
						
							|  |  |  |     if (!call) { | 
					
						
							|  |  |  |       window.log.warn('Trying to set presenting for a non-existent call'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.videoCapturer.disable(); | 
					
						
							|  |  |  |     if (source) { | 
					
						
							|  |  |  |       this.hadLocalVideoBeforePresenting = hasLocalVideo; | 
					
						
							|  |  |  |       this.videoCapturer.enableCaptureAndSend(call, { | 
					
						
							|  |  |  |         // 15fps is much nicer but takes up a lot more CPU.
 | 
					
						
							|  |  |  |         maxFramerate: 5, | 
					
						
							|  |  |  |         maxHeight: 1080, | 
					
						
							|  |  |  |         maxWidth: 1920, | 
					
						
							|  |  |  |         screenShareSourceId: source.id, | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       this.setOutgoingVideo(conversationId, true); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       this.setOutgoingVideo( | 
					
						
							|  |  |  |         conversationId, | 
					
						
							| 
									
										
										
										
											2021-06-02 14:41:28 -04:00
										 |  |  |         this.hadLocalVideoBeforePresenting ?? hasLocalVideo | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |       ); | 
					
						
							|  |  |  |       this.hadLocalVideoBeforePresenting = undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const isPresenting = Boolean(source); | 
					
						
							|  |  |  |     this.setOutgoingVideoIsScreenShare(call, isPresenting); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (source) { | 
					
						
							| 
									
										
										
										
											2021-06-03 12:52:11 -07:00
										 |  |  |       ipcRenderer.send('show-screen-share', source.name); | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |       notify({ | 
					
						
							|  |  |  |         icon: 'images/icons/v2/video-solid-24.svg', | 
					
						
							|  |  |  |         message: window.i18n('calling__presenting--notification-body'), | 
					
						
							|  |  |  |         onNotificationClick: () => { | 
					
						
							|  |  |  |           if (this.uxActions) { | 
					
						
							|  |  |  |             this.uxActions.setPresenting(); | 
					
						
							|  |  |  |           } | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |         silent: true, | 
					
						
							|  |  |  |         title: window.i18n('calling__presenting--notification-title'), | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       ipcRenderer.send('close-screen-share-controller'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   private async startDeviceReselectionTimer(): Promise<void> { | 
					
						
							|  |  |  |     // Poll once
 | 
					
						
							|  |  |  |     await this.pollForMediaDevices(); | 
					
						
							|  |  |  |     // Start the timer
 | 
					
						
							|  |  |  |     if (!this.deviceReselectionTimer) { | 
					
						
							|  |  |  |       this.deviceReselectionTimer = setInterval(async () => { | 
					
						
							|  |  |  |         await this.pollForMediaDevices(); | 
					
						
							|  |  |  |       }, 3000); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   private stopDeviceReselectionTimer() { | 
					
						
							|  |  |  |     if (this.deviceReselectionTimer) { | 
					
						
							|  |  |  |       clearInterval(this.deviceReselectionTimer); | 
					
						
							|  |  |  |       this.deviceReselectionTimer = undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private mediaDeviceSettingsEqual( | 
					
						
							|  |  |  |     a?: MediaDeviceSettings, | 
					
						
							|  |  |  |     b?: MediaDeviceSettings | 
					
						
							|  |  |  |   ): boolean { | 
					
						
							|  |  |  |     if (!a && !b) { | 
					
						
							|  |  |  |       return true; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (!a || !b) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       a.availableCameras.length !== b.availableCameras.length || | 
					
						
							|  |  |  |       a.availableMicrophones.length !== b.availableMicrophones.length || | 
					
						
							|  |  |  |       a.availableSpeakers.length !== b.availableSpeakers.length | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     for (let i = 0; i < a.availableCameras.length; i += 1) { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       if ( | 
					
						
							|  |  |  |         a.availableCameras[i].deviceId !== b.availableCameras[i].deviceId || | 
					
						
							|  |  |  |         a.availableCameras[i].groupId !== b.availableCameras[i].groupId || | 
					
						
							|  |  |  |         a.availableCameras[i].label !== b.availableCameras[i].label | 
					
						
							|  |  |  |       ) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     for (let i = 0; i < a.availableMicrophones.length; i += 1) { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       if ( | 
					
						
							|  |  |  |         a.availableMicrophones[i].name !== b.availableMicrophones[i].name || | 
					
						
							| 
									
										
										
										
											2020-08-27 12:21:10 -07:00
										 |  |  |         a.availableMicrophones[i].uniqueId !== | 
					
						
							|  |  |  |           b.availableMicrophones[i].uniqueId | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       ) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     for (let i = 0; i < a.availableSpeakers.length; i += 1) { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       if ( | 
					
						
							|  |  |  |         a.availableSpeakers[i].name !== b.availableSpeakers[i].name || | 
					
						
							| 
									
										
										
										
											2020-08-27 12:21:10 -07:00
										 |  |  |         a.availableSpeakers[i].uniqueId !== b.availableSpeakers[i].uniqueId | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       ) { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       (a.selectedCamera && !b.selectedCamera) || | 
					
						
							|  |  |  |       (!a.selectedCamera && b.selectedCamera) || | 
					
						
							|  |  |  |       (a.selectedMicrophone && !b.selectedMicrophone) || | 
					
						
							|  |  |  |       (!a.selectedMicrophone && b.selectedMicrophone) || | 
					
						
							|  |  |  |       (a.selectedSpeaker && !b.selectedSpeaker) || | 
					
						
							|  |  |  |       (!a.selectedSpeaker && b.selectedSpeaker) | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       a.selectedCamera && | 
					
						
							|  |  |  |       b.selectedCamera && | 
					
						
							|  |  |  |       a.selectedCamera !== b.selectedCamera | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       a.selectedMicrophone && | 
					
						
							|  |  |  |       b.selectedMicrophone && | 
					
						
							|  |  |  |       a.selectedMicrophone.index !== b.selectedMicrophone.index | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       a.selectedSpeaker && | 
					
						
							|  |  |  |       b.selectedSpeaker && | 
					
						
							|  |  |  |       a.selectedSpeaker.index !== b.selectedSpeaker.index | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async pollForMediaDevices(): Promise<void> { | 
					
						
							|  |  |  |     const newSettings = await this.getMediaDeviceSettings(); | 
					
						
							|  |  |  |     if ( | 
					
						
							|  |  |  |       !this.mediaDeviceSettingsEqual(this.lastMediaDeviceSettings, newSettings) | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       window.log.info( | 
					
						
							|  |  |  |         'MediaDevice: available devices changed (from->to)', | 
					
						
							|  |  |  |         this.lastMediaDeviceSettings, | 
					
						
							|  |  |  |         newSettings | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2020-08-31 19:24:47 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |       await this.selectPreferredDevices(newSettings); | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       this.lastMediaDeviceSettings = newSettings; | 
					
						
							|  |  |  |       this.uxActions?.refreshIODevices(newSettings); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getMediaDeviceSettings(): Promise<MediaDeviceSettings> { | 
					
						
							|  |  |  |     const availableMicrophones = RingRTC.getAudioInputs(); | 
					
						
							|  |  |  |     const preferredMicrophone = window.storage.get( | 
					
						
							|  |  |  |       'preferred-audio-input-device' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const selectedMicIndex = this.findBestMatchingDeviceIndex( | 
					
						
							|  |  |  |       availableMicrophones, | 
					
						
							|  |  |  |       preferredMicrophone | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const selectedMicrophone = | 
					
						
							|  |  |  |       selectedMicIndex !== undefined | 
					
						
							|  |  |  |         ? availableMicrophones[selectedMicIndex] | 
					
						
							|  |  |  |         : undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const availableSpeakers = RingRTC.getAudioOutputs(); | 
					
						
							|  |  |  |     const preferredSpeaker = window.storage.get( | 
					
						
							|  |  |  |       'preferred-audio-output-device' | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const selectedSpeakerIndex = this.findBestMatchingDeviceIndex( | 
					
						
							|  |  |  |       availableSpeakers, | 
					
						
							|  |  |  |       preferredSpeaker | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     const selectedSpeaker = | 
					
						
							|  |  |  |       selectedSpeakerIndex !== undefined | 
					
						
							|  |  |  |         ? availableSpeakers[selectedSpeakerIndex] | 
					
						
							|  |  |  |         : undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-04 14:27:12 -04:00
										 |  |  |     const availableCameras = await this.videoCapturer.enumerateDevices(); | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     const preferredCamera = window.storage.get('preferred-video-input-device'); | 
					
						
							|  |  |  |     const selectedCamera = this.findBestMatchingCamera( | 
					
						
							|  |  |  |       availableCameras, | 
					
						
							|  |  |  |       preferredCamera | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       availableMicrophones, | 
					
						
							|  |  |  |       availableSpeakers, | 
					
						
							|  |  |  |       selectedMicrophone, | 
					
						
							|  |  |  |       selectedSpeaker, | 
					
						
							|  |  |  |       availableCameras, | 
					
						
							|  |  |  |       selectedCamera, | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   findBestMatchingDeviceIndex( | 
					
						
							|  |  |  |     available: Array<AudioDevice>, | 
					
						
							|  |  |  |     preferred: AudioDevice | undefined | 
					
						
							|  |  |  |   ): number | undefined { | 
					
						
							| 
									
										
										
										
											2020-10-22 14:40:31 -07:00
										 |  |  |     if (preferred) { | 
					
						
							|  |  |  |       // Match by uniqueId first, if available
 | 
					
						
							|  |  |  |       if (preferred.uniqueId) { | 
					
						
							|  |  |  |         const matchIndex = available.findIndex( | 
					
						
							|  |  |  |           d => d.uniqueId === preferred.uniqueId | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |         if (matchIndex !== -1) { | 
					
						
							|  |  |  |           return matchIndex; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       // Match by name second
 | 
					
						
							|  |  |  |       const matchingNames = available.filter(d => d.name === preferred.name); | 
					
						
							|  |  |  |       if (matchingNames.length > 0) { | 
					
						
							|  |  |  |         return matchingNames[0].index; | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-10-22 14:40:31 -07:00
										 |  |  |     // Nothing matches or no preference; take the first device if there are any
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     return available.length > 0 ? 0 : undefined; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   findBestMatchingCamera( | 
					
						
							|  |  |  |     available: Array<MediaDeviceInfo>, | 
					
						
							|  |  |  |     preferred?: string | 
					
						
							|  |  |  |   ): string | undefined { | 
					
						
							|  |  |  |     const matchingId = available.filter(d => d.deviceId === preferred); | 
					
						
							|  |  |  |     const nonInfrared = available.filter(d => !d.label.includes('IR Camera')); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     // By default, pick the first non-IR camera (but allow the user to pick the
 | 
					
						
							|  |  |  |     // infrared if they so desire)
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     if (matchingId.length > 0) { | 
					
						
							|  |  |  |       return matchingId[0].deviceId; | 
					
						
							| 
									
										
										
										
											2020-09-08 17:46:29 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |     if (nonInfrared.length > 0) { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       return nonInfrared[0].deviceId; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-08 17:46:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return undefined; | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |   setPreferredMicrophone(device: AudioDevice): void { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     window.log.info('MediaDevice: setPreferredMicrophone', device); | 
					
						
							|  |  |  |     window.storage.put('preferred-audio-input-device', device); | 
					
						
							|  |  |  |     RingRTC.setAudioInput(device.index); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |   setPreferredSpeaker(device: AudioDevice): void { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     window.log.info('MediaDevice: setPreferredSpeaker', device); | 
					
						
							|  |  |  |     window.storage.put('preferred-audio-output-device', device); | 
					
						
							|  |  |  |     RingRTC.setAudioOutput(device.index); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |   enableLocalCamera(): void { | 
					
						
							|  |  |  |     this.videoCapturer.enableCapture(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  |   disableLocalVideo(): void { | 
					
						
							| 
									
										
										
										
											2020-10-07 21:25:33 -04:00
										 |  |  |     this.videoCapturer.disable(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |   async setPreferredCamera(device: string): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     window.log.info('MediaDevice: setPreferredCamera', device); | 
					
						
							|  |  |  |     window.storage.put('preferred-video-input-device', device); | 
					
						
							|  |  |  |     await this.videoCapturer.setPreferredDevice(device); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async handleCallingMessage( | 
					
						
							|  |  |  |     envelope: EnvelopeClass, | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |     callingMessage: CallingMessage | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  |     window.log.info('CallingClass.handleCallingMessage()'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const enableIncomingCalls = await window.getIncomingCallNotification(); | 
					
						
							|  |  |  |     if (callingMessage.offer && !enableIncomingCalls) { | 
					
						
							|  |  |  |       // Drop offers silently if incoming call notifications are disabled.
 | 
					
						
							|  |  |  |       window.log.info('Incoming calls are disabled, ignoring call offer.'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-11 17:53:19 -07:00
										 |  |  |     const remoteUserId = envelope.sourceUuid || envelope.source; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const remoteDeviceId = this.parseDeviceId(envelope.sourceDevice); | 
					
						
							|  |  |  |     if (!remoteUserId || !remoteDeviceId || !this.localDeviceId) { | 
					
						
							|  |  |  |       window.log.error('Missing identifier, ignoring call message.'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-11 17:53:19 -07:00
										 |  |  |     const senderIdentityRecord = window.textsecure.storage.protocol.getIdentityRecord( | 
					
						
							|  |  |  |       remoteUserId | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (!senderIdentityRecord) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'Missing sender identity record; ignoring call message.' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const senderIdentityKey = senderIdentityRecord.publicKey.slice(1); // Ignore the type header, it is not used.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const receiverIdentityRecord = window.textsecure.storage.protocol.getIdentityRecord( | 
					
						
							|  |  |  |       window.textsecure.storage.user.getUuid() || | 
					
						
							|  |  |  |         window.textsecure.storage.user.getNumber() | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |     if (!receiverIdentityRecord) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'Missing receiver identity record; ignoring call message.' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const receiverIdentityKey = receiverIdentityRecord.publicKey.slice(1); // Ignore the type header, it is not used.
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |     const conversation = window.ConversationController.get(remoteUserId); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       window.log.error('Missing conversation; ignoring call message.'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (callingMessage.offer && !conversation.getAccepted()) { | 
					
						
							|  |  |  |       window.log.info( | 
					
						
							|  |  |  |         'Conversation was not approved by user; rejecting call message.' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const hangup = new HangupMessage(); | 
					
						
							|  |  |  |       hangup.callId = callingMessage.offer.callId; | 
					
						
							|  |  |  |       hangup.deviceId = remoteDeviceId; | 
					
						
							|  |  |  |       hangup.type = HangupType.NeedPermission; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       const message = new CallingMessage(); | 
					
						
							|  |  |  |       message.legacyHangup = hangup; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       await this.handleOutgoingSignaling(remoteUserId, message); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.addCallHistoryForFailedIncomingCall( | 
					
						
							|  |  |  |         conversation, | 
					
						
							| 
									
										
										
										
											2021-06-03 13:19:14 -07:00
										 |  |  |         callingMessage.offer.type === OfferType.VideoCall, | 
					
						
							|  |  |  |         envelope.timestamp | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |     const sourceUuid = envelope.sourceUuid | 
					
						
							|  |  |  |       ? uuidToArrayBuffer(envelope.sourceUuid) | 
					
						
							|  |  |  |       : null; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-25 14:42:32 -07:00
										 |  |  |     const messageAgeSec = envelope.messageAgeSec ? envelope.messageAgeSec : 0; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  |     window.log.info('CallingClass.handleCallingMessage(): Handling in RingRTC'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     RingRTC.handleCallingMessage( | 
					
						
							|  |  |  |       remoteUserId, | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |       sourceUuid, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       remoteDeviceId, | 
					
						
							|  |  |  |       this.localDeviceId, | 
					
						
							|  |  |  |       messageAgeSec, | 
					
						
							| 
									
										
										
										
											2020-09-11 17:53:19 -07:00
										 |  |  |       callingMessage, | 
					
						
							|  |  |  |       senderIdentityKey, | 
					
						
							|  |  |  |       receiverIdentityKey | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-31 19:24:47 -04:00
										 |  |  |   private async selectPreferredDevices( | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     settings: MediaDeviceSettings | 
					
						
							|  |  |  |   ): Promise<void> { | 
					
						
							| 
									
										
										
										
											2020-08-31 19:24:47 -04:00
										 |  |  |     if ( | 
					
						
							|  |  |  |       (!this.lastMediaDeviceSettings && settings.selectedCamera) || | 
					
						
							|  |  |  |       (this.lastMediaDeviceSettings && | 
					
						
							|  |  |  |         settings.selectedCamera && | 
					
						
							|  |  |  |         this.lastMediaDeviceSettings.selectedCamera !== settings.selectedCamera) | 
					
						
							|  |  |  |     ) { | 
					
						
							|  |  |  |       window.log.info('MediaDevice: selecting camera', settings.selectedCamera); | 
					
						
							|  |  |  |       await this.videoCapturer.setPreferredDevice(settings.selectedCamera); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     // Assume that the MediaDeviceSettings have been obtained very recently and
 | 
					
						
							|  |  |  |     // the index is still valid (no devices have been plugged in in between).
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     if (settings.selectedMicrophone) { | 
					
						
							|  |  |  |       window.log.info( | 
					
						
							|  |  |  |         'MediaDevice: selecting microphone', | 
					
						
							|  |  |  |         settings.selectedMicrophone | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       RingRTC.setAudioInput(settings.selectedMicrophone.index); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-08-31 19:24:47 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |     if (settings.selectedSpeaker) { | 
					
						
							|  |  |  |       window.log.info( | 
					
						
							|  |  |  |         'MediaDevice: selecting speaker', | 
					
						
							| 
									
										
										
										
											2020-12-04 13:10:33 -06:00
										 |  |  |         settings.selectedSpeaker | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |       ); | 
					
						
							|  |  |  |       RingRTC.setAudioOutput(settings.selectedSpeaker.index); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   private async requestCameraPermissions(): Promise<boolean> { | 
					
						
							|  |  |  |     const cameraPermission = await window.getMediaCameraPermissions(); | 
					
						
							|  |  |  |     if (!cameraPermission) { | 
					
						
							|  |  |  |       await window.showCallingPermissionsPopup(true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Check the setting again (from the source of truth).
 | 
					
						
							|  |  |  |       return window.getMediaCameraPermissions(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async requestMicrophonePermissions(): Promise<boolean> { | 
					
						
							|  |  |  |     const microphonePermission = await window.getMediaPermissions(); | 
					
						
							|  |  |  |     if (!microphonePermission) { | 
					
						
							|  |  |  |       await window.showCallingPermissionsPopup(false); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       // Check the setting again (from the source of truth).
 | 
					
						
							|  |  |  |       return window.getMediaPermissions(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return true; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async requestPermissions(isVideoCall: boolean): Promise<boolean> { | 
					
						
							|  |  |  |     const microphonePermission = await this.requestMicrophonePermissions(); | 
					
						
							|  |  |  |     if (microphonePermission) { | 
					
						
							|  |  |  |       if (isVideoCall) { | 
					
						
							|  |  |  |         return this.requestCameraPermissions(); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-09-08 17:46:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |       return true; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-09-08 17:46:29 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return false; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   private async handleSendCallMessage( | 
					
						
							|  |  |  |     recipient: ArrayBuffer, | 
					
						
							|  |  |  |     data: ArrayBuffer | 
					
						
							|  |  |  |   ): Promise<boolean> { | 
					
						
							|  |  |  |     const userId = arrayBufferToUuid(recipient); | 
					
						
							|  |  |  |     if (!userId) { | 
					
						
							|  |  |  |       window.log.error('handleSendCallMessage(): bad recipient UUID'); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const message = new CallingMessage(); | 
					
						
							|  |  |  |     message.opaque = new OpaqueMessage(); | 
					
						
							|  |  |  |     message.opaque.data = data; | 
					
						
							|  |  |  |     return this.handleOutgoingSignaling(userId, message); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   private async handleOutgoingSignaling( | 
					
						
							|  |  |  |     remoteUserId: UserId, | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |     message: CallingMessage | 
					
						
							| 
									
										
										
										
											2020-06-25 14:41:33 -07:00
										 |  |  |   ): Promise<boolean> { | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const conversation = window.ConversationController.get(remoteUserId); | 
					
						
							| 
									
										
										
										
											2020-07-10 11:28:49 -07:00
										 |  |  |     const sendOptions = conversation | 
					
						
							| 
									
										
										
										
											2021-06-07 12:39:13 -04:00
										 |  |  |       ? await getSendOptions(conversation.attributes) | 
					
						
							| 
									
										
										
										
											2020-07-10 11:28:49 -07:00
										 |  |  |       : undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     if (!window.textsecure.messaging) { | 
					
						
							|  |  |  |       window.log.warn('handleOutgoingSignaling() returning false; offline'); | 
					
						
							|  |  |  |       return false; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       await window.textsecure.messaging.sendCallingMessage( | 
					
						
							|  |  |  |         remoteUserId, | 
					
						
							|  |  |  |         message, | 
					
						
							|  |  |  |         sendOptions | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       window.log.info('handleOutgoingSignaling() completed successfully'); | 
					
						
							| 
									
										
										
										
											2020-06-25 14:41:33 -07:00
										 |  |  |       return true; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     } catch (err) { | 
					
						
							|  |  |  |       if (err && err.errors && err.errors.length > 0) { | 
					
						
							|  |  |  |         window.log.error( | 
					
						
							|  |  |  |           `handleOutgoingSignaling() failed: ${err.errors[0].reason}` | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         window.log.error('handleOutgoingSignaling() failed'); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-06-25 14:41:33 -07:00
										 |  |  |       return false; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-29 16:20:05 -07:00
										 |  |  |   // If we return null here, we hang up the call.
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   private async handleIncomingCall(call: Call): Promise<CallSettings | null> { | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  |     window.log.info('CallingClass.handleIncomingCall()'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     if (!this.uxActions || !this.localDeviceId) { | 
					
						
							|  |  |  |       window.log.error('Missing required objects, ignoring incoming call.'); | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const conversation = window.ConversationController.get(call.remoteUserId); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       window.log.error('Missing conversation, ignoring incoming call.'); | 
					
						
							|  |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       // The peer must be 'trusted' before accepting a call from them.
 | 
					
						
							|  |  |  |       // This is mostly the safety number check, unverified meaning that they were
 | 
					
						
							|  |  |  |       // verified before but now they are not.
 | 
					
						
							|  |  |  |       const verifiedEnum = await conversation.safeGetVerified(); | 
					
						
							|  |  |  |       if ( | 
					
						
							|  |  |  |         verifiedEnum === | 
					
						
							|  |  |  |         window.textsecure.storage.protocol.VerifiedStatus.UNVERIFIED | 
					
						
							|  |  |  |       ) { | 
					
						
							| 
									
										
										
										
											2020-07-23 11:59:44 -07:00
										 |  |  |         window.log.info( | 
					
						
							|  |  |  |           `Peer is not trusted, ignoring incoming call for conversation: ${conversation.idForLogging()}` | 
					
						
							|  |  |  |         ); | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |         this.addCallHistoryForFailedIncomingCall( | 
					
						
							|  |  |  |           conversation, | 
					
						
							| 
									
										
										
										
											2021-06-03 13:19:14 -07:00
										 |  |  |           call.isVideoCall, | 
					
						
							|  |  |  |           Date.now() | 
					
						
							| 
									
										
										
										
											2020-07-23 11:59:44 -07:00
										 |  |  |         ); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |         return null; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       this.attachToCall(conversation, call); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |       this.uxActions.receiveIncomingCall({ | 
					
						
							|  |  |  |         conversationId: conversation.id, | 
					
						
							|  |  |  |         isVideoCall: call.isVideoCall, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-25 14:05:25 -07:00
										 |  |  |       window.log.info('CallingClass.handleIncomingCall(): Proceeding'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       return await this.getCallSettings(conversation); | 
					
						
							|  |  |  |     } catch (err) { | 
					
						
							|  |  |  |       window.log.error(`Ignoring incoming call: ${err.stack}`); | 
					
						
							| 
									
										
										
										
											2021-06-03 13:19:14 -07:00
										 |  |  |       this.addCallHistoryForFailedIncomingCall( | 
					
						
							|  |  |  |         conversation, | 
					
						
							|  |  |  |         call.isVideoCall, | 
					
						
							|  |  |  |         Date.now() | 
					
						
							|  |  |  |       ); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       return null; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private handleAutoEndedIncomingCallRequest( | 
					
						
							|  |  |  |     remoteUserId: UserId, | 
					
						
							|  |  |  |     reason: CallEndedReason | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     const conversation = window.ConversationController.get(remoteUserId); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     this.addCallHistoryForAutoEndedIncomingCall(conversation, reason); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-24 13:57:54 -07:00
										 |  |  |   private attachToCall(conversation: ConversationModel, call: Call): void { | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |     this.callsByConversation[conversation.id] = call; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const { uxActions } = this; | 
					
						
							|  |  |  |     if (!uxActions) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let acceptedTime: number | undefined; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     // eslint-disable-next-line no-param-reassign
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     call.handleStateChanged = () => { | 
					
						
							|  |  |  |       if (call.state === CallState.Accepted) { | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |         acceptedTime = acceptedTime || Date.now(); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       } else if (call.state === CallState.Ended) { | 
					
						
							|  |  |  |         this.addCallHistoryForEndedCall(conversation, call, acceptedTime); | 
					
						
							| 
									
										
										
										
											2020-08-26 20:03:42 -04:00
										 |  |  |         this.stopDeviceReselectionTimer(); | 
					
						
							|  |  |  |         this.lastMediaDeviceSettings = undefined; | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |         delete this.callsByConversation[conversation.id]; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       } | 
					
						
							|  |  |  |       uxActions.callStateChange({ | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |         conversationId: conversation.id, | 
					
						
							|  |  |  |         acceptedTime, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |         callState: call.state, | 
					
						
							| 
									
										
										
										
											2020-10-01 14:09:15 -05:00
										 |  |  |         callEndedReason: call.endedReason, | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |         isIncoming: call.isIncoming, | 
					
						
							|  |  |  |         isVideoCall: call.isVideoCall, | 
					
						
							|  |  |  |         title: conversation.getTitle(), | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     // eslint-disable-next-line no-param-reassign
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     call.handleRemoteVideoEnabled = () => { | 
					
						
							|  |  |  |       uxActions.remoteVideoChange({ | 
					
						
							| 
									
										
										
										
											2020-11-06 11:36:37 -06:00
										 |  |  |         conversationId: conversation.id, | 
					
						
							|  |  |  |         hasVideo: call.remoteVideoEnabled, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       }); | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2021-05-20 17:54:03 -04:00
										 |  |  | 
 | 
					
						
							|  |  |  |     // eslint-disable-next-line no-param-reassign
 | 
					
						
							|  |  |  |     call.handleRemoteSharingScreen = () => { | 
					
						
							|  |  |  |       uxActions.remoteSharingScreenChange({ | 
					
						
							|  |  |  |         conversationId: conversation.id, | 
					
						
							|  |  |  |         isSharingScreen: Boolean(call.remoteSharingScreen), | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async handleLogMessage( | 
					
						
							|  |  |  |     level: CallLogLevel, | 
					
						
							|  |  |  |     fileName: string, | 
					
						
							|  |  |  |     line: number, | 
					
						
							|  |  |  |     message: string | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     switch (level) { | 
					
						
							|  |  |  |       case CallLogLevel.Info: | 
					
						
							|  |  |  |         window.log.info(`${fileName}:${line} ${message}`); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case CallLogLevel.Warn: | 
					
						
							|  |  |  |         window.log.warn(`${fileName}:${line} ${message}`); | 
					
						
							|  |  |  |         break; | 
					
						
							|  |  |  |       case CallLogLevel.Error: | 
					
						
							|  |  |  |         window.log.error(`${fileName}:${line} ${message}`); | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |         break; | 
					
						
							|  |  |  |       default: | 
					
						
							|  |  |  |         break; | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |   private async handleSendHttpRequest( | 
					
						
							|  |  |  |     requestId: number, | 
					
						
							|  |  |  |     url: string, | 
					
						
							|  |  |  |     method: HttpMethod, | 
					
						
							|  |  |  |     headers: { [name: string]: string }, | 
					
						
							|  |  |  |     body: ArrayBuffer | undefined | 
					
						
							|  |  |  |   ) { | 
					
						
							|  |  |  |     if (!window.textsecure.messaging) { | 
					
						
							|  |  |  |       RingRTC.httpRequestFailed(requestId, 'We are offline'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const httpMethod = RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD.get(method); | 
					
						
							|  |  |  |     if (httpMethod === undefined) { | 
					
						
							|  |  |  |       RingRTC.httpRequestFailed( | 
					
						
							|  |  |  |         requestId, | 
					
						
							|  |  |  |         `Unknown method: ${JSON.stringify(method)}` | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     let result; | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       result = await window.textsecure.messaging.server.makeSfuRequest( | 
					
						
							|  |  |  |         url, | 
					
						
							|  |  |  |         httpMethod, | 
					
						
							|  |  |  |         headers, | 
					
						
							|  |  |  |         body | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |     } catch (err) { | 
					
						
							| 
									
										
										
										
											2020-11-19 13:35:11 -08:00
										 |  |  |       if (err.code !== -1) { | 
					
						
							|  |  |  |         // WebAPI treats certain response codes as errors, but RingRTC still needs to
 | 
					
						
							|  |  |  |         // see them. It does not currently look at the response body, so we're giving
 | 
					
						
							|  |  |  |         // it an empty one.
 | 
					
						
							|  |  |  |         RingRTC.receivedHttpResponse(requestId, err.code, new ArrayBuffer(0)); | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         window.log.error('handleSendHttpRequest: fetch failed with error', err); | 
					
						
							|  |  |  |         RingRTC.httpRequestFailed(requestId, String(err)); | 
					
						
							|  |  |  |       } | 
					
						
							| 
									
										
										
										
											2020-11-13 13:57:55 -06:00
										 |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     RingRTC.receivedHttpResponse( | 
					
						
							|  |  |  |       requestId, | 
					
						
							|  |  |  |       result.response.status, | 
					
						
							|  |  |  |       result.data | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   private getRemoteUserIdFromConversation( | 
					
						
							| 
									
										
										
										
											2020-09-24 13:57:54 -07:00
										 |  |  |     conversation: ConversationModel | 
					
						
							|  |  |  |   ): UserId | undefined | null { | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const recipients = conversation.getRecipients(); | 
					
						
							|  |  |  |     if (recipients.length !== 1) { | 
					
						
							|  |  |  |       return undefined; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return recipients[0]; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private get localDeviceId(): DeviceId | null { | 
					
						
							|  |  |  |     return this.parseDeviceId(window.textsecure.storage.user.getDeviceId()); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private parseDeviceId( | 
					
						
							|  |  |  |     deviceId: number | string | undefined | 
					
						
							|  |  |  |   ): DeviceId | null { | 
					
						
							|  |  |  |     if (typeof deviceId === 'string') { | 
					
						
							|  |  |  |       return parseInt(deviceId, 10); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     if (typeof deviceId === 'number') { | 
					
						
							|  |  |  |       return deviceId; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     return null; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private async getCallSettings( | 
					
						
							| 
									
										
										
										
											2020-09-24 13:57:54 -07:00
										 |  |  |     conversation: ConversationModel | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   ): Promise<CallSettings> { | 
					
						
							| 
									
										
										
										
											2020-07-10 11:28:49 -07:00
										 |  |  |     if (!window.textsecure.messaging) { | 
					
						
							|  |  |  |       throw new Error('getCallSettings: offline!'); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const iceServerJson = await window.textsecure.messaging.server.getIceServers(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const shouldRelayCalls = Boolean(await window.getAlwaysRelayCalls()); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     // If the peer is 'unknown', i.e. not in the contact list, force IP hiding.
 | 
					
						
							| 
									
										
										
										
											2020-07-29 09:29:57 -07:00
										 |  |  |     const isContactUnknown = !conversation.isFromOrAddedByTrustedContact(); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | 
 | 
					
						
							|  |  |  |     return { | 
					
						
							|  |  |  |       iceServer: JSON.parse(iceServerJson), | 
					
						
							|  |  |  |       hideIp: shouldRelayCalls || isContactUnknown, | 
					
						
							| 
									
										
										
										
											2021-02-04 11:54:18 -08:00
										 |  |  |       bandwidthMode: BandwidthMode.Normal, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     }; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private addCallHistoryForEndedCall( | 
					
						
							| 
									
										
										
										
											2020-09-24 13:57:54 -07:00
										 |  |  |     conversation: ConversationModel, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     call: Call, | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     acceptedTimeParam: number | undefined | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   ) { | 
					
						
							| 
									
										
										
										
											2020-09-03 07:59:24 -07:00
										 |  |  |     let acceptedTime = acceptedTimeParam; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     const { endedReason, isIncoming } = call; | 
					
						
							|  |  |  |     const wasAccepted = Boolean(acceptedTime); | 
					
						
							|  |  |  |     const isOutgoing = !isIncoming; | 
					
						
							|  |  |  |     const wasDeclined = | 
					
						
							|  |  |  |       !wasAccepted && | 
					
						
							|  |  |  |       (endedReason === CallEndedReason.Declined || | 
					
						
							|  |  |  |         endedReason === CallEndedReason.DeclinedOnAnotherDevice || | 
					
						
							|  |  |  |         (isIncoming && endedReason === CallEndedReason.LocalHangup) || | 
					
						
							| 
									
										
										
										
											2020-07-06 17:37:43 -07:00
										 |  |  |         (isOutgoing && endedReason === CallEndedReason.RemoteHangup) || | 
					
						
							|  |  |  |         (isOutgoing && | 
					
						
							|  |  |  |           endedReason === CallEndedReason.RemoteHangupNeedPermission)); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     if (call.endedReason === CallEndedReason.AcceptedOnAnotherDevice) { | 
					
						
							|  |  |  |       acceptedTime = Date.now(); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-07 20:26:42 -05:00
										 |  |  |     conversation.addCallHistory({ | 
					
						
							| 
									
										
										
										
											2020-12-07 14:43:19 -06:00
										 |  |  |       callMode: CallMode.Direct, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       wasIncoming: call.isIncoming, | 
					
						
							|  |  |  |       wasVideoCall: call.isVideoCall, | 
					
						
							|  |  |  |       wasDeclined, | 
					
						
							|  |  |  |       acceptedTime, | 
					
						
							|  |  |  |       endedTime: Date.now(), | 
					
						
							| 
									
										
										
										
											2020-10-07 20:26:42 -05:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private addCallHistoryForFailedIncomingCall( | 
					
						
							| 
									
										
										
										
											2020-09-24 13:57:54 -07:00
										 |  |  |     conversation: ConversationModel, | 
					
						
							| 
									
										
										
										
											2021-06-03 13:19:14 -07:00
										 |  |  |     wasVideoCall: boolean, | 
					
						
							|  |  |  |     timestamp: number | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   ) { | 
					
						
							| 
									
										
										
										
											2020-10-07 20:26:42 -05:00
										 |  |  |     conversation.addCallHistory({ | 
					
						
							| 
									
										
										
										
											2020-12-07 14:43:19 -06:00
										 |  |  |       callMode: CallMode.Direct, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       wasIncoming: true, | 
					
						
							| 
									
										
										
										
											2020-09-28 14:02:35 -05:00
										 |  |  |       wasVideoCall, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       // Since the user didn't decline, make sure it shows up as a missed call instead
 | 
					
						
							|  |  |  |       wasDeclined: false, | 
					
						
							|  |  |  |       acceptedTime: undefined, | 
					
						
							| 
									
										
										
										
											2021-06-03 13:19:14 -07:00
										 |  |  |       endedTime: timestamp, | 
					
						
							| 
									
										
										
										
											2020-10-07 20:26:42 -05:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   private addCallHistoryForAutoEndedIncomingCall( | 
					
						
							| 
									
										
										
										
											2020-09-24 13:57:54 -07:00
										 |  |  |     conversation: ConversationModel, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |     _reason: CallEndedReason | 
					
						
							|  |  |  |   ) { | 
					
						
							| 
									
										
										
										
											2020-10-07 20:26:42 -05:00
										 |  |  |     conversation.addCallHistory({ | 
					
						
							| 
									
										
										
										
											2020-12-07 14:43:19 -06:00
										 |  |  |       callMode: CallMode.Direct, | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |       wasIncoming: true, | 
					
						
							|  |  |  |       // We don't actually know, but it doesn't seem that important in this case,
 | 
					
						
							|  |  |  |       // but we could maybe plumb this info through RingRTC
 | 
					
						
							|  |  |  |       wasVideoCall: false, | 
					
						
							|  |  |  |       // Since the user didn't decline, make sure it shows up as a missed call instead
 | 
					
						
							|  |  |  |       wasDeclined: false, | 
					
						
							|  |  |  |       acceptedTime: undefined, | 
					
						
							|  |  |  |       endedTime: Date.now(), | 
					
						
							| 
									
										
										
										
											2020-10-07 20:26:42 -05:00
										 |  |  |     }); | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-12-07 14:43:19 -06:00
										 |  |  | 
 | 
					
						
							|  |  |  |   public updateCallHistoryForGroupCall( | 
					
						
							|  |  |  |     conversationId: string, | 
					
						
							|  |  |  |     peekInfo: undefined | PeekInfo | 
					
						
							|  |  |  |   ): void { | 
					
						
							|  |  |  |     // If we don't have the necessary pieces to peek, bail. (It's okay if we don't.)
 | 
					
						
							|  |  |  |     if (!peekInfo || !peekInfo.eraId || !peekInfo.creator) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     const creatorUuid = arrayBufferToUuid(peekInfo.creator); | 
					
						
							|  |  |  |     if (!creatorUuid) { | 
					
						
							|  |  |  |       window.log.error('updateCallHistoryForGroupCall(): bad creator UUID'); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const conversation = window.ConversationController.get(conversationId); | 
					
						
							|  |  |  |     if (!conversation) { | 
					
						
							|  |  |  |       window.log.error( | 
					
						
							|  |  |  |         'updateCallHistoryForGroupCall(): could not find conversation' | 
					
						
							|  |  |  |       ); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     conversation.updateCallHistoryForGroupCall(peekInfo.eraId, creatorUuid); | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2020-06-04 11:16:19 -07:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | export const calling = new CallingClass(); |