Compare commits

...

14 Commits

Author SHA1 Message Date
Elias Nahum
21ac2b3168 Bump app build number to 397 (#6235) 2022-05-05 14:22:29 -04:00
Mattermost Build
491209fdae MM-43904 - Fix: Calls batch actions (#6229) (#6233)
* fix batch actions

* tests

(cherry picked from commit 67c65156a7)

Co-authored-by: Christopher Poile <cpoile@gmail.com>
2022-05-05 14:13:30 -04:00
Elias Nahum
696f8d69a3 Dot release 1.51.2 (#6217)
* Bump app version number to  1.51.2

* Bump app build number to  395
2022-05-03 18:10:42 -04:00
Mattermost Build
0e06eb60fb MM-43904 - Fix: Calls: "Access to route for non-existent plugin" error log (#6210) (#6213)
* add isCallsPluginEnabled; refactor Calls.PluginId

* revert Podfile.lock changes

(cherry picked from commit 912287fbe0)

Co-authored-by: Christopher Poile <cpoile@gmail.com>
2022-05-03 15:10:40 -04:00
Elias Nahum
7a672c39b5 Bump version 1.51.1 build 392 (#6187)
* Bump app version number to  1.51.1

* Bump app build number to  392
2022-04-22 10:48:52 -04:00
Mattermost Build
aed2bd2d09 Show login options when using multiple SSO login methods (#6185) (#6186)
(cherry picked from commit 8303588d29)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2022-04-21 12:59:02 -04:00
Mattermost Build
275992bbee Bump app build number to 391 (#6159) (#6160)
(cherry picked from commit 31a1efaff9)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2022-04-11 14:39:43 -04:00
Mattermost Build
82df285146 MM-42320: Calls - Fix for: iOS loses calls audio after leaving 1st call and entering next (#6143) (#6158)
* allow 2nd+ calls to connect to iOS microphone/audio

* peerconnection callback & index.d.ts, webrtc and incallmanager deps

(cherry picked from commit 3739d3d2f8)

Co-authored-by: Christopher Poile <cpoile@gmail.com>
2022-04-11 12:14:46 -04:00
Mattermost Build
eb353936b1 Bump app build number to 390 (#6149) (#6150)
(cherry picked from commit a40399a98e)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2022-04-08 13:59:41 -04:00
Mattermost Build
996b78e027 Fix floating container using a hook inside the styles (#6146) (#6148)
(cherry picked from commit 6936d2bca8)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2022-04-08 13:10:43 -04:00
Mattermost Build
a195a6ed53 [v1] Upgrade Commonmark (#6145) (#6147)
* upgrade commonmark

* Fix messages with markdown being potentially cut off

* Fix messages with markdown from potentially being cut off

(cherry picked from commit ce72fd874f)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2022-04-08 10:09:10 -04:00
Mattermost Build
6dc9632240 MM-42320: Calls - small fix for microphone permissions (#6134) (#6137)
* small fix for microphone permissions

* use standard permissions check/denied popups

* tests and i18n

* permission wording

* 18n

(cherry picked from commit 368584a1b7)

Co-authored-by: Christopher Poile <cpoile@gmail.com>
2022-04-06 15:02:08 -04:00
Mattermost Build
e16e89bb00 MM-42821 - Calls mobile: Default calls doesn't work on mobile (#6107) (#6120)
* use call config when checking for Enable/Disable and StartCall permissions

* store config, refresh after 20 minutes

* load config on startup

* batch load calls and load config on startup/reconnect

(cherry picked from commit 10479d1d35)

Co-authored-by: Christopher Poile <cpoile@gmail.com>
2022-04-01 10:29:51 -04:00
Mattermost Build
b13f07a66a Bump version 1.51.0 build 389 (#6110) (#6111)
* Bump app version number to  1.51.0

* Bump app build number to  389

* update fastlane & critical deps

* detox npm audit

(cherry picked from commit 6251c3ce2b)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
2022-03-31 12:16:52 -03:00
53 changed files with 891 additions and 1056 deletions

View File

@@ -131,8 +131,8 @@ android {
applicationId "com.mattermost.rnbeta"
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 388
versionName "1.50.1"
versionCode 397
versionName "1.51.2"
multiDexEnabled = true
testBuildType System.getProperty('testBuildType', 'debug')
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'

View File

@@ -24,7 +24,7 @@ import {TeamMembership} from '@mm-redux/types/teams';
import {WebSocketMessage} from '@mm-redux/types/websocket';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {removeUserFromList} from '@mm-redux/utils/user_utils';
import {loadCalls} from '@mmproducts/calls/store/actions/calls';
import {batchLoadCalls} from '@mmproducts/calls/store/actions/calls';
import {
handleCallStarted,
handleCallUserConnected,
@@ -171,7 +171,7 @@ export function doReconnect(now: number) {
if (!me.error) {
if (isSupportedServerCalls) {
dispatch(loadCalls());
dispatch(batchLoadCalls(true));
}
const roles = [];

View File

@@ -7,6 +7,7 @@
import {RNFetchBlobFetchRepsonse} from 'rn-fetch-blob';
import urlParse from 'url-parse';
import Calls from '@constants/calls';
import {Options} from '@mm-redux/types/client4';
import * as ClientConstants from './constants';
@@ -286,12 +287,16 @@ export default class ClientBase {
return `${this.getUserThreadsRoute(userId, teamId)}/${threadId}`;
}
getPluginsRoute() {
return `${this.getBaseRoute()}/plugins`;
}
getAppsProxyRoute() {
return `${this.url}/plugins/com.mattermost.apps`;
}
getCallsRoute() {
return `${this.url}/plugins/com.mattermost.calls`;
return `${this.url}/plugins/${Calls.PluginId}`;
}
// Client Helpers

View File

@@ -1,6 +1,7 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import ClientPlugins, {ClientPluginsMix} from '@client/rest/plugins';
import ClientCalls, {ClientCallsMix} from '@mmproducts/calls/client/rest';
import mix from '@utils/mix';
@@ -36,7 +37,8 @@ interface Client extends ClientBase,
ClientTeamsMix,
ClientTosMix,
ClientUsersMix,
ClientCallsMix
ClientCallsMix,
ClientPluginsMix
{}
class Client extends mix(ClientBase).with(
@@ -55,6 +57,7 @@ class Client extends mix(ClientBase).with(
ClientTos,
ClientUsers,
ClientCalls,
ClientPlugins,
) {}
const Client4 = new Client();

View File

@@ -0,0 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {ClientPluginManifest} from '@mm-redux/types/plugins';
export interface ClientPluginsMix {
getPluginsManifests: () => Promise<ClientPluginManifest[]>;
}
const ClientPlugins = (superclass: any) => class extends superclass {
getPluginsManifests = async () => {
return this.doFetch(
`${this.getPluginsRoute()}/webapp`,
{method: 'get'},
);
};
};
export default ClientPlugins;

View File

@@ -1,6 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
const RefreshConfigMillis = 20 * 60 * 1000; // Refresh config after 20 minutes
const RequiredServer = {
FULL_VERSION: '6.3.0',
MAJOR_VERSION: 6,
@@ -8,4 +10,6 @@ const RequiredServer = {
PATCH_VERSION: 0,
};
export default {RequiredServer};
const PluginId = 'com.mattermost.calls';
export default {RequiredServer, RefreshConfigMillis, PluginId};

View File

@@ -1,5 +1,8 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import Calls from '@constants/calls';
const WebsocketEvents = {
POSTED: 'posted',
POST_EDITED: 'post_edited',
@@ -53,18 +56,18 @@ const WebsocketEvents = {
SIDEBAR_CATEGORY_UPDATED: 'sidebar_category_updated',
SIDEBAR_CATEGORY_DELETED: 'sidebar_category_deleted',
SIDEBAR_CATEGORY_ORDER_UPDATED: 'sidebar_category_order_updated',
CALLS_CHANNEL_ENABLED: 'custom_com.mattermost.calls_channel_enable_voice',
CALLS_CHANNEL_DISABLED: 'custom_com.mattermost.calls_channel_disable_voice',
CALLS_USER_CONNECTED: 'custom_com.mattermost.calls_user_connected',
CALLS_USER_DISCONNECTED: 'custom_com.mattermost.calls_user_disconnected',
CALLS_USER_MUTED: 'custom_com.mattermost.calls_user_muted',
CALLS_USER_UNMUTED: 'custom_com.mattermost.calls_user_unmuted',
CALLS_USER_VOICE_ON: 'custom_com.mattermost.calls_user_voice_on',
CALLS_USER_VOICE_OFF: 'custom_com.mattermost.calls_user_voice_off',
CALLS_CALL_START: 'custom_com.mattermost.calls_call_start',
CALLS_SCREEN_ON: 'custom_com.mattermost.calls_user_screen_on',
CALLS_SCREEN_OFF: 'custom_com.mattermost.calls_user_screen_off',
CALLS_USER_RAISE_HAND: 'custom_com.mattermost.calls_user_raise_hand',
CALLS_USER_UNRAISE_HAND: 'custom_com.mattermost.calls_user_unraise_hand',
CALLS_CHANNEL_ENABLED: `custom_${Calls.PluginId}_channel_enable_voice`,
CALLS_CHANNEL_DISABLED: `custom_${Calls.PluginId}_channel_disable_voice`,
CALLS_USER_CONNECTED: `custom_${Calls.PluginId}_user_connected`,
CALLS_USER_DISCONNECTED: `custom_${Calls.PluginId}_user_disconnected`,
CALLS_USER_MUTED: `custom_${Calls.PluginId}_user_muted`,
CALLS_USER_UNMUTED: `custom_${Calls.PluginId}_user_unmuted`,
CALLS_USER_VOICE_ON: `custom_${Calls.PluginId}_user_voice_on`,
CALLS_USER_VOICE_OFF: `custom_${Calls.PluginId}_user_voice_off`,
CALLS_CALL_START: `custom_${Calls.PluginId}_call_start`,
CALLS_SCREEN_ON: `custom_${Calls.PluginId}_user_screen_on`,
CALLS_SCREEN_OFF: `custom_${Calls.PluginId}_user_screen_off`,
CALLS_USER_RAISE_HAND: `custom_${Calls.PluginId}_user_raise_hand`,
CALLS_USER_UNRAISE_HAND: `custom_${Calls.PluginId}_user_unraise_hand`,
};
export default WebsocketEvents;

View File

@@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {ServerChannelState} from '@mmproducts/calls/store/types/calls';
import type {ServerChannelState, ServerConfig} from '@mmproducts/calls/store/types/calls';
export interface ClientCallsMix {
getEnabled: () => Promise<Boolean>;
getCalls: () => Promise<ServerChannelState[]>;
getCallsConfig: () => Promise<ServerConfig>;
enableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
disableChannelCalls: (channelId: string) => Promise<ServerChannelState>;
}
@@ -34,7 +35,7 @@ const ClientCalls = (superclass: any) => class extends superclass {
return this.doFetch(
`${this.getCallsRoute()}/config`,
{method: 'get'},
);
) as ServerConfig;
};
enableChannelCalls = async (channelId: string) => {

View File

@@ -1,149 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`EnableDisableCalls should match snapshot if calls are disabled 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Enable Calls"
icon="phone-outline"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;
exports[`EnableDisableCalls should match snapshot if calls are enabled 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Disable Calls"
icon="phone-outline"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;

View File

@@ -3,7 +3,7 @@
import moment from 'moment-timezone';
import React, {useCallback} from 'react';
import {injectIntl, IntlShape} from 'react-intl';
import {injectIntl, intlShape, IntlShape} from 'react-intl';
import {View, TouchableOpacity, Text} from 'react-native';
import CompassIcon from '@components/compass_icon';
@@ -19,7 +19,7 @@ import type {UserProfile} from '@mm-redux/types/users';
type CallMessageProps = {
actions: {
joinCall: (channelId: string) => void;
joinCall: (channelId: string, intl: typeof intlShape) => void;
};
post: Post;
user: UserProfile;

View File

@@ -0,0 +1,40 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {intlShape} from 'react-intl';
import {Theme} from '@mm-redux/types/theme';
import EnableDisableCalls from '@mmproducts/calls/components/channel_info/enable_disable_calls';
import StartCall from '@mmproducts/calls/components/channel_info/start_call';
import {useCallsChannelSettings} from '@mmproducts/calls/hooks';
type Props = {
theme: Theme;
joinCall: (channelId: string, intl: typeof intlShape) => void;
}
const CallsChannelInfo = ({theme, joinCall}: Props) => {
const [enabled, canEnableDisable] = useCallsChannelSettings();
return (
<>
{enabled &&
<StartCall
testID='channel_info.start_call.action'
theme={theme}
joinCall={joinCall}
/>
}
{canEnableDisable &&
<EnableDisableCalls
testID='channel_info.start_call.action'
theme={theme}
enabled={enabled}
/>
}
</>
);
};
export default CallsChannelInfo;

View File

@@ -2,30 +2,35 @@
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {useSelector} from 'react-redux';
import {getCurrentChannel} from '@mm-redux/selectors/entities/channels';
import {Theme} from '@mm-redux/types/theme';
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
import {disableChannelCalls, enableChannelCalls} from '@mmproducts/calls/store/actions/calls';
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
import Separator from '@screens/channel_info/separator';
import {preventDoubleTap} from '@utils/tap';
type Props = {
testID?: string;
testID: string;
theme: Theme;
onPress: (channelId: string) => void;
canEnableDisableCalls: boolean;
enabled: boolean;
}
const EnableDisableCalls = (props: Props) => {
const {testID, canEnableDisableCalls, theme, onPress, enabled} = props;
const EnableDisableCalls = ({testID, theme, enabled}: Props) => {
const currentChannel = useSelector(getCurrentChannel);
const [tryOnPress, msgPostfix] = useTryCallsFunction(onPress);
const handleEnableDisableCalls = useCallback(preventDoubleTap(tryOnPress), [tryOnPress]);
const toggleCalls = useCallback(() => {
if (enabled) {
disableChannelCalls(currentChannel.id);
} else {
enableChannelCalls(currentChannel.id);
}
}, [enabled, currentChannel.id]);
if (!canEnableDisableCalls) {
return null;
}
const [tryOnPress, msgPostfix] = useTryCallsFunction(toggleCalls);
const handleEnableDisableCalls = preventDoubleTap(tryOnPress);
return (
<>

View File

@@ -0,0 +1,61 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {injectIntl, intlShape, IntlShape} from 'react-intl';
import {useSelector} from 'react-redux';
import {getChannel, getCurrentChannel} from '@mm-redux/selectors/entities/channels';
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 ChannelInfoRow from '@screens/channel_info/channel_info_row';
import Separator from '@screens/channel_info/separator';
import {preventDoubleTap} from '@utils/tap';
type Props = {
testID: string;
theme: Theme;
intl: typeof IntlShape;
joinCall: (channelId: string, intl: typeof intlShape) => void;
}
const StartCall = ({testID, theme, intl, joinCall}: Props) => {
const currentChannel = useSelector(getCurrentChannel);
const call = useSelector(getCalls)[currentChannel.id];
const currentCall = useSelector(getCurrentCall);
const currentCallChannelId = currentCall?.channelId || '';
const callChannelName = useSelector((state: GlobalState) => getChannel(state, currentCallChannelId)?.display_name) || '';
const confirmToJoin = Boolean(currentCall && currentCall.channelId !== currentChannel.id);
const alreadyInTheCall = Boolean(currentCall && call && currentCall.channelId === call.channelId);
const ongoingCall = Boolean(call);
const leaveAndJoin = useCallback(() => {
leaveAndJoinWithAlert(intl, currentChannel.id, callChannelName, currentChannel.display_name, confirmToJoin, joinCall);
}, [intl, currentChannel.id, callChannelName, currentChannel.display_name, confirmToJoin, joinCall]);
const [tryLeaveAndJoin, msgPostfix] = useTryCallsFunction(leaveAndJoin);
const handleStartCall = useCallback(preventDoubleTap(tryLeaveAndJoin), [tryLeaveAndJoin]);
if (alreadyInTheCall) {
return null;
}
return (
<>
<Separator theme={theme}/>
<ChannelInfoRow
testID={testID}
action={handleStartCall}
defaultMessage={(ongoingCall ? 'Join Ongoing Call' : 'Start Call') + msgPostfix}
icon='phone-in-talk'
theme={theme}
rightArrow={false}
/>
</>
);
};
export default injectIntl(StartCall);

View File

@@ -1,39 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import Preferences from '@mm-redux/constants/preferences';
import EnableDisableCalls from './enable_disable_calls';
describe('EnableDisableCalls', () => {
const baseProps = {
testID: 'test-id',
theme: Preferences.THEMES.denim,
onPress: jest.fn(),
canEnableDisableCalls: true,
enabled: false,
};
test('should match snapshot if calls are disabled', () => {
const wrapper = shallow(<EnableDisableCalls {...baseProps}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should match snapshot if calls are enabled', () => {
const props = {...baseProps, enabled: true};
const wrapper = shallow(<EnableDisableCalls {...props}/>);
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should be null if you can not enable/disable calls', () => {
const props = {...baseProps, canEnableDisableCalls: false};
const wrapper = shallow(<EnableDisableCalls {...props}/>);
expect(wrapper.getElement()).toBeNull();
});
});

View File

@@ -1,13 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useState, useEffect} from 'react';
import {View, Platform} from 'react-native';
import React, {useState, useEffect, useMemo} from 'react';
import {View, Platform, StyleSheet} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import {ViewTypes} from '@constants';
import EventEmitter from '@mm-redux/utils/event_emitter';
import {makeStyleSheetFromTheme} from '@utils/theme';
const {
IOS_TOP_PORTRAIT,
@@ -15,46 +14,48 @@ const {
ANDROID_TOP_PORTRAIT,
} = ViewTypes;
const getStyleSheet = makeStyleSheetFromTheme(() => {
const insets = useSafeAreaInsets();
let topBarHeight = ANDROID_TOP_PORTRAIT;
if (Platform.OS === 'ios') {
topBarHeight = (IOS_TOP_PORTRAIT - STATUS_BAR_HEIGHT) + insets.top;
}
let topBarHeight = ANDROID_TOP_PORTRAIT;
if (Platform.OS === 'ios') {
topBarHeight = (IOS_TOP_PORTRAIT - STATUS_BAR_HEIGHT);
}
return {
wrapper: {
position: 'absolute',
top: topBarHeight,
width: '100%',
...Platform.select({
android: {
elevation: 9,
},
ios: {
zIndex: 9,
},
}),
},
withIndicatorBar: {
top: topBarHeight + ViewTypes.INDICATOR_BAR_HEIGHT,
},
};
const style = StyleSheet.create({
wrapper: {
position: 'absolute',
width: '100%',
...Platform.select({
android: {
elevation: 9,
},
ios: {
zIndex: 9,
},
}),
},
});
type Props = {
children: React.ReactNodeArray;
children: React.ReactChildren;
}
const FloatingCallContainer = (props: Props) => {
const style = getStyleSheet(props);
const insets = useSafeAreaInsets();
const [indicatorBarVisible, setIndicatorBarVisible] = useState(false);
useEffect(() => {
EventEmitter.on(ViewTypes.INDICATOR_BAR_VISIBLE, setIndicatorBarVisible);
return () => EventEmitter.off(ViewTypes.INDICATOR_BAR_VISIBLE, setIndicatorBarVisible);
}, []);
const wrapperTop = useMemo(() => ({
top: topBarHeight + insets.top,
}), [insets]);
const withIndicatorBar = useMemo(() => ({
top: wrapperTop.top + ViewTypes.INDICATOR_BAR_HEIGHT,
}), [wrapperTop]);
return (
<View style={[style.wrapper, indicatorBarVisible ? style.withIndicatorBar : undefined]}>
<View style={[style.wrapper, wrapperTop, indicatorBarVisible ? withIndicatorBar : undefined]}>
{props.children}
</View>
);

View File

@@ -18,7 +18,7 @@ import type {Call} from '@mmproducts/calls/store/types/calls';
type Props = {
actions: {
joinCall: (channelId: string) => any;
joinCall: (channelId: string, intl: typeof IntlShape) => void;
};
theme: Theme;
call: Call;

View File

@@ -4,7 +4,7 @@
import {IntlShape} from 'react-intl';
import {Alert} from 'react-native';
export default function leaveAndJoinWithAlert(intl: typeof IntlShape, channelId: string, callChannelName: string, currentChannelName: string, confirmToJoin: boolean, joinCall: (channelId: string) => void) {
export default function leaveAndJoinWithAlert(intl: typeof IntlShape, channelId: string, callChannelName: string, currentChannelName: string, confirmToJoin: boolean, joinCall: (channelId: string, intl: typeof IntlShape) => void) {
if (confirmToJoin) {
Alert.alert(
'Are you sure you want to switch to a different call?',
@@ -15,12 +15,12 @@ export default function leaveAndJoinWithAlert(intl: typeof IntlShape, channelId:
},
{
text: 'Leave & Join',
onPress: () => joinCall(channelId),
onPress: () => joinCall(channelId, intl),
style: 'cancel',
},
],
);
} else {
joinCall(channelId);
joinCall(channelId, intl);
}
}

View File

@@ -1,149 +0,0 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`StartCall should match snapshot 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Start Call"
icon="phone-in-talk"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;
exports[`StartCall should match snapshot when there is already an ongoing call in the channel 1`] = `
<React.Fragment>
<Separator
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
<channelInfoRow
action={[Function]}
defaultMessage="Join Ongoing Call"
icon="phone-in-talk"
rightArrow={false}
shouldRender={true}
testID="test-id"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
togglable={false}
/>
</React.Fragment>
`;

View File

@@ -1,34 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {connect} from 'react-redux';
import {bindActionCreators, Dispatch} from 'redux';
import {getChannel, getCurrentChannelId} from '@mm-redux/selectors/entities/channels';
import {joinCall} from '@mmproducts/calls/store/actions/calls';
import {getCalls, getCurrentCall} from '@mmproducts/calls/store/selectors/calls';
import StartCall from './start_call';
import type {GlobalState} from '@mm-redux/types/store';
function mapStateToProps(state: GlobalState) {
const currentChannelId = getCurrentChannelId(state);
const call = getCalls(state)[currentChannelId];
const currentCall = getCurrentCall(state);
return {
confirmToJoin: Boolean(currentCall && currentCall.channelId !== currentChannelId),
alreadyInTheCall: Boolean(currentCall && call && currentCall.channelId === call.channelId),
callChannelName: currentCall ? getChannel(state, currentCall.channelId)?.display_name : '',
ongoingCall: Boolean(call),
};
}
function mapDispatchToProps(dispatch: Dispatch) {
return {
actions: bindActionCreators({
joinCall,
}, dispatch),
};
}
export default connect(mapStateToProps, mapDispatchToProps)(StartCall);

View File

@@ -1,134 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import nock from 'nock';
import React from 'react';
import {Alert, TouchableHighlight} from 'react-native';
import {Client4} from '@client/rest';
import Preferences from '@mm-redux/constants/preferences';
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
import {shallowWithIntl} from '@test/intl-test-helper';
import TestHelper from '@test/test_helper';
import StartCall from './start_call';
describe('StartCall', () => {
beforeAll(async () => {
await TestHelper.initBasic(Client4);
});
afterAll(async () => {
await TestHelper.tearDown();
});
const baseProps = {
actions: {
joinCall: jest.fn(),
},
testID: 'test-id',
theme: Preferences.THEMES.denim,
currentChannelId: 'channel-id',
currentChannelName: 'Channel Name',
canStartCall: true,
callChannelName: 'Call channel name',
confirmToJoin: false,
alreadyInTheCall: false,
ongoingCall: false,
};
test('should match snapshot', () => {
const wrapper = shallowWithIntl(<StartCall {...baseProps}/>).dive();
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should match snapshot when there is already an ongoing call in the channel', () => {
const props = {...baseProps, ongoingCall: true};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
expect(wrapper.getElement()).toMatchSnapshot();
});
test('should be null when you are already in the channel call', () => {
const props = {...baseProps, alreadyInTheCall: true};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
expect(wrapper.getElement()).toBeNull();
});
test('should be null if you can not start a call', () => {
const props = {...baseProps, canStartCall: false};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
expect(wrapper.getElement()).toBeNull();
});
test('should start on click when calls is enabled', async () => {
nock(Client4.getCallsRoute()).
get('/version').
times(2).
reply(200, {version: 1, build: 2});
const joinCall = jest.fn();
const props = {...baseProps, actions: {joinCall}};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
// This is so that the awaited call within ClientCalls in products/calls/client/rest.ts has
// a chance to be completed by nock:
await Client4.doFetch(
`${Client4.getUrl()}/plugins/com.mattermost.calls/version`,
{method: 'get'},
);
expect(Alert.alert).not.toHaveBeenCalled();
expect(props.actions.joinCall).toHaveBeenCalled();
});
test('should not start on click and should show alert when calls is not enabled', async () => {
nock(Client4.getCallsRoute()).
get('/version').
times(2).
reply(404);
const joinCall = jest.fn();
const props = {...baseProps, actions: {joinCall}};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
// This is so that the awaited call within ClientCalls in products/calls/client/rest.ts has
// a chance to be completed by nock:
try {
await Client4.doFetch(
`${Client4.getUrl()}/plugins/com.mattermost.calls/version`,
{method: 'get'},
);
} catch (e) {
// expected
}
expect(Alert.alert).toHaveBeenCalled();
expect(props.actions.joinCall).not.toHaveBeenCalled();
});
test('should ask for confirmation on click', async () => {
nock(Client4.getCallsRoute()).
get('/version').
times(2).
reply(200, {version: 1, build: 2});
const joinCall = jest.fn();
const props = {...baseProps, confirmToJoin: true, actions: {joinCall}};
const wrapper = shallowWithIntl(<StartCall {...props}/>).dive();
wrapper.find(ChannelInfoRow).dive().find(TouchableHighlight).simulate('press');
// This is so that the awaited call within ClientCalls in products/calls/client/rest.ts has
// a chance to be completed by nock:
await Client4.doFetch(
`${Client4.getUrl()}/plugins/com.mattermost.calls/version`,
{method: 'get'},
);
expect(Alert.alert).toHaveBeenCalled();
expect(props.actions.joinCall).not.toHaveBeenCalled();
});
});

View File

@@ -1,62 +0,0 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React, {useCallback} from 'react';
import {injectIntl, IntlShape} from 'react-intl';
import {Theme} from '@mm-redux/types/theme';
import leaveAndJoinWithAlert from '@mmproducts/calls/components/leave_and_join_alert';
import {useTryCallsFunction} from '@mmproducts/calls/hooks';
import ChannelInfoRow from '@screens/channel_info/channel_info_row';
import Separator from '@screens/channel_info/separator';
import {preventDoubleTap} from '@utils/tap';
type Props = {
actions: {
joinCall: (channelId: string) => any;
};
testID?: string;
theme: Theme;
currentChannelId: string;
currentChannelName: string;
callChannelName: string;
confirmToJoin: boolean;
alreadyInTheCall: boolean;
canStartCall: boolean;
ongoingCall: boolean;
intl: typeof IntlShape;
}
const StartCall = (props: Props) => {
const {testID, canStartCall, theme, actions, currentChannelId, callChannelName, currentChannelName, confirmToJoin, alreadyInTheCall, ongoingCall, intl} = props;
const leaveAndJoin = useCallback(() => {
leaveAndJoinWithAlert(intl, currentChannelId, callChannelName, currentChannelName, confirmToJoin, actions.joinCall);
}, [intl, currentChannelId, callChannelName, currentChannelName, confirmToJoin, actions.joinCall]);
const [tryLeaveAndJoin, msgPostfix] = useTryCallsFunction(leaveAndJoin);
const handleStartCall = useCallback(preventDoubleTap(tryLeaveAndJoin), [tryLeaveAndJoin]);
if (!canStartCall) {
return null;
}
if (alreadyInTheCall) {
return null;
}
return (
<>
<Separator theme={theme}/>
<ChannelInfoRow
testID={testID}
action={handleStartCall}
defaultMessage={(ongoingCall ? 'Join Ongoing Call' : 'Start Call') + msgPostfix}
icon='phone-in-talk'
theme={theme}
rightArrow={false}
/>
</>
);
};
export default injectIntl(StartCall);

View File

@@ -21,7 +21,7 @@ export let client: any = null;
const websocketConnectTimeout = 3000;
export async function newClient(channelID: string, closeCb: () => void, setScreenShareURL: (url: string) => void) {
let peer: any = null;
let peer: Peer | null = null;
let stream: MediaStream;
let voiceTrackAdded = false;
let voiceTrack: MediaStreamTrack | null = null;
@@ -50,13 +50,15 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
streams.forEach((s) => {
s.getTracks().forEach((track: MediaStreamTrack) => {
track.stop();
track.release();
});
});
if (peer) {
peer.destroy();
}
InCallManager.stop();
peer?.destroy(undefined, undefined, () => {
// Wait until the peer connection is closed, which avoids the following racy error that can cause problems with accessing the audio system in the future:
// AVAudioSession_iOS.mm:1243 Deactivating an audio session that has running I/O. All I/O should be stopped or paused prior to deactivating the audio session.
InCallManager.stop();
});
if (closeCb) {
closeCb();
@@ -67,7 +69,7 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
if (!peer) {
return;
}
if (voiceTrackAdded) {
if (voiceTrackAdded && voiceTrack) {
peer.replaceTrack(voiceTrack, null, stream);
}
if (voiceTrack) {
@@ -160,7 +162,7 @@ export async function newClient(channelID: string, closeCb: () => void, setScree
ws.on('message', ({data}) => {
const msg = JSON.parse(data);
if (msg.type === 'answer' || msg.type === 'offer') {
peer.signal(data);
peer?.signal(data);
}
});

View File

@@ -1,10 +1,22 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {useState} from 'react';
import {useEffect, useState} from 'react';
import {Alert} from 'react-native';
import {useDispatch, useSelector} from 'react-redux';
import {Client4} from '@client/rest';
import {General} from '@mm-redux/constants';
import {getCurrentChannel} from '@mm-redux/selectors/entities/channels';
import {getCurrentUserRoles} from '@mm-redux/selectors/entities/users';
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin} from '@mm-redux/utils/user_utils';
import {loadConfig} from '@mmproducts/calls/store/actions/calls';
import {
getConfig,
isCallsExplicitlyDisabled,
isCallsExplicitlyEnabled,
isCallsPluginEnabled,
} from '@mmproducts/calls/store/selectors/calls';
// Check if calls is enabled. If it is, then run fn; if it isn't, show an alert and set
// msgPostfix to ' (Not Available)'.
@@ -33,3 +45,36 @@ export const useTryCallsFunction = (fn: (channelId: string) => void) => {
return [tryFn, msgPostfix];
};
export const useCallsChannelSettings = () => {
const dispatch = useDispatch();
const config = useSelector(getConfig);
const currentChannel = useSelector(getCurrentChannel);
const pluginEnabled = useSelector(isCallsPluginEnabled);
const explicitlyDisabled = useSelector(isCallsExplicitlyDisabled);
const explicitlyEnabled = useSelector(isCallsExplicitlyEnabled);
const roles = useSelector(getCurrentUserRoles);
useEffect(() => {
if (pluginEnabled) {
dispatch(loadConfig());
}
}, []);
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
const isGroupMessage = currentChannel.type === General.GM_CHANNEL;
const isAdmin = checkIsAdmin(roles);
const isChannelAdmin = isAdmin || checkIsChannelAdmin(roles);
const enabled = pluginEnabled && (explicitlyEnabled || (!explicitlyDisabled && config.DefaultEnabled));
let canEnableDisable;
if (!pluginEnabled) {
canEnableDisable = false;
} else if (config.AllowEnableCalls) {
canEnableDisable = isDirectMessage || isGroupMessage || isChannelAdmin;
} else {
canEnableDisable = isAdmin;
}
return [enabled, canEnableDisable];
};

View File

@@ -421,7 +421,12 @@ export default class Peer extends stream.Duplex {
this.isNegotiating = true;
}
_destroy(err: Error | null, cb: (error: Error | null) => void) {
destroy(err?: Error, cb?: (error: Error | null) => void, cbPCClose?: () => void): this {
this._destroy(err, cb, cbPCClose);
return this;
}
_destroy(err?: Error | null, cb?: (error: Error | null) => void, cbPcClose?: () => void) {
if (this.destroyed || this.destroying) {
return;
}
@@ -468,14 +473,14 @@ export default class Peer extends stream.Duplex {
} catch (err) {} // eslint-disable-line
// allow events concurrent with destruction to be handled
this.channel.onmessage = null;
this.channel.onopen = null;
this.channel.onclose = null;
this.channel.onerror = null;
this.channel.onmessage = undefined;
this.channel.onopen = undefined;
this.channel.onclose = undefined;
this.channel.onerror = undefined;
}
if (this.pc) {
try {
this.pc.close();
this.pc.close(cbPcClose);
} catch (err) {} // eslint-disable-line
// allow events concurrent with destruction to be handled
@@ -491,7 +496,7 @@ export default class Peer extends stream.Duplex {
this.emit('error', err);
}
this.emit('close');
cb(null);
cb?.(null);
}, 0);
}

View File

@@ -23,4 +23,6 @@ export default keyMirror({
RECEIVED_UNRAISE_HAND: null,
SET_SCREENSHARE_URL: null,
SET_SPEAKERPHONE: null,
RECEIVED_CONFIG: null,
RECEIVED_PLUGIN_ENABLED: null,
});

View File

@@ -3,10 +3,12 @@
import assert from 'assert';
import {IntlProvider} from 'react-intl';
import InCallManager from 'react-native-incall-manager';
import {Client4} from '@client/rest';
import configureStore from '@test/test_store';
import * as PermissionUtils from '@utils/permission';
import CallsTypes from '../action_types/calls';
@@ -31,6 +33,18 @@ jest.mock('@client/rest', () => ({
enabled: true,
},
]),
getCallsConfig: jest.fn(() => ({
ICEServers: ['mattermost.com'],
AllowEnableCalls: true,
DefaultEnabled: true,
last_retrieved_at: 1234,
})),
getPluginsManifests: jest.fn(() => (
[
{id: 'playbooks'},
{id: 'com.mattermost.calls'},
]
)),
enableChannelCalls: jest.fn(() => null),
disableChannelCalls: jest.fn(() => null),
},
@@ -67,11 +81,15 @@ describe('Actions.Calls', () => {
let store;
const {newClient} = require('@mmproducts/calls/connection');
InCallManager.setSpeakerphoneOn = jest.fn();
const intlProvider = new IntlProvider({locale: 'en'}, {});
const {intl} = intlProvider.getChildContext();
jest.spyOn(PermissionUtils, 'hasMicrophonePermission').mockReturnValue(true);
beforeEach(async () => {
newClient.mockClear();
Client4.setUrl.mockClear();
Client4.getCalls.mockClear();
Client4.getCallsConfig.mockClear();
Client4.enableChannelCalls.mockClear();
Client4.disableChannelCalls.mockClear();
store = await configureStore();
@@ -79,7 +97,7 @@ describe('Actions.Calls', () => {
it('joinCall', async () => {
await store.dispatch(addFakeCall('channel-id'));
const response = await store.dispatch(CallsActions.joinCall('channel-id'));
const response = await store.dispatch(CallsActions.joinCall('channel-id', intl));
const result = store.getState().entities.calls.joined;
assert.equal('channel-id', result);
assert.equal(response.data, 'channel-id');
@@ -92,7 +110,7 @@ describe('Actions.Calls', () => {
await store.dispatch(addFakeCall('channel-id'));
expect(CallsActions.ws).toBe(null);
await store.dispatch(CallsActions.joinCall('channel-id'));
await store.dispatch(CallsActions.joinCall('channel-id', intl));
let result = store.getState().entities.calls.joined;
assert.equal('channel-id', result);
@@ -108,7 +126,7 @@ describe('Actions.Calls', () => {
it('muteMyself', async () => {
await store.dispatch(addFakeCall('channel-id'));
await store.dispatch(CallsActions.joinCall('channel-id'));
await store.dispatch(CallsActions.joinCall('channel-id', intl));
await store.dispatch(CallsActions.muteMyself());
expect(CallsActions.ws.mute).toBeCalled();
await store.dispatch(CallsActions.leaveCall());
@@ -129,6 +147,28 @@ describe('Actions.Calls', () => {
assert.equal(store.getState().entities.calls.enabled['channel-1'], true);
});
it('loadConfig', async () => {
await store.dispatch(CallsActions.loadConfig());
expect(Client4.getCallsConfig).toBeCalledWith();
assert.equal(store.getState().entities.calls.config.DefaultEnabled, true);
assert.equal(store.getState().entities.calls.config.AllowEnableCalls, true);
});
it('batchLoadConfig', async () => {
await store.dispatch(CallsActions.batchLoadCalls());
expect(Client4.getPluginsManifests).toBeCalledWith();
expect(Client4.getCallsConfig).toBeCalledWith();
expect(Client4.getCalls).toBeCalledWith();
// For some reason the above await is not working. This helps us:
await store.dispatch(CallsActions.enableChannelCalls('channel-1'));
assert.equal(store.getState().entities.calls.config.DefaultEnabled, true);
assert.equal(store.getState().entities.calls.config.AllowEnableCalls, true);
assert.equal(store.getState().entities.calls.calls['channel-1'].channelId, 'channel-1');
assert.equal(store.getState().entities.calls.enabled['channel-1'], true);
});
it('enableChannelCalls', async () => {
assert.equal(store.getState().entities.calls.enabled['channel-1'], undefined);
await store.dispatch(CallsActions.enableChannelCalls('channel-1'));

View File

@@ -1,29 +1,67 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {intlShape} from 'react-intl';
import InCallManager from 'react-native-incall-manager';
import {batch} from 'react-redux';
import {Client4} from '@client/rest';
import Calls from '@constants/calls';
import {logError} from '@mm-redux/actions/errors';
import {forceLogoutIfNecessary} from '@mm-redux/actions/helpers';
import {GenericAction, ActionFunc, DispatchFunc, GetStateFunc} from '@mm-redux/types/actions';
import {
GenericAction,
ActionFunc,
DispatchFunc,
GetStateFunc,
ActionResult,
} from '@mm-redux/types/actions';
import {Dictionary} from '@mm-redux/types/utilities';
import {newClient} from '@mmproducts/calls/connection';
import CallsTypes from '@mmproducts/calls/store/action_types/calls';
import type {Call, CallParticipant} from '@mmproducts/calls/store/types/calls';
import {getConfig} from '@mmproducts/calls/store/selectors/calls';
import {Call, CallParticipant, DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
import {hasMicrophonePermission} from '@utils/permission';
export let ws: any = null;
export function loadConfig(force = false): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc): Promise<ActionResult> => {
if (!force) {
if ((Date.now() - getConfig(getState()).last_retrieved_at) < Calls.RefreshConfigMillis) {
return {} as GenericAction;
}
}
let data;
try {
data = await Client4.getCallsConfig();
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
// Reset the config to the default (off) since it looks like Calls is not enabled.
dispatch({
type: CallsTypes.RECEIVED_CONFIG,
data: {...DefaultServerConfig, last_retrieved_at: Date.now()},
});
}
data = {...data, last_retrieved_at: Date.now()};
dispatch({type: CallsTypes.RECEIVED_CONFIG, data});
return {data};
};
}
export function loadCalls(): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
return async (dispatch: DispatchFunc, getState: GetStateFunc): Promise<ActionResult> => {
let resp = [];
try {
resp = await Client4.getCalls();
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {error};
return {};
}
const callsResults: Dictionary<Call> = {};
@@ -55,11 +93,45 @@ export function loadCalls(): ActionFunc {
};
dispatch({type: CallsTypes.RECEIVED_CALLS, data});
return {data};
};
}
export function batchLoadCalls(forceConfig = false): ActionFunc {
return async (dispatch: DispatchFunc) => {
const res = await dispatch(checkIsCallsPluginEnabled());
if (!res.data) {
// Calls is not enabled.
return {};
}
batch(() => {
dispatch(loadConfig(forceConfig));
dispatch(loadCalls());
});
return {};
};
}
export function checkIsCallsPluginEnabled(): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
let data;
try {
data = await Client4.getPluginsManifests();
} catch (error) {
forceLogoutIfNecessary(error, dispatch, getState);
dispatch(logError(error));
return {} as GenericAction;
}
const enabled = data.findIndex((m) => m.id === Calls.PluginId) !== -1;
dispatch({type: CallsTypes.RECEIVED_PLUGIN_ENABLED, data: enabled});
return {data: enabled};
};
}
export function enableChannelCalls(channelId: string): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
try {
@@ -92,8 +164,17 @@ export function disableChannelCalls(channelId: string): ActionFunc {
};
}
export function joinCall(channelId: string): ActionFunc {
export function joinCall(channelId: string, intl: typeof intlShape): ActionFunc {
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
// Edge case: calls was disabled when app loaded, and then enabled, but app hasn't
// reconnected its websocket since then (i.e., hasn't called batchLoadCalls yet)
dispatch(checkIsCallsPluginEnabled());
const hasPermission = await hasMicrophonePermission(intl);
if (!hasPermission) {
return {error: 'no permissions to microphone, unable to start call'};
}
const setScreenShareURL = (url: string) => {
dispatch({
type: CallsTypes.SET_SCREENSHARE_URL,

View File

@@ -4,6 +4,7 @@
import assert from 'assert';
import CallsTypes from '@mmproducts/calls/store/action_types/calls';
import {DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
import callsReducer from './calls';
@@ -390,3 +391,20 @@ describe('Reducers.calls.screenShareURL', () => {
assert.deepEqual(state.screenShareURL, 'new-url');
});
});
describe('Reducers.calls.config', () => {
it('RECEIVED_CONFIG', async () => {
const initialState = {config: DefaultServerConfig};
const testAction = {
type: CallsTypes.RECEIVED_CONFIG,
data: {
ICEServers: ['google.com'],
AllowEnableCalls: true,
DefaultEnabled: true,
last_retrieved_at: 123,
},
};
const state = callsReducer(initialState, testAction);
assert.deepEqual(state.config, testAction.data);
});
});

View File

@@ -5,7 +5,7 @@ import {combineReducers} from 'redux';
import {GenericAction} from '@mm-redux/types/actions';
import {Dictionary} from '@mm-redux/types/utilities';
import CallsTypes from '@mmproducts/calls/store/action_types/calls';
import {Call} from '@mmproducts/calls/store/types/calls';
import {Call, DefaultServerConfig} from '@mmproducts/calls/store/types/calls';
function calls(state: Dictionary<Call> = {}, action: GenericAction) {
switch (action.type) {
@@ -147,6 +147,16 @@ function calls(state: Dictionary<Call> = {}, action: GenericAction) {
}
}
function config(state = DefaultServerConfig, action: GenericAction) {
switch (action.type) {
case CallsTypes.RECEIVED_CONFIG: {
return action.data;
}
default:
return state;
}
}
function joined(state = '', action: GenericAction) {
switch (action.type) {
case CallsTypes.RECEIVED_MYSELF_JOINED_CALL: {
@@ -206,10 +216,22 @@ function speakerphoneOn(state = false, action: GenericAction) {
}
}
function pluginEnabled(state = false, action: GenericAction) {
switch (action.type) {
case CallsTypes.RECEIVED_PLUGIN_ENABLED: {
return action.data;
}
default:
return state;
}
}
export default combineReducers({
calls,
enabled,
joined,
screenShareURL,
speakerphoneOn,
config,
pluginEnabled,
});

View File

@@ -55,7 +55,7 @@ describe('Selectors.Calls', () => {
});
it('isCallsEnabled', () => {
assert.equal(Selectors.isCallsEnabled(testState), true);
assert.equal(Selectors.isCallsExplicitlyEnabled(testState), true);
let newState = {
...testState,
entities: {
@@ -63,7 +63,7 @@ describe('Selectors.Calls', () => {
channels: {currentChannelId: 'channel-2'},
},
};
assert.equal(Selectors.isCallsEnabled(newState), false);
assert.equal(Selectors.isCallsExplicitlyEnabled(newState), false);
newState = {
...testState,
entities: {
@@ -71,7 +71,7 @@ describe('Selectors.Calls', () => {
channels: {currentChannelId: 'not-valid-channel'},
},
};
assert.equal(Selectors.isCallsEnabled(newState), false);
assert.equal(Selectors.isCallsExplicitlyEnabled(newState), false);
});
it('getScreenShareURL', () => {

View File

@@ -8,6 +8,10 @@ import {getServerVersion} from '@mm-redux/selectors/entities/general';
import {GlobalState} from '@mm-redux/types/store';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
export function getConfig(state: GlobalState) {
return state.entities.calls.config;
}
export function getCalls(state: GlobalState) {
return state.entities.calls.calls;
}
@@ -20,8 +24,20 @@ export function getCurrentCall(state: GlobalState) {
return state.entities.calls.calls[currentCall];
}
export function isCallsEnabled(state: GlobalState) {
return Boolean(state.entities.calls.enabled[getCurrentChannelId(state)]);
// isCallsExplicitlyEnabled returns true if and only if calls has been explicitly enabled in the current channel
// Both this and isCallsExplicitlyDisabled can be false if the channel has never had a call in it.
export function isCallsExplicitlyEnabled(state: GlobalState) {
const currentChannelId = getCurrentChannelId(state);
const enabledDict = state.entities.calls.enabled;
return enabledDict.hasOwnProperty(currentChannelId) && enabledDict[currentChannelId];
}
// isCallsExplicitlyEnabled returns true if and only if calls has been explicitly disabled in the current channel
// Both this and isCallsExplicitlyEnabled can be false if the channel has never had a call in it.
export function isCallsExplicitlyDisabled(state: GlobalState) {
const currentChannelId = getCurrentChannelId(state);
const enabledDict = state.entities.calls.enabled;
return enabledDict.hasOwnProperty(currentChannelId) && !enabledDict[currentChannelId];
}
export function getScreenShareURL(state: GlobalState) {
@@ -45,3 +61,7 @@ export function isSupportedServer(state: GlobalState) {
return false;
}
export function isCallsPluginEnabled(state: GlobalState) {
return state.entities.calls.pluginEnabled;
}

View File

@@ -10,6 +10,8 @@ export type CallsState = {
joined: string;
screenShareURL: string;
speakerphoneOn: boolean;
config: ServerConfig;
pluginEnabled: boolean;
}
export type Call = {
@@ -53,3 +55,17 @@ export type VoiceEventData = {
channelId: string;
userId: string;
}
export type ServerConfig = {
ICEServers: string[];
AllowEnableCalls: boolean;
DefaultEnabled: boolean;
last_retrieved_at: number;
}
export const DefaultServerConfig = {
ICEServers: [],
AllowEnableCalls: false,
DefaultEnabled: false,
last_retrieved_at: 0,
} as ServerConfig;

View File

@@ -2,13 +2,14 @@
// See LICENSE.txt for license information.
import {EventEmitter} from 'events';
import Calls from '@constants/calls';
import {encode} from '@msgpack/msgpack/dist';
export default class WebSocketClient extends EventEmitter {
private ws: WebSocket | null;
private seqNo = 0;
private connID = '';
private eventPrefix = 'custom_com.mattermost.calls';
private eventPrefix = `custom_${Calls.PluginId}`;
constructor(connURL: string) {
super();

View File

@@ -64,7 +64,7 @@ export default class ChannelAndroid extends ChannelBase {
}
render() {
const {theme, viewingGlobalThreads, isSupportedServerCalls} = this.props;
const {theme, viewingGlobalThreads, isCallsEnabled} = this.props;
let component;
if (viewingGlobalThreads) {
@@ -106,11 +106,12 @@ export default class ChannelAndroid extends ChannelBase {
{component}
<NetworkIndicator/>
<AnnouncementBanner/>
{isSupportedServerCalls &&
{isCallsEnabled &&
<FloatingCallContainer>
<JoinCall/>
<CurrentCall/>
</FloatingCallContainer>}
</FloatingCallContainer>
}
</>
);

View File

@@ -57,7 +57,7 @@ export default class ChannelIOS extends ChannelBase {
};
render() {
const {currentChannelId, theme, viewingGlobalThreads, isSupportedServerCalls} = this.props;
const {currentChannelId, theme, viewingGlobalThreads, isCallsEnabled} = this.props;
let component;
let renderDraftArea = false;
@@ -85,11 +85,12 @@ export default class ChannelIOS extends ChannelBase {
<>
<AnnouncementBanner/>
<NetworkIndicator/>
{isSupportedServerCalls &&
{isCallsEnabled &&
<FloatingCallContainer>
<JoinCall/>
<CurrentCall/>
</FloatingCallContainer>}
</FloatingCallContainer>
}
</>
);
const header = (

View File

@@ -24,7 +24,7 @@ export default class ChannelBase extends PureComponent {
loadChannelsForTeam: PropTypes.func.isRequired,
selectDefaultTeam: PropTypes.func.isRequired,
selectInitialChannel: PropTypes.func.isRequired,
loadCalls: PropTypes.func.isRequired,
batchLoadCalls: PropTypes.func.isRequired,
}).isRequired,
componentId: PropTypes.string.isRequired,
currentChannelId: PropTypes.string,
@@ -40,6 +40,7 @@ export default class ChannelBase extends PureComponent {
viewingGlobalThreads: PropTypes.bool,
collapsedThreadsEnabled: PropTypes.bool.isRequired,
isSupportedServerCalls: PropTypes.bool.isRequired,
isCallsEnabled: PropTypes.bool.isRequired,
selectedPost: PropTypes.shape({
id: PropTypes.string.isRequired,
channel_id: PropTypes.string.isRequired,
@@ -106,7 +107,7 @@ export default class ChannelBase extends PureComponent {
}
if (this.props.isSupportedServerCalls) {
this.props.actions.loadCalls();
this.props.actions.batchLoadCalls();
}
}

View File

@@ -17,8 +17,11 @@ import {getCurrentTeam} from '@mm-redux/selectors/entities/teams';
import {getCurrentUserId, getCurrentUserRoles, shouldShowTermsOfService} from '@mm-redux/selectors/entities/users';
import {isMinimumServerVersion} from '@mm-redux/utils/helpers';
import {isSystemAdmin as checkIsSystemAdmin} from '@mm-redux/utils/user_utils';
import {loadCalls} from '@mmproducts/calls/store/actions/calls';
import {isSupportedServer as isSupportedServerForCalls} from '@mmproducts/calls/store/selectors/calls';
import {batchLoadCalls} from '@mmproducts/calls/store/actions/calls';
import {
isCallsPluginEnabled,
isSupportedServer as isSupportedServerForCalls,
} from '@mmproducts/calls/store/selectors/calls';
import {getViewingGlobalThreads} from '@selectors/threads';
import Channel from './channel';
@@ -44,6 +47,7 @@ function mapStateToProps(state) {
const currentChannelId = currentTeam?.delete_at === 0 ? getCurrentChannelId(state) : '';
const collapsedThreadsEnabled = isCollapsedThreadsEnabled(state);
const isSupportedServerCalls = isSupportedServerForCalls(state);
const isCallsEnabled = isCallsPluginEnabled(state);
return {
currentChannelId,
@@ -58,6 +62,7 @@ function mapStateToProps(state) {
theme: getTheme(state),
viewingGlobalThreads: collapsedThreadsEnabled && getViewingGlobalThreads(state),
isSupportedServerCalls,
isCallsEnabled,
};
}
@@ -68,7 +73,7 @@ function mapDispatchToProps(dispatch) {
loadChannelsForTeam,
selectDefaultTeam,
selectInitialChannel,
loadCalls,
batchLoadCalls,
}, dispatch),
};
}

View File

@@ -1117,79 +1117,38 @@ exports[`channelInfo should match snapshot on calls supported and calls disabled
}
}
/>
<React.Fragment>
<Connect(InjectIntl(StartCall))
canStartCall={false}
currentChannelId="1234"
currentChannelName="Channel Name"
joinCall={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
<CallsChannelInfo
joinCall={[Function]}
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
/>
<EnableDisableCalls
canEnableDisableCalls={true}
enabled={false}
onPress={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
</React.Fragment>
}
/>
<Connect(InjectIntl(Component))
theme={
Object {
@@ -1804,79 +1763,38 @@ exports[`channelInfo should match snapshot on calls supported and calls enabled
}
}
/>
<React.Fragment>
<Connect(InjectIntl(StartCall))
canStartCall={true}
currentChannelId="1234"
currentChannelName="Channel Name"
joinCall={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
<CallsChannelInfo
joinCall={[Function]}
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
/>
<EnableDisableCalls
canEnableDisableCalls={true}
enabled={true}
onPress={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
</React.Fragment>
}
/>
<Connect(InjectIntl(Component))
theme={
Object {
@@ -2491,79 +2409,38 @@ exports[`channelInfo should match snapshot on calls supported, user is not admin
}
}
/>
<React.Fragment>
<Connect(InjectIntl(StartCall))
canStartCall={false}
currentChannelId="1234"
currentChannelName="Channel Name"
joinCall={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
<CallsChannelInfo
joinCall={[Function]}
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
/>
<EnableDisableCalls
canEnableDisableCalls={false}
enabled={false}
onPress={[Function]}
testID="channel_info.start_call.action"
theme={
Object {
"awayIndicator": "#ffbc1f",
"buttonBg": "#1c58d9",
"buttonColor": "#ffffff",
"centerChannelBg": "#ffffff",
"centerChannelColor": "#3f4350",
"codeTheme": "github",
"dndIndicator": "#d24b4e",
"errorTextColor": "#d24b4e",
"linkColor": "#386fe5",
"mentionBg": "#ffffff",
"mentionColor": "#1e325c",
"mentionHighlightBg": "#ffd470",
"mentionHighlightLink": "#1b1d22",
"newMessageSeparator": "#cc8f00",
"onlineIndicator": "#3db887",
"sidebarBg": "#1e325c",
"sidebarHeaderBg": "#192a4d",
"sidebarHeaderTextColor": "#ffffff",
"sidebarTeamBarBg": "#14213e",
"sidebarText": "#ffffff",
"sidebarTextActiveBorder": "#5d89ea",
"sidebarTextActiveColor": "#ffffff",
"sidebarTextHoverBg": "#28427b",
"sidebarUnreadText": "#ffffff",
"type": "Denim",
}
}
/>
</React.Fragment>
}
/>
<Connect(InjectIntl(Component))
theme={
Object {

View File

@@ -13,8 +13,7 @@ import {SafeAreaView} from 'react-native-safe-area-context';
import {dismissModal} from '@actions/navigation';
import StatusBar from '@components/status_bar';
import EnableDisableCalls from '@mmproducts/calls/components/enable_disable_calls';
import StartCall from '@mmproducts/calls/components/start_call';
import CallsChannelInfo from '@mmproducts/calls/components/channel_info/calls_channel_info';
import {alertErrorWithFallback} from '@utils/general';
import {t} from '@utils/i18n';
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
@@ -51,15 +50,12 @@ export default class ChannelInfo extends PureComponent {
currentUserId: PropTypes.string,
isTeammateGuest: PropTypes.bool.isRequired,
isDirectMessage: PropTypes.bool.isRequired,
isGroupMessage: PropTypes.bool.isRequired,
teammateId: PropTypes.string,
theme: PropTypes.object.isRequired,
customStatus: PropTypes.object,
isCustomStatusEnabled: PropTypes.bool.isRequired,
isCustomStatusExpired: PropTypes.bool.isRequired,
isCustomStatusExpirySupported: PropTypes.bool.isRequired,
isCallsEnabled: PropTypes.bool.isRequired,
isChannelAdmin: PropTypes.bool.isRequired,
isSupportedServerCalls: PropTypes.bool.isRequired,
};
@@ -93,19 +89,11 @@ export default class ChannelInfo extends PureComponent {
dismissModal();
};
startCallHandler = (channelId) => {
this.props.actions.joinCall(channelId);
joinCallHandler = (channelId, intl) => {
this.props.actions.joinCall(channelId, intl);
this.close();
};
toggleCalls = () => {
if (this.props.isCallsEnabled) {
this.props.actions.disableChannelCalls(this.props.currentChannel.id);
} else {
this.props.actions.enableChannelCalls(this.props.currentChannel.id);
}
};
permalinkBadTeam = () => {
const {intl} = this.context;
const message = {
@@ -117,7 +105,7 @@ export default class ChannelInfo extends PureComponent {
};
actionsRows = (channelIsArchived) => {
const {currentChannel, currentUserId, isDirectMessage, isGroupMessage, theme, isCallsEnabled, isSupportedServerCalls, isChannelAdmin} = this.props;
const {currentChannel, currentUserId, isDirectMessage, theme, isSupportedServerCalls} = this.props;
if (channelIsArchived) {
return (
@@ -179,23 +167,11 @@ export default class ChannelInfo extends PureComponent {
theme={theme}
/>
{isSupportedServerCalls &&
<>
<StartCall
testID='channel_info.start_call.action'
theme={theme}
currentChannelId={currentChannel.id}
currentChannelName={currentChannel.display_name}
joinCall={this.startCallHandler}
canStartCall={isCallsEnabled}
/>
<EnableDisableCalls
testID='channel_info.start_call.action'
theme={theme}
onPress={this.toggleCalls}
canEnableDisableCalls={isDirectMessage || isGroupMessage || isChannelAdmin}
enabled={isCallsEnabled}
/>
</>}
<CallsChannelInfo
theme={theme}
joinCall={this.joinCallHandler}
/>
}
<Bindings
theme={theme}
/>

View File

@@ -10,11 +10,11 @@ import {getCustomEmojisInText} from '@mm-redux/actions/emojis';
import {General} from '@mm-redux/constants';
import {getCurrentChannel, getCurrentChannelStats} from '@mm-redux/selectors/entities/channels';
import {getTeammateNameDisplaySetting, getTheme} from '@mm-redux/selectors/entities/preferences';
import {getCurrentUserRoles, getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
import {getCurrentUserId, getUser} from '@mm-redux/selectors/entities/users';
import {getUserIdFromChannelName} from '@mm-redux/utils/channel_utils';
import {isAdmin as checkIsAdmin, isChannelAdmin as checkIsChannelAdmin, displayUsername} from '@mm-redux/utils/user_utils';
import {displayUsername} from '@mm-redux/utils/user_utils';
import {joinCall, enableChannelCalls, disableChannelCalls} from '@mmproducts/calls/store/actions/calls';
import {isCallsEnabled, isSupportedServer} from '@mmproducts/calls/store/selectors/calls';
import {isSupportedServer} from '@mmproducts/calls/store/selectors/calls';
import {makeGetCustomStatus, isCustomStatusEnabled, isCustomStatusExpired, isCustomStatusExpirySupported} from '@selectors/custom_status';
import {isGuest} from '@utils/users';
@@ -38,8 +38,6 @@ function makeMapStateToProps() {
let customStatus;
let customStatusExpired = true;
let customStatusExpirySupported = false;
const roles = getCurrentUserRoles(state) || '';
const isChannelAdmin = checkIsAdmin(roles) || checkIsChannelAdmin(roles);
const isDirectMessage = currentChannel.type === General.DM_CHANNEL;
if (isDirectMessage) {
@@ -70,16 +68,13 @@ function makeMapStateToProps() {
currentUserId,
isTeammateGuest,
isDirectMessage,
isGroupMessage,
teammateId,
theme: getTheme(state),
customStatus,
isCustomStatusEnabled: customStatusEnabled,
isCustomStatusExpired: customStatusExpired,
isCustomStatusExpirySupported: customStatusExpirySupported,
isCallsEnabled: isCallsEnabled(state),
isSupportedServerCalls,
isChannelAdmin,
};
};
}

View File

@@ -259,7 +259,7 @@ export default class SelectServer extends PureComponent {
screen = 'SSO';
title = formatMessage({id: 'mobile.routes.sso', defaultMessage: 'Single Sign-On'});
props = {ssoType: enabledSSOs[0]};
} else if (hasLoginForm && numberSSOs > 0) {
} else if ((hasLoginForm && numberSSOs > 0) || numberSSOs > 1) {
screen = 'LoginOptions';
title = formatMessage({id: 'mobile.routes.loginOptions', defaultMessage: 'Login Chooser'});
} else {

View File

@@ -65,6 +65,21 @@ const getStoragePermissionDeniedMessage = (intl: typeof intlShape) => {
};
};
const getMicrophonePermissionDeniedMessage = (intl: typeof intlShape) => {
const {formatMessage} = intl;
const applicationName = DeviceInfo.getApplicationName();
return {
title: formatMessage({
id: 'mobile.microphone_permission_denied_title',
defaultMessage: '{applicationName} would like to access your microphone',
}, {applicationName}),
text: formatMessage({
id: 'mobile.microphone_permission_denied_description',
defaultMessage: 'To participate in this call, open Settings to grant Mattermost access to your microphone.',
}),
};
};
export const hasCameraPermission = async (intl: typeof intlShape) => {
const {formatMessage} = intl;
const targetSource = Platform.OS === 'ios' ? Permissions.PERMISSIONS.IOS.CAMERA : Permissions.PERMISSIONS.ANDROID.CAMERA;
@@ -108,6 +123,49 @@ export const hasCameraPermission = async (intl: typeof intlShape) => {
return true;
};
export const hasMicrophonePermission = async (intl: typeof intlShape) => {
const {formatMessage} = intl;
const targetSource = Platform.OS === 'ios' ? Permissions.PERMISSIONS.IOS.MICROPHONE : Permissions.PERMISSIONS.ANDROID.RECORD_AUDIO;
const hasPermission = await Permissions.check(targetSource);
switch (hasPermission) {
case Permissions.RESULTS.DENIED:
case Permissions.RESULTS.UNAVAILABLE: {
const permissionRequest = await Permissions.request(targetSource);
return permissionRequest === Permissions.RESULTS.GRANTED;
}
case Permissions.RESULTS.BLOCKED: {
const grantOption = {
text: formatMessage({
id: 'mobile.permission_denied_retry',
defaultMessage: 'Settings',
}),
onPress: () => Permissions.openSettings(),
};
const {title, text} = getMicrophonePermissionDeniedMessage(intl);
Alert.alert(
title,
text,
[
grantOption,
{
text: formatMessage({
id: 'mobile.permission_denied_dismiss',
defaultMessage: 'Don\'t Allow',
}),
},
],
);
return false;
}
}
return true;
};
export const hasPhotoPermission = async (intl: typeof intlShape) => {
const {formatMessage} = intl;
const targetSource = Platform.OS === 'ios' ? Permissions.PERMISSIONS.IOS.PHOTO_LIBRARY : Permissions.PERMISSIONS.ANDROID.READ_EXTERNAL_STORAGE;

View File

@@ -415,6 +415,8 @@
"mobile.message_length.message": "Your current message is too long. Current character count: {count}/{max}",
"mobile.message_length.message_split_left": "Message exceeds the character limit",
"mobile.message_length.title": "Message Length",
"mobile.microphone_permission_denied_description": "To participate in this call, open Settings to grant Mattermost access to your microphone.",
"mobile.microphone_permission_denied_title": "{applicationName} would like to access your microphone",
"mobile.more_dms.add_more": "You can add {remaining, number} more users",
"mobile.more_dms.cannot_add_more": "You cannot add more users",
"mobile.more_dms.one_more": "You can add 1 more user",

View File

@@ -6546,9 +6546,9 @@
}
},
"node_modules/minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"node_modules/mkdirp": {
@@ -13383,9 +13383,9 @@
}
},
"minimist": {
"version": "1.2.5",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
"integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==",
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==",
"dev": true
},
"mkdirp": {

View File

@@ -8,20 +8,20 @@ GEM
artifactory (3.0.15)
atomos (0.1.3)
aws-eventstream (1.2.0)
aws-partitions (1.568.0)
aws-sdk-core (3.130.0)
aws-partitions (1.579.0)
aws-sdk-core (3.130.1)
aws-eventstream (~> 1, >= 1.0.2)
aws-partitions (~> 1, >= 1.525.0)
aws-sigv4 (~> 1.1)
jmespath (~> 1.0)
aws-sdk-kms (1.55.0)
aws-sdk-kms (1.56.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sigv4 (~> 1.1)
aws-sdk-s3 (1.113.0)
aws-sdk-core (~> 3, >= 3.127.0)
aws-sdk-kms (~> 1)
aws-sigv4 (~> 1.4)
aws-sigv4 (1.4.0)
aws-sigv4 (1.5.0)
aws-eventstream (~> 1, >= 1.0.2)
babosa (1.0.4)
claide (1.1.0)
@@ -36,7 +36,7 @@ GEM
unf (>= 0.0.5, < 1.0.0)
dotenv (2.7.6)
emoji_regex (3.2.3)
excon (0.92.0)
excon (0.92.2)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
@@ -66,7 +66,7 @@ GEM
faraday_middleware (1.2.0)
faraday (~> 1.0)
fastimage (2.2.6)
fastlane (2.205.0)
fastlane (2.205.2)
CFPropertyList (>= 2.3, < 4.0.0)
addressable (>= 2.8, < 3.0.0)
artifactory (~> 3.0)
@@ -111,7 +111,7 @@ GEM
fastlane-plugin-find_replace_string (0.1.0)
fastlane-plugin-versioning_android (0.1.0)
gh_inspector (1.1.3)
google-apis-androidpublisher_v3 (0.16.0)
google-apis-androidpublisher_v3 (0.19.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-core (0.4.2)
addressable (~> 2.5, >= 2.5.1)
@@ -126,15 +126,15 @@ GEM
google-apis-core (>= 0.4, < 2.a)
google-apis-playcustomapp_v1 (0.7.0)
google-apis-core (>= 0.4, < 2.a)
google-apis-storage_v1 (0.11.0)
google-apis-storage_v1 (0.12.0)
google-apis-core (>= 0.4, < 2.a)
google-cloud-core (1.6.0)
google-cloud-env (~> 1.0)
google-cloud-errors (~> 1.0)
google-cloud-env (1.5.0)
faraday (>= 0.17.3, < 2.0)
google-cloud-env (1.6.0)
faraday (>= 0.17.3, < 3.0)
google-cloud-errors (1.2.0)
google-cloud-storage (1.36.1)
google-cloud-storage (1.36.2)
addressable (~> 2.8)
digest-crc (~> 0.4)
google-apis-iamcredentials_v1 (~> 0.1)
@@ -142,7 +142,7 @@ GEM
google-cloud-core (~> 1.6)
googleauth (>= 0.16.2, < 2.a)
mini_mime (~> 1.0)
googleauth (1.1.2)
googleauth (1.1.3)
faraday (>= 0.17.3, < 3.a)
jwt (>= 1.4, < 3.0)
memoist (~> 0.16)

View File

@@ -909,7 +909,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 388;
CURRENT_PROJECT_VERSION = 397;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -951,7 +951,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
CURRENT_PROJECT_VERSION = 388;
CURRENT_PROJECT_VERSION = 397;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;

View File

@@ -21,7 +21,7 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.50.1</string>
<string>1.51.2</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleURLTypes</key>
@@ -37,7 +37,7 @@
</dict>
</array>
<key>CFBundleVersion</key>
<string>388</string>
<string>397</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<key>LSRequiresIPhoneOS</key>

View File

@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.50.1</string>
<string>1.51.2</string>
<key>CFBundleVersion</key>
<string>388</string>
<string>397</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>

View File

@@ -19,9 +19,9 @@
<key>CFBundlePackageType</key>
<string>XPC!</string>
<key>CFBundleShortVersionString</key>
<string>1.50.1</string>
<string>1.51.2</string>
<key>CFBundleVersion</key>
<string>388</string>
<string>397</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionPointIdentifier</key>

View File

@@ -348,7 +348,7 @@ PODS:
- ReactNativeExceptionHandler (2.10.10):
- React-Core
- ReactNativeIncallManager (3.3.0):
- React
- React-Core
- ReactNativeKeyboardTrackingView (5.7.0):
- React
- ReactNativeNavigation (7.25.1):
@@ -781,7 +781,7 @@ SPEC CHECKSUMS:
React-runtimeexecutor: 2450b43df7ffe8e805a0b3dcb2abd4282f1f1836
ReactCommon: d98c6c96b567f9b3a15f9fd4cc302c1eda8e3cf2
ReactNativeExceptionHandler: b11ff67c78802b2f62eed0e10e75cb1ef7947c60
ReactNativeIncallManager: a840ef75640518065742a893e6c6ff98ee820039
ReactNativeIncallManager: 642c22630caadff0a0619413aff4a9da08d63df9
ReactNativeKeyboardTrackingView: 02137fac3b2ebd330d74fa54ead48b14750a2306
ReactNativeNavigation: 6e747bdf88f138088a105285274170b3fc0404ed
rn-fetch-blob: 17961aec08caae68bb8fc0e5b40f93b3acfa6932

201
package-lock.json generated
View File

@@ -1,12 +1,11 @@
{
"name": "mattermost-mobile",
"version": "1.50.1",
"version": "1.51.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "mattermost-mobile",
"version": "1.50.1",
"version": "1.51.0",
"hasInstallScript": true,
"license": "Apache 2.0",
"dependencies": {
@@ -28,7 +27,7 @@
"array.prototype.flat": "1.2.5",
"base-64": "1.0.0",
"buffer": "6.0.3",
"commonmark": "github:mattermost/commonmark.js#90a62d97ed2dbd2d4711a5adda327128f5827983",
"commonmark": "github:mattermost/commonmark.js#d1003be97d15414af6c21894125623c45e3f5096",
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#4e52e1725c0ef5b1e2ecfe9883220ec36c2eb67d",
"deep-equal": "2.0.5",
"deepmerge": "4.2.2",
@@ -58,7 +57,7 @@
"react-native-haptic-feedback": "1.13.0",
"react-native-hw-keyboard-event": "0.0.4",
"react-native-image-picker": "4.7.3",
"react-native-incall-manager": "3.3.0",
"react-native-incall-manager": "github:cpoile/react-native-incall-manager",
"react-native-keyboard-aware-scrollview": "2.1.0",
"react-native-keyboard-tracking-view": "5.7.0",
"react-native-keychain": "8.0.0",
@@ -82,7 +81,7 @@
"react-native-svg": "12.1.1",
"react-native-vector-icons": "9.0.0",
"react-native-video": "5.2.0",
"react-native-webrtc": "github:streamer45/react-native-webrtc",
"react-native-webrtc": "github:mattermost/react-native-webrtc",
"react-native-webview": "11.17.1",
"react-native-youtube": "2.0.2",
"react-redux": "7.2.6",
@@ -6476,9 +6475,9 @@
}
},
"node_modules/bplist-parser": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.0.tgz",
"integrity": "sha512-zgmaRvT6AN1JpPPV+S0a1/FAtoxSreYDccZGIqEMSvZl9DMe70mJ7MFzpxa1X+gHVdkToE2haRUHHMiW1OdejA==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz",
"integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==",
"dependencies": {
"big-integer": "1.6.x"
},
@@ -7212,7 +7211,8 @@
},
"node_modules/commonmark": {
"version": "0.30.0",
"resolved": "git+ssh://git@github.com/mattermost/commonmark.js.git#90a62d97ed2dbd2d4711a5adda327128f5827983",
"resolved": "git+ssh://git@github.com/mattermost/commonmark.js.git#d1003be97d15414af6c21894125623c45e3f5096",
"integrity": "sha512-80VOWbVPw7LzPokYFKLVDsB9xdbfb3o4vBE0fPPronTJX84vDI37iqeud20Th6r4DZtghRkR9AJjBhYmC2jlsg==",
"license": "BSD-2-Clause",
"dependencies": {
"entities": "~3.0.1",
@@ -19400,8 +19400,8 @@
},
"node_modules/react-native-incall-manager": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/react-native-incall-manager/-/react-native-incall-manager-3.3.0.tgz",
"integrity": "sha512-SvomgHUoKqVso/BGv02b4ndBqenlqYqW3pptZz5qHwteidKFNWI6ny+PPw5X55MdK0lyTbzBoiT3DwS06Qu0lg==",
"resolved": "git+ssh://git@github.com/cpoile/react-native-incall-manager.git#8c55b9dac0a2ab25d651fb54b504d384f9989b36",
"license": "ISC",
"peerDependencies": {
"react-native": ">=0.40.0"
}
@@ -19746,7 +19746,7 @@
},
"node_modules/react-native-webrtc": {
"version": "1.75.3",
"resolved": "git+ssh://git@github.com/streamer45/react-native-webrtc.git#7fe7d434892e6b29c5a6086d30b10f482db1e592",
"resolved": "git+ssh://git@github.com/mattermost/react-native-webrtc.git#7f765758f2f67e467ebd224c1c4d00a475e400d3",
"dependencies": {
"base64-js": "^1.1.2",
"event-target-shim": "^1.0.5",
@@ -21375,13 +21375,13 @@
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
},
"node_modules/simple-plist": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.0.tgz",
"integrity": "sha512-uYWpeGFtZtVt2NhG4AHgpwx323zxD85x42heMJBan1qAiqqozIlaGrwrEt6kRjXWRWIXsuV1VLCvVmZan2B5dg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz",
"integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==",
"dependencies": {
"bplist-creator": "0.1.0",
"bplist-parser": "0.3.0",
"plist": "^3.0.4"
"bplist-parser": "0.3.1",
"plist": "^3.0.5"
}
},
"node_modules/simple-swizzle": {
@@ -25837,7 +25837,8 @@
"version": "0.3.6",
"resolved": "https://registry.npmjs.org/@mattermost/react-native-paste-input/-/react-native-paste-input-0.3.6.tgz",
"integrity": "sha512-/XwUkWfkXDPv1/N+3sILKRoqa4sElqN/fADQzkC2KHYxVKN72297vMm8s+X1n2l+y7phNQ8ZmhjjL0ghuz/1og==",
"requires": {}
"requires": {
}
},
"@msgpack/msgpack": {
"version": "2.7.1",
@@ -25889,7 +25890,8 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/@react-native-community/cameraroll/-/cameraroll-4.1.2.tgz",
"integrity": "sha512-jkdhMByMKD2CZ/5MPeBieYn8vkCfC4MOTouPpBpps3I8N6HUYJk+1JnDdktVYl2WINnqXpQptDA2YptVyifYAg==",
"requires": {}
"requires": {
}
},
"@react-native-community/cli": {
"version": "6.4.0",
@@ -26639,7 +26641,8 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/@react-native-community/clipboard/-/clipboard-1.5.1.tgz",
"integrity": "sha512-AHAmrkLEH5UtPaDiRqoULERHh3oNv7Dgs0bTC0hO5Z2GdNokAMPT5w8ci8aMcRemcwbtdHjxChgtjbeA38GBdA==",
"requires": {}
"requires": {
}
},
"@react-native-community/datetimepicker": {
"version": "5.1.0",
@@ -26744,7 +26747,8 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-22.4.1.tgz",
"integrity": "sha512-gcLfn6P2PrFAVx3AobaOzlIEevpAEf9chTpFZz7bYfc7pz8XRv7vuKTIE4hxPKZSha6XWKKplDQ0x9Pq8xX2mg==",
"dev": true,
"requires": {}
"requires": {
}
},
"eslint-plugin-react-native": {
"version": "3.11.0",
@@ -26774,13 +26778,15 @@
"version": "0.1.11",
"resolved": "https://registry.npmjs.org/@react-native-community/masked-view/-/masked-view-0.1.11.tgz",
"integrity": "sha512-rQfMIGSR/1r/SyN87+VD8xHHzDYeHaJq6elOSCAD+0iLagXkSI2pfA0LmSXP21uw5i3em7GkkRjfJ8wpqWXZNw==",
"requires": {}
"requires": {
}
},
"@react-native-community/netinfo": {
"version": "7.1.9",
"resolved": "https://registry.npmjs.org/@react-native-community/netinfo/-/netinfo-7.1.9.tgz",
"integrity": "sha512-xxbxFherpOjQeJm3rIx6gmZNEEBqVDIcmBII5QVSN8zf3xAwmaT/RxT74ISYDkeSlZwv4eegpkD4QA1ky52CNg==",
"requires": {}
"requires": {
}
},
"@react-native-cookies/cookies": {
"version": "6.0.11",
@@ -26821,7 +26827,8 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/@react-navigation/elements/-/elements-1.3.0.tgz",
"integrity": "sha512-C0roIxajvleskEpeYYJ1+2XTg8UqZO6o/SyzqTRuEUVsVD8CbFzKSOfj9kpfzA7jbYNzZPbzUJcm9kIigY9kHg==",
"requires": {}
"requires": {
}
},
"@react-navigation/native": {
"version": "6.0.7",
@@ -27758,7 +27765,8 @@
"resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.1.1.tgz",
"integrity": "sha512-1FBc1f9G4P/AxMqIgfZgeOTuRnwZMten8E7zap5zgpPInnCrP8D4Q81+4CWIch8i/Nf7nXjP0v6CjjbHOrXhKg==",
"dev": true,
"requires": {}
"requires": {
}
},
"@webpack-cli/info": {
"version": "1.4.1",
@@ -27774,7 +27782,8 @@
"resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.6.1.tgz",
"integrity": "sha512-gNGTiTrjEVQ0OcVnzsRSqTxaBSr+dmTfm+qJsCDluky8uhdLWep7Gcr62QsAKHTMxjCS/8nEITsmFAhfIx+QSw==",
"dev": true,
"requires": {}
"requires": {
}
},
"@xtuc/ieee754": {
"version": "1.2.0",
@@ -27859,14 +27868,16 @@
"integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==",
"dev": true,
"peer": true,
"requires": {}
"requires": {
}
},
"acorn-jsx": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
"dev": true,
"requires": {}
"requires": {
}
},
"acorn-walk": {
"version": "7.2.0",
@@ -28254,7 +28265,8 @@
"version": "7.0.0-bridge.0",
"resolved": "https://registry.npmjs.org/babel-core/-/babel-core-7.0.0-bridge.0.tgz",
"integrity": "sha512-poPX9mZH/5CSanm50Q+1toVci6pv5KSRv/5TWCwtzQS5XEwn40BcCrgIeMFWP9CKKIniKXNxoIOnOq4VVlGXhg==",
"requires": {}
"requires": {
}
},
"babel-eslint": {
"version": "10.1.0",
@@ -28650,9 +28662,9 @@
}
},
"bplist-parser": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.0.tgz",
"integrity": "sha512-zgmaRvT6AN1JpPPV+S0a1/FAtoxSreYDccZGIqEMSvZl9DMe70mJ7MFzpxa1X+gHVdkToE2haRUHHMiW1OdejA==",
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/bplist-parser/-/bplist-parser-0.3.1.tgz",
"integrity": "sha512-PyJxiNtA5T2PlLIeBot4lbp7rj4OadzjnMZD/G5zuBNt8ei/yCU7+wW0h2bag9vr8c+/WuRWmSxbqAl9hL1rBA==",
"requires": {
"big-integer": "1.6.x"
}
@@ -29214,8 +29226,9 @@
"integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs="
},
"commonmark": {
"version": "git+ssh://git@github.com/mattermost/commonmark.js.git#90a62d97ed2dbd2d4711a5adda327128f5827983",
"from": "commonmark@github:mattermost/commonmark.js#90a62d97ed2dbd2d4711a5adda327128f5827983",
"version": "git+ssh://git@github.com/mattermost/commonmark.js.git#d1003be97d15414af6c21894125623c45e3f5096",
"integrity": "sha512-80VOWbVPw7LzPokYFKLVDsB9xdbfb3o4vBE0fPPronTJX84vDI37iqeud20Th6r4DZtghRkR9AJjBhYmC2jlsg==",
"from": "commonmark@github:mattermost/commonmark.js#d1003be97d15414af6c21894125623c45e3f5096",
"requires": {
"entities": "~3.0.1",
"mdurl": "~1.0.1",
@@ -30551,7 +30564,8 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-header/-/eslint-plugin-header-3.1.1.tgz",
"integrity": "sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==",
"dev": true,
"requires": {}
"requires": {
}
},
"eslint-plugin-import": {
"version": "2.25.4",
@@ -30678,7 +30692,8 @@
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz",
"integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==",
"dev": true,
"requires": {}
"requires": {
}
},
"eslint-plugin-react-native-globals": {
"version": "0.1.2",
@@ -34284,7 +34299,8 @@
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
"dev": true,
"requires": {}
"requires": {
}
},
"jest-regex-util": {
"version": "27.4.0",
@@ -35105,7 +35121,8 @@
"integrity": "sha512-Kbk4Nxyq7/ZWqr/tarI9yIt/+iNNFOjBXEWgTb4ydaNHBNGgvf2QHbS9fdfsndfjFlFwEd4Al+mw83YkaD10ZA==",
"dev": true,
"peer": true,
"requires": {}
"requires": {
}
}
}
},
@@ -35114,7 +35131,8 @@
"resolved": "https://registry.npmjs.org/jsdom-global/-/jsdom-global-3.0.2.tgz",
"integrity": "sha1-a9KZwTsMRiay2iwDk81DhdYGrLk=",
"dev": true,
"requires": {}
"requires": {
}
},
"jsesc": {
"version": "2.5.2",
@@ -38506,7 +38524,8 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/react-freeze/-/react-freeze-1.0.0.tgz",
"integrity": "sha512-yQaiOqDmoKqks56LN9MTgY06O0qQHgV4FUrikH357DydArSZHQhl0BJFqGKIZoTqi8JizF9Dxhuk1FIZD6qCaw==",
"requires": {}
"requires": {
}
},
"react-intl": {
"version": "2.8.0",
@@ -38714,7 +38733,8 @@
"version": "8.4.8",
"resolved": "https://registry.npmjs.org/react-native-device-info/-/react-native-device-info-8.4.8.tgz",
"integrity": "sha512-92676ZWHZHsPM/EW1ulgb2MuVfjYfMWRTWMbLcrCsipkcMaZ9Traz5mpsnCS7KZpsOksnvUinzDIjsct2XGc6Q==",
"requires": {}
"requires": {
}
},
"react-native-document-picker": {
"version": "8.0.0",
@@ -38752,19 +38772,22 @@
"version": "2.10.10",
"resolved": "https://registry.npmjs.org/react-native-exception-handler/-/react-native-exception-handler-2.10.10.tgz",
"integrity": "sha512-otAXGoZDl1689OoUJWN/rXxVbdoZ3xcmyF1uq/CsizdLwwyZqVGd6d+p/vbYvnF996FfEyAEBnHrdFxulTn51w==",
"requires": {}
"requires": {
}
},
"react-native-fast-image": {
"version": "8.5.11",
"resolved": "https://registry.npmjs.org/react-native-fast-image/-/react-native-fast-image-8.5.11.tgz",
"integrity": "sha512-cNW4bIJg3nvKaheG8vGMfqCt5LMWX9MS5+wMudgKIHbGO51spRr4sgnlhVgwHLcZ5aeNOVJ8CPRxDIWKRq/0QA==",
"requires": {}
"requires": {
}
},
"react-native-file-viewer": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/react-native-file-viewer/-/react-native-file-viewer-2.1.5.tgz",
"integrity": "sha512-MGC6sx9jsqHdefhVQ6o0akdsPGpkXgiIbpygb2Sg4g4bh7v6K1cardLV1NwGB9A6u1yICOSDT/MOC//9Ez6EUg==",
"requires": {}
"requires": {
}
},
"react-native-gesture-handler": {
"version": "2.2.0",
@@ -38782,37 +38805,42 @@
"version": "1.13.0",
"resolved": "https://registry.npmjs.org/react-native-haptic-feedback/-/react-native-haptic-feedback-1.13.0.tgz",
"integrity": "sha512-g8G5QURitDeC/zRlobDvUXLxKYfMlIM2HQNWJKbHPSu61qfs0djnK4s1NZuQzihkeAO0KJ4AS2XWvKBzUmlXtA==",
"requires": {}
"requires": {
}
},
"react-native-hw-keyboard-event": {
"version": "0.0.4",
"resolved": "https://registry.npmjs.org/react-native-hw-keyboard-event/-/react-native-hw-keyboard-event-0.0.4.tgz",
"integrity": "sha512-G8qp0nm17PHigLb/axgdF9xg51BKCG2p1AGeq//J/luLp5zNczIcQJh+nm02R1MeEUE3e53wqO4LMe0MV3raZg==",
"requires": {}
"requires": {
}
},
"react-native-image-picker": {
"version": "4.7.3",
"resolved": "https://registry.npmjs.org/react-native-image-picker/-/react-native-image-picker-4.7.3.tgz",
"integrity": "sha512-eRKm4wlsmZHmsWFyv77kYc2F+ZyEVqe0m7mqhsMzWk6TQT4FBDtEDxmRDDFq+ivCu/1QD+EPhmYcAIpeGr7Ekg==",
"requires": {}
"requires": {
}
},
"react-native-incall-manager": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/react-native-incall-manager/-/react-native-incall-manager-3.3.0.tgz",
"integrity": "sha512-SvomgHUoKqVso/BGv02b4ndBqenlqYqW3pptZz5qHwteidKFNWI6ny+PPw5X55MdK0lyTbzBoiT3DwS06Qu0lg==",
"requires": {}
"version": "git+ssh://git@github.com/cpoile/react-native-incall-manager.git#8c55b9dac0a2ab25d651fb54b504d384f9989b36",
"from": "react-native-incall-manager@github:cpoile/react-native-incall-manager",
"requires": {
}
},
"react-native-keyboard-aware-scrollview": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/react-native-keyboard-aware-scrollview/-/react-native-keyboard-aware-scrollview-2.1.0.tgz",
"integrity": "sha512-XfWozWFPhdecfxN+wuqERX3mCGDrAim5siC6TWg3Qw7wK/zlwIwe1UIsHDNOQCzf9oIh0SkZXvoOFsMrnyIVmQ==",
"requires": {}
"requires": {
}
},
"react-native-keyboard-tracking-view": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/react-native-keyboard-tracking-view/-/react-native-keyboard-tracking-view-5.7.0.tgz",
"integrity": "sha512-MDeEwAbn9LJDOfHq0QLCGaZirVLk2X/tHqkAqz3y6uxryTRdSl9PwleOVar5Jx2oAPEg4J9BXbUD1wwOOi+5Kg==",
"requires": {}
"requires": {
}
},
"react-native-keychain": {
"version": "8.0.0",
@@ -38823,25 +38851,29 @@
"version": "2.5.6",
"resolved": "https://registry.npmjs.org/react-native-linear-gradient/-/react-native-linear-gradient-2.5.6.tgz",
"integrity": "sha512-HDwEaXcQIuXXCV70O+bK1rizFong3wj+5Q/jSyifKFLg0VWF95xh8XQgfzXwtq0NggL9vNjPKXa016KuFu+VFg==",
"requires": {}
"requires": {
}
},
"react-native-local-auth": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/react-native-local-auth/-/react-native-local-auth-1.6.0.tgz",
"integrity": "sha512-36cYGZGCG82pMiVJbQa5WMA93khP4v5JqLutFkMyB/eRpCULHmojNIBlbUPIY9SCeN4sg5VBRFTVGCtTg2r2kA==",
"requires": {}
"requires": {
}
},
"react-native-localize": {
"version": "2.1.9",
"resolved": "https://registry.npmjs.org/react-native-localize/-/react-native-localize-2.1.9.tgz",
"integrity": "sha512-n2Bi5P0cII8BrnG23Fs9PmpYcufD3JRbr1IBn3ArIA3Z3cGWg4eebv6am+CgdFC4kTPWUlVzX9fEgEMA+X8d6w==",
"requires": {}
"requires": {
}
},
"react-native-mmkv-storage": {
"version": "0.6.11",
"resolved": "https://registry.npmjs.org/react-native-mmkv-storage/-/react-native-mmkv-storage-0.6.11.tgz",
"integrity": "sha512-PaxUxbTwr+PHfzoYt6E1Mds9/CjV5uOFVstuBW0laFUWlGrTHgiaLWR8VwJt0EPScSl5Lz4RM8IiqmXbIUSbbw==",
"requires": {}
"requires": {
}
},
"react-native-navigation": {
"version": "7.25.1",
@@ -38866,7 +38898,8 @@
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/react-native-notifications/-/react-native-notifications-4.1.3.tgz",
"integrity": "sha512-A4SmRyfh2OlkptlJQvcQKkfnBKO1toUShmFplTkLXPNCqfpm/i4Fz+Uv+LzHSvbsU5U7EYf3JX9sfuyR06ZGPg==",
"requires": {}
"requires": {
}
},
"react-native-passcode-status": {
"version": "1.1.2",
@@ -38877,7 +38910,8 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/react-native-permissions/-/react-native-permissions-3.2.0.tgz",
"integrity": "sha512-UPXxf2twjYL9vPI4HP2kT15AOTY489MhsNuyAgp+wJM2IRkkSVW6rO3k4WuSRL9ZmPhwkWb9bYjf8EEwRzZcXg==",
"requires": {}
"requires": {
}
},
"react-native-ratings": {
"version": "8.0.4",
@@ -38935,7 +38969,8 @@
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-3.3.2.tgz",
"integrity": "sha512-yOwiiPJ1rk+/nfK13eafbpW6sKW0jOnsRem2C1LPJjM3tfTof6hlvV5eWHATye3XOpu2cJ7N+HdkUvUDGwFD2Q==",
"requires": {}
"requires": {
}
},
"react-native-screens": {
"version": "3.10.2",
@@ -38960,7 +38995,8 @@
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/react-native-size-matters/-/react-native-size-matters-0.3.1.tgz",
"integrity": "sha512-mKOfBLIBFBcs9br1rlZDvxD5+mAl8Gfr5CounwJtxI6Z82rGrMO+Kgl9EIg3RMVf3G855a85YVqHJL2f5EDRlw==",
"requires": {}
"requires": {
}
},
"react-native-slider": {
"version": "0.11.0",
@@ -38974,7 +39010,8 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/react-native-startup-time/-/react-native-startup-time-2.0.0.tgz",
"integrity": "sha512-tYruEDvwoVEOf+FMTTqp3aHNfA5ARWXXV+ar4wJBIQBdIlPEYDQWaYljOAV2dITuTsKAyPY2Q/N58gkKvGWIrA==",
"requires": {}
"requires": {
}
},
"react-native-svg": {
"version": "12.1.1",
@@ -39059,8 +39096,8 @@
}
},
"react-native-webrtc": {
"version": "git+ssh://git@github.com/streamer45/react-native-webrtc.git#7fe7d434892e6b29c5a6086d30b10f482db1e592",
"from": "react-native-webrtc@github:streamer45/react-native-webrtc",
"version": "git+ssh://git@github.com/mattermost/react-native-webrtc.git#7f765758f2f67e467ebd224c1c4d00a475e400d3",
"from": "react-native-webrtc@github:mattermost/react-native-webrtc",
"requires": {
"base64-js": "^1.1.2",
"event-target-shim": "^1.0.5",
@@ -39339,7 +39376,8 @@
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/redux-batched-actions/-/redux-batched-actions-0.5.0.tgz",
"integrity": "sha512-6orZWyCnIQXMGY4DUGM0oj0L7oYnwTACsfsru/J7r94RM3P9eS7SORGpr3LCeRCMoIMQcpfKZ7X4NdyFHBS8Eg==",
"requires": {}
"requires": {
}
},
"redux-mock-store": {
"version": "1.5.4",
@@ -39354,7 +39392,8 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz",
"integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==",
"requires": {}
"requires": {
}
},
"redux-persist-node-storage": {
"version": "2.0.0",
@@ -39383,7 +39422,8 @@
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
"integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
"requires": {}
"requires": {
}
},
"reflect.ownkeys": {
"version": "0.2.0",
@@ -39726,7 +39766,8 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/rn-placeholder/-/rn-placeholder-3.0.3.tgz",
"integrity": "sha512-EmVeLT8zDcTPilQZ2OHO/IiYUy2gApKGgbshDZBX0C4qxsn0cFATwgwOwyz8O7Vwg1Hul97Ci95hu7d6Js6XMQ==",
"requires": {}
"requires": {
}
},
"rst-selector-parser": {
"version": "2.2.3",
@@ -40042,7 +40083,8 @@
"resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz",
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"requires": {}
"requires": {
}
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -40254,13 +40296,13 @@
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
},
"simple-plist": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.0.tgz",
"integrity": "sha512-uYWpeGFtZtVt2NhG4AHgpwx323zxD85x42heMJBan1qAiqqozIlaGrwrEt6kRjXWRWIXsuV1VLCvVmZan2B5dg==",
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/simple-plist/-/simple-plist-1.3.1.tgz",
"integrity": "sha512-iMSw5i0XseMnrhtIzRb7XpQEXepa9xhWxGUojHBL43SIpQuDQkh3Wpy67ZbDzZVr6EKxvwVChnVpdl8hEVLDiw==",
"requires": {
"bplist-creator": "0.1.0",
"bplist-parser": "0.3.0",
"plist": "^3.0.4"
"bplist-parser": "0.3.1",
"plist": "^3.0.5"
}
},
"simple-swizzle": {
@@ -41087,7 +41129,8 @@
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peer": true,
"requires": {}
"requires": {
}
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -41810,7 +41853,8 @@
"integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==",
"dev": true,
"peer": true,
"requires": {}
"requires": {
}
},
"json-schema-traverse": {
"version": "0.4.1",
@@ -42079,7 +42123,8 @@
"version": "7.5.6",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz",
"integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==",
"requires": {}
"requires": {
}
},
"xcode": {
"version": "3.0.1",

View File

@@ -1,6 +1,6 @@
{
"name": "mattermost-mobile",
"version": "1.50.1",
"version": "1.51.2",
"description": "Mattermost Mobile with React Native",
"repository": "git@github.com:mattermost/mattermost-mobile.git",
"author": "Mattermost, Inc.",
@@ -25,7 +25,7 @@
"array.prototype.flat": "1.2.5",
"base-64": "1.0.0",
"buffer": "6.0.3",
"commonmark": "github:mattermost/commonmark.js#90a62d97ed2dbd2d4711a5adda327128f5827983",
"commonmark": "github:mattermost/commonmark.js#d1003be97d15414af6c21894125623c45e3f5096",
"commonmark-react-renderer": "github:mattermost/commonmark-react-renderer#4e52e1725c0ef5b1e2ecfe9883220ec36c2eb67d",
"deep-equal": "2.0.5",
"deepmerge": "4.2.2",
@@ -55,7 +55,7 @@
"react-native-haptic-feedback": "1.13.0",
"react-native-hw-keyboard-event": "0.0.4",
"react-native-image-picker": "4.7.3",
"react-native-incall-manager": "3.3.0",
"react-native-incall-manager": "github:cpoile/react-native-incall-manager",
"react-native-keyboard-aware-scrollview": "2.1.0",
"react-native-keyboard-tracking-view": "5.7.0",
"react-native-keychain": "8.0.0",
@@ -79,7 +79,7 @@
"react-native-svg": "12.1.1",
"react-native-vector-icons": "9.0.0",
"react-native-video": "5.2.0",
"react-native-webrtc": "github:streamer45/react-native-webrtc",
"react-native-webrtc": "github:mattermost/react-native-webrtc",
"react-native-webview": "11.17.1",
"react-native-youtube": "2.0.2",
"react-redux": "7.2.6",

View File

@@ -50,11 +50,17 @@ declare module 'react-native-webrtc' {
constructor();
stop(): void;
applyConstraints(): void;
clone(): void;
getCapabilities(): void;
getConstraints(): void;
getSettings(): void;
release(): void;
private _switchCamera(): void;
@@ -74,13 +80,21 @@ declare module 'react-native-webrtc' {
constructor(arg: any);
addTrack(track: MediaStreamTrack): void;
removeTrack(track: MediaStreamTrack): void;
getTracks(): MediaStreamTrack[];
getTrackById(trackId: string): MediaStreamTrack | undefined;
getAudioTracks(): MediaStreamTrack[];
getVideoTracks(): MediaStreamTrack[];
clone(): void;
toURL(): string;
release(): void;
}
@@ -92,23 +106,27 @@ declare module 'react-native-webrtc' {
bufferedAmountLowThreshold: number;
id: number;
label: string;
maxPacketLifeTime: ?number;
maxRetransmits: ?number;
maxPacketLifeTime?: number;
maxRetransmits?: number;
negotiated: boolean;
ordered: boolean;
protocol: string;
readyState: 'connecting' | 'open' | 'closing' | 'closed';
onopen: ?Function;
onmessage: ?Function;
onbufferedamountlow: ?Function;
onerror: ?Function;
onclose: ?Function;
onopen?: Function;
onmessage?: Function;
onbufferedamountlow?: Function;
onerror?: Function;
onclose?: Function;
constructor(peerConnectionId: number, label: string, dataChannelDict: RTCDataChannelInit)
send(data: string | ArrayBuffer | ArrayBufferView): void
close(): void
_unregisterEvents(): void
_registerEvents(): void
}
@@ -116,6 +134,7 @@ declare module 'react-native-webrtc' {
type: string;
data: string | ArrayBuffer | Blob;
origin: string;
constructor(type: any, eventInitDict: any)
}
@@ -129,6 +148,33 @@ declare module 'react-native-webrtc' {
track?: MediaStreamTrack;
}
export interface EventOnConnectionStateChange {
target: {
iceConnectionState: RTCIceConnectionState;
};
}
export interface ConfigurationParam {
username?: string | undefined;
credential?: string | undefined;
}
export interface ConfigurationParamWithUrls extends ConfigurationParam {
urls: string[];
}
export interface ConfigurationParamWithUrl extends ConfigurationParam {
url: string;
}
export interface RTCPeerConnectionConfiguration {
iceServers: ConfigurationParamWithUrls[] | ConfigurationParamWithUrl[];
iceTransportPolicy?: 'all' | 'relay' | 'nohost' | 'none' | undefined;
bundlePolicy?: 'balanced' | 'max-compat' | 'max-bundle' | undefined;
rtcpMuxPolicy?: 'negotiate' | 'require' | undefined;
iceCandidatePoolSize?: number | undefined;
}
export class RTCPeerConnection {
localDescription: RTCSessionDescriptionType;
remoteDescription: RTCSessionDescriptionType;
@@ -164,11 +210,11 @@ declare module 'react-native-webrtc' {
addTrack(track: MediaStreamTrack): void;
addTransceiver(kind: 'audio'|'video'|MediaStreamTrack, init: any): void;
addTransceiver(kind: 'audio' | 'video' | MediaStreamTrack, init: any): void;
removeStream(stream: MediaStream): void;
removeTrack(sender: RtpSender): Promise<boolean>
removeTrack(sender: RTCRtpSender): Promise<boolean>
createOffer(options?: RTCOfferOptions): Promise<RTCSessionDescriptionType>;
@@ -188,7 +234,7 @@ declare module 'react-native-webrtc' {
getRemoteStreams(): MediaStream[];
close(): void;
close(cb?: () => void): void;
private _getTrack(streamReactTag: string, trackId: string): MediaStreamTrack;
@@ -218,6 +264,7 @@ declare module 'react-native-webrtc' {
export class RTCSessionDescription extends RTCSessionDescriptionType {
constructor(info: RTCSessionDescriptionType);
toJSON(): RTCSessionDescriptionType;
}