Add support for ICEServersConfigs (#6371)

This commit is contained in:
Claudio Costa 2022-06-16 14:15:17 +02:00 committed by GitHub
parent 5e7f368583
commit ee38dd39ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 85 additions and 18 deletions

View file

@ -14,6 +14,7 @@ import {
import {Client4} from '@client/rest';
import {WebsocketEvents} from '@constants';
import {ICEServersConfigs} from '@mmproducts/calls/store/types/calls';
import Peer from './simple-peer';
import WebSocketClient from './websocket';
@ -22,7 +23,7 @@ export let client: any = null;
const websocketConnectTimeout = 3000;
export async function newClient(channelID: string, iceServers: string[], closeCb: () => void, setScreenShareURL: (url: string) => void) {
export async function newClient(channelID: string, iceServers: ICEServersConfigs, closeCb: () => void, setScreenShareURL: (url: string) => void) {
let peer: Peer | null = null;
let stream: MediaStream;
let voiceTrackAdded = false;

View file

@ -21,6 +21,8 @@ import {
} from 'react-native-webrtc';
import stream from 'readable-stream';
import {ICEServersConfigs} from '@mmproducts/calls/store/types/calls';
const queueMicrotask = (callback: any) => {
Promise.resolve().then(callback).catch((e) => setTimeout(() => {
throw e;
@ -94,7 +96,7 @@ export default class Peer extends stream.Duplex {
private pc: RTCPeerConnection|null = null;
private onFinishBound?: () => void;
constructor(localStream: MediaStream | null, iceServers?: string[]) {
constructor(localStream: MediaStream | null, iceServers: ICEServersConfigs) {
super({allowHalfOpen: false});
this.streams = localStream ? [localStream] : [];
@ -104,21 +106,10 @@ export default class Peer extends stream.Duplex {
};
const connConfig = {
iceServers: [
{
urls: [
'stun:stun.l.google.com:19302',
'stun:global.stun.twilio.com:3478',
],
},
],
iceServers,
sdpSemantics: 'unified-plan',
};
if (iceServers && iceServers.length > 0) {
connConfig.iceServers[0].urls = iceServers;
}
try {
this.pc = new RTCPeerConnection(connConfig);
} catch (err) {

View file

@ -34,7 +34,10 @@ jest.mock('@client/rest', () => ({
},
]),
getCallsConfig: jest.fn(() => ({
ICEServers: ['mattermost.com'],
ICEServersConfigs: [{
urls: 'stun:stun1.example.com',
},
],
AllowEnableCalls: true,
DefaultEnabled: true,
last_retrieved_at: 1234,

View file

@ -28,6 +28,7 @@ import CallsTypes from '@mmproducts/calls/store/action_types/calls';
import {
getCallInCurrentChannel,
getConfig,
getICEServersConfigs,
getNumCurrentConnectedParticipants,
} from '@mmproducts/calls/store/selectors/calls';
import {Call, CallParticipant, DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
@ -201,7 +202,7 @@ export function joinCall(channelId: string, intl: typeof intlShape): ActionFunc
dispatch(setSpeakerphoneOn(false));
try {
ws = await newClient(channelId, getConfig(getState()).ICEServers, () => {
ws = await newClient(channelId, getICEServersConfigs(getState()), () => {
dispatch(setSpeakerphoneOn(false));
dispatch({type: CallsTypes.RECEIVED_MYSELF_LEFT_CALL});
}, setScreenShareURL);

View file

@ -408,7 +408,7 @@ describe('Reducers.calls.config', () => {
const testAction = {
type: CallsTypes.RECEIVED_CONFIG,
data: {
ICEServers: ['google.com'],
ICEServers: ['stun:stun.example.com'],
AllowEnableCalls: true,
DefaultEnabled: true,
last_retrieved_at: 123,

View file

@ -197,4 +197,47 @@ describe('Selectors.Calls', () => {
// On cloud, MaxCallParticipants missing, default should be used.
assert.equal(Selectors.isLimitRestricted(newState, 'call1'), true);
});
it('getICEServersConfigs', () => {
assert.deepEqual(Selectors.getICEServersConfigs(testState), []);
// backwards compatible case, no ICEServersConfigs present.
let newState = {
...testState,
entities: {
...testState.entities,
calls: {
...testState.entities.calls,
config: {
...testState.entities.calls.config,
ICEServers: ['stun:stun1.example.com'],
},
},
},
};
assert.deepEqual(Selectors.getICEServersConfigs(newState), [{urls: ['stun:stun1.example.com']}]);
// ICEServersConfigs defined case
newState = {
...testState,
entities: {
...testState.entities,
calls: {
...testState.entities.calls,
config: {
...testState.entities.calls.config,
ICEServers: ['stun:stun1.example.com'],
ICEServersConfigs: [
{urls: 'stun:stun1.example.com'},
{urls: 'turn:turn.example.com', username: 'username', credentail: 'password'},
],
},
},
},
};
assert.deepEqual(Selectors.getICEServersConfigs(newState), [
{urls: 'stun:stun1.example.com'},
{urls: 'turn:turn.example.com', username: 'username', credentail: 'password'},
]);
});
});

View file

@ -9,7 +9,7 @@ import {getCurrentChannelId} from '@mm-redux/selectors/entities/common';
import {getLicense, getServerVersion} from '@mm-redux/selectors/entities/general';
import {GlobalState} from '@mm-redux/types/store';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import {Call} from '@mmproducts/calls/store/types/calls';
import {Call, ICEServersConfigs} from '@mmproducts/calls/store/types/calls';
export function getConfig(state: GlobalState) {
return state.entities.calls.config;
@ -114,3 +114,25 @@ export const isLimitRestricted: (state: GlobalState, channelId?: string) => bool
return max !== 0 && numParticipants >= max;
},
);
export const getICEServersConfigs: (state: GlobalState) => ICEServersConfigs = createSelector(
getConfig,
(config) => {
// if ICEServersConfigs is set, we can trust this to be complete and
// coming from an updated API.
if (config.ICEServersConfigs?.length > 0) {
return config.ICEServersConfigs;
}
// otherwise we revert to using the now deprecated field.
if (config.ICEServers?.length > 0) {
return [
{
urls: config.ICEServers,
},
];
}
return [];
},
);

View file

@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ConfigurationParamWithUrls, ConfigurationParamWithUrl} from 'react-native-webrtc';
import {UserProfile} from '@mm-redux/types/users';
import {Dictionary} from '@mm-redux/types/utilities';
@ -60,6 +62,7 @@ export type VoiceEventData = {
export type ServerConfig = {
ICEServers: string[];
ICEServersConfigs: ICEServersConfigs;
AllowEnableCalls: boolean;
DefaultEnabled: boolean;
MaxCallParticipants: number;
@ -69,9 +72,12 @@ export type ServerConfig = {
export const DefaultServerConfig = {
ICEServers: [],
ICEServersConfigs: [],
AllowEnableCalls: false,
DefaultEnabled: false,
MaxCallParticipants: 0,
sku_short_name: '',
last_retrieved_at: 0,
} as ServerConfig;
export type ICEServersConfigs = ConfigurationParamWithUrls[] | ConfigurationParamWithUrl[];