diff --git a/config/default.json b/config/default.json index b61a567d891..6d4d1f3fb97 100644 --- a/config/default.json +++ b/config/default.json @@ -11,6 +11,7 @@ "contentProxyUrl": "http://contentproxy.signal.org:443", "updatesUrl": "https://updates2.signal.org/desktop", "updatesPublicKey": "fd7dd3de7149dc0a127909fee7de0f7620ddd0de061b37a2c303e37de802a401", + "sfuUrl": "https://sfu.voip.signal.org/", "updatesEnabled": false, "openDevTools": false, "buildExpiration": 0, diff --git a/main.js b/main.js index 535d1c9a307..efc776f1537 100644 --- a/main.js +++ b/main.js @@ -204,6 +204,7 @@ function prepareURL(pathSegments, moreKeys) { appInstance: process.env.NODE_APP_INSTANCE, proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy, contentProxyUrl: config.contentProxyUrl, + sfuUrl: config.get('sfuUrl'), importMode: importMode ? true : undefined, // for stringify() serverPublicParams: config.get('serverPublicParams'), serverTrustRoot: config.get('serverTrustRoot'), diff --git a/preload.js b/preload.js index 7e9079db372..ea413a92499 100644 --- a/preload.js +++ b/preload.js @@ -45,6 +45,7 @@ try { window.getHostName = () => config.hostname; window.getServerTrustRoot = () => config.serverTrustRoot; window.getServerPublicParams = () => config.serverPublicParams; + window.getSfuUrl = () => config.sfuUrl; window.isBehindProxy = () => Boolean(config.proxyUrl); function setSystemTheme() { diff --git a/ts/background.ts b/ts/background.ts index f2e634a60fb..3559cdd0ac3 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -646,7 +646,10 @@ type WhatIsThis = import('./window.d').WhatIsThis; window.reduxActions.updates, window.Whisper.events ); - window.Signal.Services.calling.initialize(window.reduxActions.calling); + window.Signal.Services.calling.initialize( + window.reduxActions.calling, + window.getSfuUrl() + ); window.reduxActions.expiration.hydrateExpirationStatus( window.Signal.Util.hasExpired() ); diff --git a/ts/services/calling.ts b/ts/services/calling.ts index 5ad2221dac1..3ff9f68bc2a 100644 --- a/ts/services/calling.ts +++ b/ts/services/calling.ts @@ -55,8 +55,6 @@ import { fetchMembershipProof, getMembershipList } from '../groups'; import { missingCaseError } from '../util/missingCaseError'; import { normalizeGroupCallTimestamp } from '../util/ringrtc/normalizeGroupCallTimestamp'; -const RINGRTC_SFU_URL = 'https://sfu.voip.signal.org/'; - const RINGRTC_HTTP_METHOD_TO_OUR_HTTP_METHOD: Map< HttpMethod, 'GET' | 'PUT' | 'POST' | 'DELETE' @@ -92,6 +90,8 @@ export class CallingClass { private uxActions?: UxActionsType; + private sfuUrl?: string; + private lastMediaDeviceSettings?: MediaDeviceSettings; private deviceReselectionTimer?: NodeJS.Timeout; @@ -105,11 +105,14 @@ export class CallingClass { this.callsByConversation = {}; } - initialize(uxActions: UxActionsType): void { + initialize(uxActions: UxActionsType, sfuUrl: string): void { this.uxActions = uxActions; if (!uxActions) { throw new Error('CallingClass.initialize: Invalid uxActions.'); } + + this.sfuUrl = sfuUrl; + RingRTC.handleOutgoingSignaling = this.handleOutgoingSignaling.bind(this); RingRTC.handleIncomingCall = this.handleIncomingCall.bind(this); RingRTC.handleAutoEndedIncomingCallRequest = this.handleAutoEndedIncomingCallRequest.bind( @@ -333,6 +336,10 @@ export class CallingClass { return statefulPeekInfo; } + if (!this.sfuUrl) { + throw new Error('Missing SFU URL; not peeking group call'); + } + const conversation = window.ConversationController.get(conversationId); if (!conversation) { throw new Error('Missing conversation; not peeking group call'); @@ -352,7 +359,7 @@ export class CallingClass { const membershipProof = new TextEncoder().encode(proof).buffer; return RingRTC.peekGroupCall( - RINGRTC_SFU_URL, + this.sfuUrl, membershipProof, this.getGroupCallMembers(conversationId) ); @@ -388,90 +395,88 @@ export class CallingClass { return existing; } + if (!this.sfuUrl) { + throw new Error('Missing SFU URL; not connecting group call'); + } + const groupIdBuffer = base64ToArrayBuffer(groupId); let updateMessageState = GroupCallUpdateMessageState.SentNothing; let isRequestingMembershipProof = false; - const outerGroupCall = RingRTC.getGroupCall( - groupIdBuffer, - RINGRTC_SFU_URL, - { - onLocalDeviceStateChanged: groupCall => { - const localDeviceState = groupCall.getLocalDeviceState(); - const { eraId } = groupCall.getPeekInfo() || {}; + 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. + this.disableLocalCamera(); + + delete this.callsByConversation[conversationId]; if ( - localDeviceState.connectionState === ConnectionState.NotConnected + updateMessageState === GroupCallUpdateMessageState.SentJoin && + eraId ) { - // 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. - this.disableLocalCamera(); - - delete this.callsByConversation[conversationId]; + updateMessageState = GroupCallUpdateMessageState.SentLeft; + this.sendGroupCallUpdateMessage(conversationId, eraId); + } + } else { + this.callsByConversation[conversationId] = groupCall; - if ( - updateMessageState === GroupCallUpdateMessageState.SentJoin && - eraId - ) { - updateMessageState = GroupCallUpdateMessageState.SentLeft; - this.sendGroupCallUpdateMessage(conversationId, eraId); - } + // NOTE: This assumes only one active call at a time. See comment above. + if (localDeviceState.videoMuted) { + this.disableLocalCamera(); } else { - this.callsByConversation[conversationId] = groupCall; - - // NOTE: This assumes only one active call at a time. See comment above. - if (localDeviceState.videoMuted) { - this.disableLocalCamera(); - } else { - this.videoCapturer.enableCaptureAndSend(groupCall); - } - - if ( - updateMessageState === GroupCallUpdateMessageState.SentNothing && - localDeviceState.joinState === JoinState.Joined && - eraId - ) { - updateMessageState = GroupCallUpdateMessageState.SentJoin; - this.sendGroupCallUpdateMessage(conversationId, eraId); - } + this.videoCapturer.enableCaptureAndSend(groupCall); } - this.syncGroupCallToRedux(conversationId, groupCall); - }, - onRemoteDeviceStatesChanged: groupCall => { - this.syncGroupCallToRedux(conversationId, groupCall); - }, - onPeekChanged: groupCall => { - this.syncGroupCallToRedux(conversationId, groupCall); - }, - async requestMembershipProof(groupCall) { - if (isRequestingMembershipProof) { - return; + if ( + updateMessageState === GroupCallUpdateMessageState.SentNothing && + localDeviceState.joinState === JoinState.Joined && + eraId + ) { + updateMessageState = GroupCallUpdateMessageState.SentJoin; + this.sendGroupCallUpdateMessage(conversationId, eraId); } - isRequestingMembershipProof = true; - try { - const proof = await fetchMembershipProof({ - publicParams, - secretParams, - }); - if (proof) { - const proofArray = new TextEncoder().encode(proof); - groupCall.setMembershipProof(proofArray.buffer); - } - } catch (err) { - window.log.error('Failed to fetch membership proof', err); - } finally { - isRequestingMembershipProof = false; + } + + this.syncGroupCallToRedux(conversationId, groupCall); + }, + onRemoteDeviceStatesChanged: groupCall => { + this.syncGroupCallToRedux(conversationId, groupCall); + }, + onPeekChanged: groupCall => { + 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); } - }, - requestGroupMembers: groupCall => { - groupCall.setGroupMembers(this.getGroupCallMembers(conversationId)); - }, - onEnded: noop, - } - ); + } catch (err) { + window.log.error('Failed to fetch membership proof', err); + } finally { + isRequestingMembershipProof = false; + } + }, + requestGroupMembers: groupCall => { + groupCall.setGroupMembers(this.getGroupCallMembers(conversationId)); + }, + onEnded: noop, + }); if (!outerGroupCall) { // This should be very rare, likely due to RingRTC not being able to get a lock diff --git a/ts/window.d.ts b/ts/window.d.ts index ed6a5551c74..14a35155c74 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -114,6 +114,7 @@ declare global { getMediaCameraPermissions: () => Promise; getMediaPermissions: () => Promise; getServerPublicParams: () => string; + getSfuUrl: () => string; getSocketStatus: () => number; getSyncRequest: () => WhatIsThis; getTitle: () => string;