Add support for ICEServersConfigs (#6371)
This commit is contained in:
parent
5e7f368583
commit
ee38dd39ea
8 changed files with 85 additions and 18 deletions
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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'},
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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 [];
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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[];
|
||||
|
|
|
|||
Loading…
Reference in a new issue