MM-44546 -- Calls: Cloud freemium limits (#6318)
* remove API call to config/pass iceServers; leave call on ws error * cloud limits * fix makeStyleSheetFromTheme * revert podfile & package-lock diffs * update snapshots * edge case of cloud server on calls 0.5.3
This commit is contained in:
parent
1d299d02f0
commit
c74cd14713
14 changed files with 339 additions and 209 deletions
|
|
@ -12,4 +12,8 @@ const RequiredServer = {
|
|||
|
||||
const PluginId = 'com.mattermost.calls';
|
||||
|
||||
export default {RequiredServer, RefreshConfigMillis, PluginId};
|
||||
// Used for case when cloud server is using Calls v0.5.3.
|
||||
// This can be removed when v0.5.4 is prepackaged in cloud.
|
||||
const DefaultCloudMaxParticipants = 8;
|
||||
|
||||
export default {RequiredServer, RefreshConfigMillis, PluginId, DefaultCloudMaxParticipants};
|
||||
|
|
|
|||
|
|
@ -57,31 +57,40 @@ exports[`CallMessage should match snapshot 1`] = `
|
|||
<TouchableOpacity
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="phone-outline"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Join call
|
||||
|
|
@ -239,31 +248,40 @@ exports[`CallMessage should match snapshot for the call already in the current c
|
|||
<TouchableOpacity
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignContent": "center",
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#339970",
|
||||
"borderRadius": 8,
|
||||
"flexDirection": "row",
|
||||
"padding": 12,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="phone-outline"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
"marginRight": 5,
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "white",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"color": "white",
|
||||
},
|
||||
undefined,
|
||||
]
|
||||
}
|
||||
>
|
||||
Current call
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import moment from 'moment-timezone';
|
||||
import React, {useCallback} from 'react';
|
||||
import React from 'react';
|
||||
import {injectIntl, intlShape, IntlShape} from 'react-intl';
|
||||
import {View, TouchableOpacity, Text} from 'react-native';
|
||||
|
||||
|
|
@ -32,6 +32,7 @@ type CallMessageProps = {
|
|||
currentChannelName: string;
|
||||
callChannelName: string;
|
||||
intl: typeof IntlShape;
|
||||
isCloudLimitRestricted: boolean;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
|
|
@ -66,10 +67,16 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
joinCallButtonText: {
|
||||
color: 'white',
|
||||
},
|
||||
joinCallButtonTextRestricted: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
joinCallButtonIcon: {
|
||||
color: 'white',
|
||||
marginRight: 5,
|
||||
},
|
||||
joinCallButtonIconRestricted: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.32),
|
||||
},
|
||||
startedText: {
|
||||
color: theme.centerChannelColor,
|
||||
fontWeight: 'bold',
|
||||
|
|
@ -82,6 +89,9 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
alignItems: 'center',
|
||||
alignContent: 'center',
|
||||
},
|
||||
joinCallButtonRestricted: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.08),
|
||||
},
|
||||
timeText: {
|
||||
color: theme.centerChannelColor,
|
||||
},
|
||||
|
|
@ -98,14 +108,28 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
};
|
||||
});
|
||||
|
||||
const CallMessage = ({post, user, teammateNameDisplay, confirmToJoin, alreadyInTheCall, theme, actions, userTimezone, isMilitaryTime, currentChannelName, callChannelName, intl}: CallMessageProps) => {
|
||||
const CallMessage = ({
|
||||
post,
|
||||
user,
|
||||
teammateNameDisplay,
|
||||
confirmToJoin,
|
||||
alreadyInTheCall,
|
||||
theme,
|
||||
actions,
|
||||
userTimezone,
|
||||
isMilitaryTime,
|
||||
currentChannelName,
|
||||
callChannelName,
|
||||
intl,
|
||||
isCloudLimitRestricted,
|
||||
}: CallMessageProps) => {
|
||||
const style = getStyleSheet(theme);
|
||||
const joinHandler = useCallback(() => {
|
||||
if (alreadyInTheCall) {
|
||||
const joinHandler = () => {
|
||||
if (alreadyInTheCall || isCloudLimitRestricted) {
|
||||
return;
|
||||
}
|
||||
leaveAndJoinWithAlert(intl, post.channel_id, callChannelName, currentChannelName, confirmToJoin, actions.joinCall);
|
||||
}, [post.channel_id, callChannelName, currentChannelName, confirmToJoin, actions.joinCall]);
|
||||
};
|
||||
|
||||
if (post.props.end_at) {
|
||||
return (
|
||||
|
|
@ -142,6 +166,8 @@ const CallMessage = ({post, user, teammateNameDisplay, confirmToJoin, alreadyInT
|
|||
);
|
||||
}
|
||||
|
||||
const joinCallButtonText = alreadyInTheCall ? 'Current call' : 'Join call';
|
||||
|
||||
return (
|
||||
<View style={style.messageStyle}>
|
||||
<CompassIcon
|
||||
|
|
@ -161,22 +187,17 @@ const CallMessage = ({post, user, teammateNameDisplay, confirmToJoin, alreadyInT
|
|||
</View>
|
||||
|
||||
<TouchableOpacity
|
||||
style={style.joinCallButton}
|
||||
style={[style.joinCallButton, isCloudLimitRestricted && style.joinCallButtonRestricted]}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-outline'
|
||||
size={16}
|
||||
style={style.joinCallButtonIcon}
|
||||
style={[style.joinCallButtonIcon, isCloudLimitRestricted && style.joinCallButtonIconRestricted]}
|
||||
/>
|
||||
{alreadyInTheCall &&
|
||||
<Text
|
||||
style={style.joinCallButtonText}
|
||||
>{'Current call'}</Text>}
|
||||
{!alreadyInTheCall &&
|
||||
<Text
|
||||
style={style.joinCallButtonText}
|
||||
>{'Join call'}</Text>}
|
||||
<Text style={[style.joinCallButtonText, isCloudLimitRestricted && style.joinCallButtonTextRestricted]}>
|
||||
{joinCallButtonText}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import {isTimezoneEnabled} from '@mm-redux/selectors/entities/timezone';
|
|||
import {getUser, getCurrentUser} from '@mm-redux/selectors/entities/users';
|
||||
import {getUserCurrentTimezone} from '@mm-redux/utils/timezone_utils';
|
||||
import {joinCall} from '@mmproducts/calls/store/actions/calls';
|
||||
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isCloudLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
import CallMessage from './call_message';
|
||||
|
||||
|
|
@ -41,6 +41,7 @@ function mapStateToProps(state: GlobalState, ownProps: OwnProps) {
|
|||
userTimezone: enableTimezone ? getUserCurrentTimezone(currentUser.timezone) : undefined,
|
||||
currentChannelName: getChannel(state, post.channel_id)?.display_name,
|
||||
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
|
||||
isCloudLimitRestricted: isCloudLimitRestricted(state, post.channel_id),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import {GlobalState} from '@mm-redux/types/store';
|
|||
import {Theme} from '@mm-redux/types/theme';
|
||||
import leaveAndJoinWithAlert from '@mmproducts/calls/components/leave_and_join_alert';
|
||||
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
|
||||
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isCloudLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
|
||||
import Separator from '@screens/channel_info/separator';
|
||||
import {preventDoubleTap} from '@utils/tap';
|
||||
|
|
@ -28,6 +28,7 @@ const StartCall = ({testID, theme, intl, joinCall}: Props) => {
|
|||
const currentCall = useSelector(getCurrentCall);
|
||||
const currentCallChannelId = currentCall?.channelId || '';
|
||||
const callChannelName = useSelector((state: GlobalState) => getChannel(state, currentCallChannelId)?.display_name) || '';
|
||||
const cloudLimitRestricted = useSelector(isCloudLimitRestricted);
|
||||
|
||||
const confirmToJoin = Boolean(currentCall && currentCall.channelId !== currentChannel.id);
|
||||
const alreadyInTheCall = Boolean(currentCall && call && currentCall.channelId === call.channelId);
|
||||
|
|
@ -39,7 +40,7 @@ const StartCall = ({testID, theme, intl, joinCall}: Props) => {
|
|||
const [tryLeaveAndJoin, msgPostfix] = useTryCallsFunction(leaveAndJoin);
|
||||
const handleStartCall = useCallback(preventDoubleTap(tryLeaveAndJoin), [tryLeaveAndJoin]);
|
||||
|
||||
if (alreadyInTheCall) {
|
||||
if (alreadyInTheCall || cloudLimitRestricted) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,89 +1,100 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`JoinCall should match snapshot 1`] = `
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#3DB887",
|
||||
"flexDirection": "row",
|
||||
"height": 38,
|
||||
"justifyContent": "center",
|
||||
"padding": 5,
|
||||
"width": "100%",
|
||||
"backgroundColor": "#ffffff",
|
||||
}
|
||||
}
|
||||
>
|
||||
<CompassIcon
|
||||
name="phone-in-talk"
|
||||
size={16}
|
||||
<Pressable
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"marginLeft": 10,
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
Array [
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#3DB887",
|
||||
"flexDirection": "row",
|
||||
"height": 38,
|
||||
"justifyContent": "center",
|
||||
"padding": 5,
|
||||
"width": "100%",
|
||||
},
|
||||
false,
|
||||
]
|
||||
}
|
||||
>
|
||||
Join Call
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"flex": 1,
|
||||
"fontWeight": "400",
|
||||
"marginLeft": 10,
|
||||
<CompassIcon
|
||||
name="phone-in-talk"
|
||||
size={16}
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"marginLeft": 10,
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedRelativeTime
|
||||
updateIntervalInSeconds={1}
|
||||
value={100}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginRight": 5,
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"fontSize": 16,
|
||||
"fontWeight": "bold",
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Avatars)
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.56)",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"paddingHorizontal": 16,
|
||||
"paddingVertical": 0,
|
||||
"top": 16,
|
||||
>
|
||||
Join Call
|
||||
</Text>
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "#ffffff",
|
||||
"flex": 1,
|
||||
"fontWeight": "400",
|
||||
"marginLeft": 10,
|
||||
}
|
||||
}
|
||||
>
|
||||
<FormattedRelativeTime
|
||||
updateIntervalInSeconds={1}
|
||||
value={100}
|
||||
/>
|
||||
</Text>
|
||||
<View
|
||||
style={
|
||||
Object {
|
||||
"marginRight": 5,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Connect(Avatars)
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text
|
||||
style={
|
||||
Object {
|
||||
"color": "rgba(63,67,80,0.56)",
|
||||
"fontSize": 12,
|
||||
"fontWeight": "600",
|
||||
"paddingHorizontal": 16,
|
||||
"paddingVertical": 0,
|
||||
"top": 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
>
|
||||
Call participants
|
||||
</Text>
|
||||
}
|
||||
userIds={
|
||||
Array [
|
||||
"user-1-id",
|
||||
"user-2-id",
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
>
|
||||
Call participants
|
||||
</Text>
|
||||
}
|
||||
userIds={
|
||||
Array [
|
||||
"user-1-id",
|
||||
"user-2-id",
|
||||
]
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
`;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {bindActionCreators, Dispatch} from 'redux';
|
|||
import {getChannel, getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
|
||||
import {getTheme} from '@mm-redux/selectors/entities/preferences';
|
||||
import {joinCall} from '@mmproducts/calls/store/actions/calls';
|
||||
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
|
||||
import {getCalls, getCurrentCall, isCloudLimitRestricted} from '@mmproducts/calls/store/selectors/calls';
|
||||
|
||||
import JoinCall from './join_call';
|
||||
|
||||
|
|
@ -23,6 +23,7 @@ function mapStateToProps(state: GlobalState) {
|
|||
alreadyInTheCall: Boolean(currentCall && call && currentCall.channelId === call.channelId),
|
||||
currentChannelName: getChannel(state, currentChannelId)?.display_name,
|
||||
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
|
||||
isCloudLimitRestricted: isCloudLimitRestricted(state),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ describe('JoinCall', () => {
|
|||
alreadyInTheCall: false,
|
||||
currentChannelName: 'Current Channel',
|
||||
callChannelName: 'Call Channel',
|
||||
isCloudLimitRestricted: false,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
|
|
@ -56,7 +57,7 @@ describe('JoinCall', () => {
|
|||
test('should join on click', () => {
|
||||
const joinCall = jest.fn();
|
||||
const props = {...baseProps, actions: {joinCall}};
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive();
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive().childAt(0);
|
||||
|
||||
wrapper.simulate('press');
|
||||
expect(Alert.alert).not.toHaveBeenCalled();
|
||||
|
|
@ -66,7 +67,7 @@ describe('JoinCall', () => {
|
|||
test('should ask for confirmation on click', () => {
|
||||
const joinCall = jest.fn();
|
||||
const props = {...baseProps, confirmToJoin: true, actions: {joinCall}};
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive();
|
||||
const wrapper = shallowWithIntl(<JoinCall {...props}/>).dive().childAt(0);
|
||||
|
||||
wrapper.simulate('press');
|
||||
expect(Alert.alert).toHaveBeenCalled();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useMemo} from 'react';
|
||||
import React, {useEffect, useMemo} from 'react';
|
||||
import {injectIntl, IntlShape} from 'react-intl';
|
||||
import {View, Text, Pressable} from 'react-native';
|
||||
|
||||
|
|
@ -26,49 +26,62 @@ type Props = {
|
|||
alreadyInTheCall: boolean;
|
||||
currentChannelName: string;
|
||||
callChannelName: string;
|
||||
isCloudLimitRestricted: boolean;
|
||||
intl: typeof IntlShape;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
return {
|
||||
container: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#3DB887',
|
||||
width: '100%',
|
||||
padding: 5,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: JOIN_CALL_BAR_HEIGHT,
|
||||
},
|
||||
joinCallIcon: {
|
||||
color: theme.sidebarText,
|
||||
marginLeft: 10,
|
||||
marginRight: 5,
|
||||
},
|
||||
joinCall: {
|
||||
color: theme.sidebarText,
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
},
|
||||
started: {
|
||||
flex: 1,
|
||||
color: theme.sidebarText,
|
||||
fontWeight: '400',
|
||||
marginLeft: 10,
|
||||
},
|
||||
avatars: {
|
||||
marginRight: 5,
|
||||
},
|
||||
headerText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 0,
|
||||
top: 16,
|
||||
},
|
||||
};
|
||||
});
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
outerContainer: {
|
||||
backgroundColor: theme.centerChannelBg,
|
||||
},
|
||||
innerContainer: {
|
||||
flexDirection: 'row',
|
||||
backgroundColor: '#3DB887',
|
||||
width: '100%',
|
||||
padding: 5,
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
height: JOIN_CALL_BAR_HEIGHT,
|
||||
},
|
||||
innerContainerRestricted: {
|
||||
backgroundColor: changeOpacity(theme.centerChannelColor, 0.48),
|
||||
},
|
||||
joinCallIcon: {
|
||||
color: theme.sidebarText,
|
||||
marginLeft: 10,
|
||||
marginRight: 5,
|
||||
},
|
||||
joinCall: {
|
||||
color: theme.sidebarText,
|
||||
fontWeight: 'bold',
|
||||
fontSize: 16,
|
||||
},
|
||||
started: {
|
||||
flex: 1,
|
||||
color: theme.sidebarText,
|
||||
fontWeight: '400',
|
||||
marginLeft: 10,
|
||||
},
|
||||
limitReached: {
|
||||
flex: 1,
|
||||
display: 'flex',
|
||||
textAlign: 'right',
|
||||
marginRight: 10,
|
||||
color: '#FFFFFFD6',
|
||||
fontWeight: '400',
|
||||
},
|
||||
avatars: {
|
||||
marginRight: 5,
|
||||
},
|
||||
headerText: {
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
fontSize: 12,
|
||||
fontWeight: '600',
|
||||
paddingHorizontal: 16,
|
||||
paddingVertical: 0,
|
||||
top: 16,
|
||||
},
|
||||
}));
|
||||
|
||||
const JoinCall = (props: Props) => {
|
||||
if (!props.call) {
|
||||
|
|
@ -82,9 +95,12 @@ const JoinCall = (props: Props) => {
|
|||
};
|
||||
}, [props.call, props.alreadyInTheCall]);
|
||||
|
||||
const joinHandler = useCallback(() => {
|
||||
const joinHandler = () => {
|
||||
if (props.isCloudLimitRestricted) {
|
||||
return;
|
||||
}
|
||||
leaveAndJoinWithAlert(props.intl, props.call.channelId, props.callChannelName, props.currentChannelName, props.confirmToJoin, props.actions.joinCall);
|
||||
}, [props.call.channelId, props.callChannelName, props.currentChannelName, props.confirmToJoin, props.actions.joinCall]);
|
||||
};
|
||||
|
||||
if (props.alreadyInTheCall) {
|
||||
return null;
|
||||
|
|
@ -96,32 +112,39 @@ const JoinCall = (props: Props) => {
|
|||
}, [props.call.participants]);
|
||||
|
||||
return (
|
||||
<Pressable
|
||||
style={style.container}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={16}
|
||||
style={style.joinCallIcon}
|
||||
/>
|
||||
<Text style={style.joinCall}>{'Join Call'}</Text>
|
||||
<Text style={style.started}>
|
||||
<FormattedRelativeTime
|
||||
value={props.call.startTime}
|
||||
updateIntervalInSeconds={1}
|
||||
<View style={style.outerContainer}>
|
||||
<Pressable
|
||||
style={[style.innerContainer, props.isCloudLimitRestricted && style.innerContainerRestricted]}
|
||||
onPress={joinHandler}
|
||||
>
|
||||
<CompassIcon
|
||||
name='phone-in-talk'
|
||||
size={16}
|
||||
style={style.joinCallIcon}
|
||||
/>
|
||||
</Text>
|
||||
<View style={style.avatars}>
|
||||
<Avatars
|
||||
userIds={userIds}
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text style={style.headerText}>{'Call participants'}</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
<Text style={style.joinCall}>{'Join Call'}</Text>
|
||||
{props.isCloudLimitRestricted ?
|
||||
<Text style={style.limitReached}>
|
||||
{'Participant limit reached'}
|
||||
</Text> :
|
||||
<Text style={style.started}>
|
||||
<FormattedRelativeTime
|
||||
value={props.call.startTime}
|
||||
updateIntervalInSeconds={1}
|
||||
/>
|
||||
</Text>
|
||||
}
|
||||
<View style={style.avatars}>
|
||||
<Avatars
|
||||
userIds={userIds}
|
||||
breakAt={1}
|
||||
listTitle={
|
||||
<Text style={style.headerText}>{'Call participants'}</Text>
|
||||
}
|
||||
/>
|
||||
</View>
|
||||
</Pressable>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
export default injectIntl(JoinCall);
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export let client: any = null;
|
|||
|
||||
const websocketConnectTimeout = 3000;
|
||||
|
||||
export async function newClient(channelID: string, closeCb: () => void, setScreenShareURL: (url: string) => void) {
|
||||
export async function newClient(channelID: string, iceServers: string[], closeCb: () => void, setScreenShareURL: (url: string) => void) {
|
||||
let peer: Peer | null = null;
|
||||
let stream: MediaStream;
|
||||
let voiceTrackAdded = false;
|
||||
|
|
@ -119,16 +119,8 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
|
|||
});
|
||||
|
||||
ws.on('join', async () => {
|
||||
let config;
|
||||
try {
|
||||
config = await Client4.getCallsConfig();
|
||||
} catch (err) {
|
||||
console.log('ERROR FETCHING CALLS CONFIG:', err); // eslint-disable-line no-console
|
||||
return;
|
||||
}
|
||||
|
||||
InCallManager.start({media: 'audio'});
|
||||
peer = new Peer(null, config.ICEServers);
|
||||
peer = new Peer(null, iceServers);
|
||||
peer.on('signal', (data: any) => {
|
||||
if (data.type === 'offer' || data.type === 'answer') {
|
||||
ws.send('sdp', {
|
||||
|
|
|
|||
|
|
@ -117,11 +117,14 @@ describe('Actions.Calls', () => {
|
|||
expect(CallsActions.ws.disconnect).not.toBeCalled();
|
||||
const disconnectMock = CallsActions.ws.disconnect;
|
||||
await store.dispatch(CallsActions.leaveCall());
|
||||
|
||||
// ws.disconnect calls the callback, which is what sends the CallsTypes.RECEIVED_MYSELF_LEFT_CALL action.
|
||||
expect(disconnectMock).toBeCalled();
|
||||
await store.dispatch({type: CallsTypes.RECEIVED_MYSELF_LEFT_CALL});
|
||||
expect(CallsActions.ws).toBe(null);
|
||||
|
||||
result = store.getState().entities.calls.joined;
|
||||
assert.equal('', result);
|
||||
assert.equal(result, '');
|
||||
});
|
||||
|
||||
it('muteMyself', async () => {
|
||||
|
|
|
|||
|
|
@ -189,7 +189,10 @@ export function joinCall(channelId: string, intl: typeof intlShape): ActionFunc
|
|||
dispatch(setSpeakerphoneOn(false));
|
||||
|
||||
try {
|
||||
ws = await newClient(channelId, () => null, setScreenShareURL);
|
||||
ws = await newClient(channelId, getConfig(getState()).ICEServers, () => {
|
||||
dispatch(setSpeakerphoneOn(false));
|
||||
dispatch({type: CallsTypes.RECEIVED_MYSELF_LEFT_CALL});
|
||||
}, setScreenShareURL);
|
||||
} catch (error) {
|
||||
forceLogoutIfNecessary(error, dispatch, getState);
|
||||
dispatch(logError(error));
|
||||
|
|
@ -212,13 +215,11 @@ export function joinCall(channelId: string, intl: typeof intlShape): ActionFunc
|
|||
}
|
||||
|
||||
export function leaveCall(): ActionFunc {
|
||||
return async (dispatch: DispatchFunc) => {
|
||||
return async () => {
|
||||
if (ws) {
|
||||
ws.disconnect();
|
||||
ws = null;
|
||||
}
|
||||
dispatch(setSpeakerphoneOn(false));
|
||||
dispatch({type: CallsTypes.RECEIVED_MYSELF_LEFT_CALL});
|
||||
return {};
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +1,15 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createSelector} from 'reselect';
|
||||
|
||||
import {Client4} from '@client/rest';
|
||||
import Calls from '@constants/calls';
|
||||
import {getCurrentChannelId} from '@mm-redux/selectors/entities/common';
|
||||
import {getServerVersion} from '@mm-redux/selectors/entities/general';
|
||||
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';
|
||||
|
||||
export function getConfig(state: GlobalState) {
|
||||
return state.entities.calls.config;
|
||||
|
|
@ -65,3 +68,49 @@ export function isSupportedServer(state: GlobalState) {
|
|||
export function isCallsPluginEnabled(state: GlobalState) {
|
||||
return state.entities.calls.pluginEnabled;
|
||||
}
|
||||
|
||||
const isCloud: (state: GlobalState) => boolean = createSelector(
|
||||
getLicense,
|
||||
(license) => license?.Cloud === 'true',
|
||||
);
|
||||
|
||||
// NOTE: Calls v0.5.3 will not return sku_short_name in config, so this will fail
|
||||
const isCloudProfessionalOrEnterprise: (state: GlobalState) => boolean = createSelector(
|
||||
isCloud,
|
||||
getLicense,
|
||||
getConfig,
|
||||
(cloud, license, config) => {
|
||||
return cloud && (config.sku_short_name === 'professional' || config.sku_short_name === 'enterprise');
|
||||
},
|
||||
);
|
||||
|
||||
const getCallInCurrentChannel: (state: GlobalState) => Call | undefined = createSelector(
|
||||
getCurrentChannelId,
|
||||
getCalls,
|
||||
(currentChannelId, calls) => calls[currentChannelId],
|
||||
);
|
||||
|
||||
export const isCloudLimitRestricted: (state: GlobalState, channelId?: string) => boolean = createSelector(
|
||||
isCloud,
|
||||
isCloudProfessionalOrEnterprise,
|
||||
(state: GlobalState, channelId: string) => (channelId ? getCalls(state)[channelId] : getCallInCurrentChannel(state)),
|
||||
getConfig,
|
||||
(cloud, isCloudPaid, call, config) => {
|
||||
if (!call || !cloud) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: The next block is used for case when cloud server is using Calls v0.5.3. This can be removed
|
||||
// when v0.5.4 is prepackaged in cloud. Then replace the max in the return statement with
|
||||
// config.cloud_max_participants, and replace cloudPaid with isCloudPaid
|
||||
let max = config.cloud_max_participants;
|
||||
let cloudPaid = isCloudPaid;
|
||||
if (cloud && !max) {
|
||||
// We're not sure if we're in cloud paid because this could be v0.5.3, so assume we are for now (the server will prevent calls)
|
||||
cloudPaid = true;
|
||||
max = Calls.DefaultCloudMaxParticipants;
|
||||
}
|
||||
|
||||
return cloudPaid && Object.keys(call.participants || {}).length >= max;
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ export type ServerConfig = {
|
|||
ICEServers: string[];
|
||||
AllowEnableCalls: boolean;
|
||||
DefaultEnabled: boolean;
|
||||
sku_short_name: string;
|
||||
cloud_max_participants: number;
|
||||
last_retrieved_at: number;
|
||||
}
|
||||
|
||||
|
|
@ -67,5 +69,7 @@ export const DefaultServerConfig = {
|
|||
ICEServers: [],
|
||||
AllowEnableCalls: false,
|
||||
DefaultEnabled: false,
|
||||
sku_short_name: '',
|
||||
cloud_max_participants: 0,
|
||||
last_retrieved_at: 0,
|
||||
} as ServerConfig;
|
||||
|
|
|
|||
Loading…
Reference in a new issue