forked from Ivasoft/mattermost-mobile
Compare commits
13 Commits
release-2.
...
MM-50010-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bcea5a6a3 | ||
|
|
6454a19a37 | ||
|
|
71805ed79d | ||
|
|
fe916ec740 | ||
|
|
3c046ae39c | ||
|
|
a804a7331f | ||
|
|
903aaf62b5 | ||
|
|
ef4fb9c8e0 | ||
|
|
af07f511f7 | ||
|
|
abd388986f | ||
|
|
0938045b7d | ||
|
|
9347e736e5 | ||
|
|
276bcba956 |
@@ -110,7 +110,7 @@ android {
|
||||
applicationId "com.mattermost.rnbeta"
|
||||
minSdkVersion rootProject.ext.minSdkVersion
|
||||
targetSdkVersion rootProject.ext.targetSdkVersion
|
||||
versionCode 459
|
||||
versionCode 460
|
||||
versionName "2.1.0"
|
||||
testBuildType System.getProperty('testBuildType', 'debug')
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -6,7 +6,7 @@ import {Platform} from 'react-native';
|
||||
|
||||
import {WebsocketEvents} from '@constants';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getConfigValue} from '@queries/servers/system';
|
||||
import {getConfig} from '@queries/servers/system';
|
||||
import {hasReliableWebsocket} from '@utils/config';
|
||||
import {toMilliseconds} from '@utils/datetime';
|
||||
import {logError, logInfo, logWarning} from '@utils/log';
|
||||
@@ -79,12 +79,8 @@ export default class WebSocketClient {
|
||||
return;
|
||||
}
|
||||
|
||||
const [websocketUrl, version, reliableWebsocketConfig] = await Promise.all([
|
||||
getConfigValue(database, 'WebsocketURL'),
|
||||
getConfigValue(database, 'Version'),
|
||||
getConfigValue(database, 'EnableReliableWebSockets'),
|
||||
]);
|
||||
const connectionUrl = (websocketUrl || this.serverUrl) + '/api/v4/websocket';
|
||||
const config = await getConfig(database);
|
||||
const connectionUrl = (config.WebsocketURL || this.serverUrl) + '/api/v4/websocket';
|
||||
|
||||
if (this.connectingCallback) {
|
||||
this.connectingCallback();
|
||||
@@ -105,7 +101,7 @@ export default class WebSocketClient {
|
||||
|
||||
this.url = connectionUrl;
|
||||
|
||||
const reliableWebSockets = hasReliableWebsocket(version, reliableWebsocketConfig);
|
||||
const reliableWebSockets = hasReliableWebsocket(config);
|
||||
if (reliableWebSockets) {
|
||||
// Add connection id, and last_sequence_number to the query param.
|
||||
// We cannot also send it as part of the auth_challenge, because the session cookie is already sent with the request.
|
||||
@@ -133,11 +129,6 @@ export default class WebSocketClient {
|
||||
headers.Authorization = `Bearer ${this.token}`;
|
||||
}
|
||||
const {client} = await getOrCreateWebSocketClient(this.url, {headers, timeoutInterval: WEBSOCKET_TIMEOUT});
|
||||
|
||||
// Check again if the client is the same, to avoid race conditions
|
||||
if (this.conn === client) {
|
||||
return;
|
||||
}
|
||||
this.conn = client;
|
||||
} catch (error) {
|
||||
return;
|
||||
|
||||
@@ -14,7 +14,7 @@ import TeamList from './team_list';
|
||||
type Props = {
|
||||
iconPad?: boolean;
|
||||
canJoinOtherTeams: boolean;
|
||||
hasMoreThanOneTeam: boolean;
|
||||
teamsCount: number;
|
||||
}
|
||||
|
||||
const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
@@ -36,8 +36,8 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => {
|
||||
};
|
||||
});
|
||||
|
||||
export default function TeamSidebar({iconPad, canJoinOtherTeams, hasMoreThanOneTeam}: Props) {
|
||||
const initialWidth = hasMoreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0;
|
||||
export default function TeamSidebar({iconPad, canJoinOtherTeams, teamsCount}: Props) {
|
||||
const initialWidth = teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0;
|
||||
const width = useSharedValue(initialWidth);
|
||||
const marginTop = useSharedValue(iconPad ? 44 : 0);
|
||||
const theme = useTheme();
|
||||
@@ -58,8 +58,8 @@ export default function TeamSidebar({iconPad, canJoinOtherTeams, hasMoreThanOneT
|
||||
}, [iconPad]);
|
||||
|
||||
useEffect(() => {
|
||||
width.value = hasMoreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0;
|
||||
}, [hasMoreThanOneTeam]);
|
||||
width.value = teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0;
|
||||
}, [teamsCount]);
|
||||
|
||||
return (
|
||||
<Animated.View style={[styles.container, transform]}>
|
||||
|
||||
@@ -43,14 +43,13 @@ import type {
|
||||
ApiResp,
|
||||
Call,
|
||||
CallParticipant,
|
||||
CallReactionEmoji,
|
||||
CallsConnection,
|
||||
RecordingState,
|
||||
ServerCallState,
|
||||
ServerChannelState,
|
||||
} from '@calls/types/calls';
|
||||
import type {Client} from '@client/rest';
|
||||
import type ClientError from '@client/rest/error';
|
||||
import type {CallRecordingState, EmojiData} from '@mmcalls/common/lib/types';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
||||
let connection: CallsConnection | null = null;
|
||||
@@ -322,7 +321,7 @@ export const unraiseHand = () => {
|
||||
}
|
||||
};
|
||||
|
||||
export const sendReaction = (emoji: CallReactionEmoji) => {
|
||||
export const sendReaction = (emoji: EmojiData) => {
|
||||
if (connection) {
|
||||
connection.sendReaction(emoji);
|
||||
}
|
||||
@@ -415,7 +414,7 @@ export const startCallRecording = async (serverUrl: string, callId: string) => {
|
||||
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
||||
let data: ApiResp | RecordingState;
|
||||
let data: ApiResp | CallRecordingState;
|
||||
try {
|
||||
data = await client.startCallRecording(callId);
|
||||
} catch (error) {
|
||||
@@ -433,7 +432,7 @@ export const stopCallRecording = async (serverUrl: string, callId: string) => {
|
||||
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
||||
let data: ApiResp | RecordingState;
|
||||
let data: ApiResp | CallRecordingState;
|
||||
try {
|
||||
data = await client.stopCallRecording(callId);
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,24 +1,20 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {
|
||||
ServerChannelState,
|
||||
ServerCallsConfig,
|
||||
ApiResp,
|
||||
RecordingState,
|
||||
} from '@calls/types/calls';
|
||||
import type {ServerChannelState, ApiResp} from '@calls/types/calls';
|
||||
import type {CallRecordingState, CallsConfig} from '@mmcalls/common/lib/types';
|
||||
import type {RTCIceServer} from 'react-native-webrtc';
|
||||
|
||||
export interface ClientCallsMix {
|
||||
getEnabled: () => Promise<Boolean>;
|
||||
getCalls: () => Promise<ServerChannelState[]>;
|
||||
getCallForChannel: (channelId: string) => Promise<ServerChannelState>;
|
||||
getCallsConfig: () => Promise<ServerCallsConfig>;
|
||||
getCallsConfig: () => Promise<CallsConfig>;
|
||||
enableChannelCalls: (channelId: string, enable: boolean) => Promise<ServerChannelState>;
|
||||
endCall: (channelId: string) => Promise<ApiResp>;
|
||||
genTURNCredentials: () => Promise<RTCIceServer[]>;
|
||||
startCallRecording: (callId: string) => Promise<ApiResp | RecordingState>;
|
||||
stopCallRecording: (callId: string) => Promise<ApiResp | RecordingState>;
|
||||
startCallRecording: (callId: string) => Promise<ApiResp | CallRecordingState>;
|
||||
stopCallRecording: (callId: string) => Promise<ApiResp | CallRecordingState>;
|
||||
}
|
||||
|
||||
const ClientCalls = (superclass: any) => class extends superclass {
|
||||
@@ -52,7 +48,7 @@ const ClientCalls = (superclass: any) => class extends superclass {
|
||||
return this.doFetch(
|
||||
`${this.getCallsRoute()}/config`,
|
||||
{method: 'get'},
|
||||
) as ServerCallsConfig;
|
||||
) as CallsConfig;
|
||||
};
|
||||
|
||||
enableChannelCalls = async (channelId: string, enable: boolean) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ import CompassIcon from '@components/compass_icon';
|
||||
import Emoji from '@components/emoji';
|
||||
import ProfilePicture from '@components/profile_picture';
|
||||
|
||||
import type {CallReactionEmoji} from '@calls/types/calls';
|
||||
import type {EmojiData} from '@mmcalls/common/lib/types';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
|
||||
type Props = {
|
||||
@@ -18,7 +18,7 @@ type Props = {
|
||||
muted?: boolean;
|
||||
sharingScreen?: boolean;
|
||||
raisedHand?: boolean;
|
||||
reaction?: CallReactionEmoji;
|
||||
reaction?: EmojiData;
|
||||
size?: 'm' | 'l';
|
||||
}
|
||||
|
||||
|
||||
@@ -1,18 +1,17 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
import {deflate} from 'pako/lib/deflate.js';
|
||||
import {RTCPeer} from '@mmcalls/common/lib';
|
||||
import {deflate} from 'pako';
|
||||
import {DeviceEventEmitter, EmitterSubscription} from 'react-native';
|
||||
import InCallManager from 'react-native-incall-manager';
|
||||
import {
|
||||
MediaStream,
|
||||
MediaStreamTrack,
|
||||
mediaDevices,
|
||||
RTCPeerConnection,
|
||||
} from 'react-native-webrtc';
|
||||
|
||||
import RTCPeer from '@calls/rtcpeer';
|
||||
import {setSpeakerPhone} from '@calls/state';
|
||||
import {getICEServersConfigs} from '@calls/utils';
|
||||
import {WebsocketEvents} from '@constants';
|
||||
@@ -22,7 +21,9 @@ import {logError, logDebug, logWarning} from '@utils/log';
|
||||
|
||||
import {WebSocketClient, wsReconnectionTimeoutErr} from './websocket_client';
|
||||
|
||||
import type {CallReactionEmoji, CallsConnection} from '@calls/types/calls';
|
||||
import type {CallsConnection} from '@calls/types/calls';
|
||||
import type {RTCPeerOpts} from '@mmcalls/common/lib/rtc_peer';
|
||||
import type {EmojiData} from '@mmcalls/common/lib/types';
|
||||
|
||||
const peerConnectTimeout = 5000;
|
||||
|
||||
@@ -164,7 +165,7 @@ export async function newConnection(
|
||||
}
|
||||
};
|
||||
|
||||
const sendReaction = (emoji: CallReactionEmoji) => {
|
||||
const sendReaction = (emoji: EmojiData) => {
|
||||
if (ws) {
|
||||
ws.send('react', {
|
||||
data: JSON.stringify(emoji),
|
||||
@@ -204,7 +205,14 @@ export async function newConnection(
|
||||
InCallManager.start({media: 'video'});
|
||||
setSpeakerPhone(true);
|
||||
|
||||
peer = new RTCPeer({iceServers: iceConfigs || []});
|
||||
const opts: RTCPeerOpts = {
|
||||
logDebug,
|
||||
webrtc: {
|
||||
MediaStream,
|
||||
RTCPeerConnection,
|
||||
},
|
||||
};
|
||||
peer = new RTCPeer({iceServers: iceConfigs || []}, opts);
|
||||
|
||||
peer.on('offer', (sdp) => {
|
||||
logDebug(`local offer, sending: ${JSON.stringify(sdp)}`);
|
||||
|
||||
@@ -1,210 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||
|
||||
import {EventEmitter} from 'events';
|
||||
|
||||
import {
|
||||
MediaStream,
|
||||
MediaStreamTrack,
|
||||
RTCIceCandidate,
|
||||
RTCPeerConnection,
|
||||
RTCPeerConnectionIceEvent,
|
||||
RTCRtpSender,
|
||||
RTCSessionDescription,
|
||||
} from 'react-native-webrtc';
|
||||
|
||||
import {logDebug, logError} from '@utils/log';
|
||||
|
||||
import type {RTCPeerConfig} from './types';
|
||||
import type RTCTrackEvent from 'react-native-webrtc/lib/typescript/RTCTrackEvent';
|
||||
|
||||
const rtcConnFailedErr = new Error('rtc connection failed');
|
||||
|
||||
export default class RTCPeer extends EventEmitter {
|
||||
private pc: RTCPeerConnection | null;
|
||||
private readonly senders: { [key: string]: RTCRtpSender };
|
||||
private candidates: RTCIceCandidate[] = [];
|
||||
private makingOffer = false;
|
||||
|
||||
public connected: boolean;
|
||||
|
||||
constructor(config: RTCPeerConfig) {
|
||||
super();
|
||||
|
||||
// We keep a map of track IDs -> RTP sender so that we can easily
|
||||
// replace tracks when muting/unmuting.
|
||||
this.senders = {};
|
||||
|
||||
this.pc = new RTCPeerConnection(config);
|
||||
this.pc.onnegotiationneeded = () => this.onNegotiationNeeded();
|
||||
this.pc.onicecandidate = (ev) => this.onICECandidate(ev);
|
||||
this.pc.oniceconnectionstatechange = () => this.onICEConnectionStateChange();
|
||||
this.pc.onconnectionstatechange = () => this.onConnectionStateChange();
|
||||
this.pc.ontrack = (ev) => this.onTrack(ev);
|
||||
|
||||
this.connected = false;
|
||||
|
||||
// We create a data channel for two reasons:
|
||||
// - Initiate a connection without preemptively adding audio/video tracks.
|
||||
// - Use this communication channel for further negotiation (to be implemented).
|
||||
this.pc.createDataChannel('calls-dc');
|
||||
}
|
||||
|
||||
private onICECandidate(ev: RTCPeerConnectionIceEvent) {
|
||||
if (ev.candidate) {
|
||||
this.emit('candidate', ev.candidate);
|
||||
}
|
||||
}
|
||||
|
||||
private onConnectionStateChange() {
|
||||
switch (this.pc?.connectionState) {
|
||||
case 'connected':
|
||||
this.connected = true;
|
||||
break;
|
||||
case 'failed':
|
||||
this.emit('close', rtcConnFailedErr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private onICEConnectionStateChange() {
|
||||
switch (this.pc?.iceConnectionState) {
|
||||
case 'connected':
|
||||
this.emit('connect');
|
||||
break;
|
||||
case 'failed':
|
||||
this.emit('close', rtcConnFailedErr);
|
||||
break;
|
||||
case 'closed':
|
||||
this.emit('close');
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
private async onNegotiationNeeded() {
|
||||
try {
|
||||
this.makingOffer = true;
|
||||
await this.pc?.setLocalDescription();
|
||||
this.emit('offer', this.pc?.localDescription);
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
} finally {
|
||||
this.makingOffer = false;
|
||||
}
|
||||
}
|
||||
|
||||
private onTrack(ev: RTCTrackEvent) {
|
||||
if (ev.streams.length === 0) {
|
||||
this.emit('stream', new MediaStream([ev.track]));
|
||||
return;
|
||||
}
|
||||
this.emit('stream', ev.streams[0]);
|
||||
}
|
||||
|
||||
public async signal(data: string) {
|
||||
if (!this.pc) {
|
||||
throw new Error('peer has been destroyed');
|
||||
}
|
||||
|
||||
const msg = JSON.parse(data);
|
||||
|
||||
if (msg.type === 'offer' && (this.makingOffer || this.pc?.signalingState !== 'stable')) {
|
||||
logDebug('signaling conflict, we are polite, proceeding...');
|
||||
}
|
||||
|
||||
try {
|
||||
switch (msg.type) {
|
||||
case 'candidate':
|
||||
// It's possible that ICE candidates are received moments before
|
||||
// we set the initial remote description which would cause an
|
||||
// error. In such case we queue them up to be added later.
|
||||
if (this.pc.remoteDescription && this.pc.remoteDescription.type) {
|
||||
this.pc.addIceCandidate(msg.candidate).catch((err) => {
|
||||
logError('failed to add candidate', err);
|
||||
});
|
||||
} else {
|
||||
logDebug('received ice candidate before remote description, queuing...');
|
||||
this.candidates.push(msg.candidate);
|
||||
}
|
||||
break;
|
||||
case 'offer':
|
||||
await this.pc.setRemoteDescription(new RTCSessionDescription(msg));
|
||||
await this.pc.setLocalDescription();
|
||||
this.emit('answer', this.pc.localDescription);
|
||||
break;
|
||||
case 'answer':
|
||||
await this.pc.setRemoteDescription(msg);
|
||||
for (const candidate of this.candidates) {
|
||||
logDebug('adding queued ice candidate');
|
||||
this.pc.addIceCandidate(candidate).catch((err) => {
|
||||
logError('failed to add candidate', err);
|
||||
});
|
||||
}
|
||||
break;
|
||||
default:
|
||||
this.emit('error', Error('invalid signaling data received'));
|
||||
}
|
||||
} catch (err) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
|
||||
public async addTrack(track: MediaStreamTrack, stream?: MediaStream) {
|
||||
if (!this.pc) {
|
||||
throw new Error('peer has been destroyed');
|
||||
}
|
||||
const sender = await this.pc.addTrack(track, stream!);
|
||||
if (sender) {
|
||||
this.senders[track.id] = sender;
|
||||
}
|
||||
}
|
||||
|
||||
public addStream(stream: MediaStream) {
|
||||
stream.getTracks().forEach((track) => {
|
||||
this.addTrack(track, stream);
|
||||
});
|
||||
}
|
||||
|
||||
public replaceTrack(oldTrackID: string, newTrack: MediaStreamTrack | null) {
|
||||
const sender = this.senders[oldTrackID];
|
||||
if (!sender) {
|
||||
throw new Error('sender for track not found');
|
||||
}
|
||||
if (newTrack && newTrack.id !== oldTrackID) {
|
||||
delete this.senders[oldTrackID];
|
||||
this.senders[newTrack.id] = sender;
|
||||
}
|
||||
sender.replaceTrack(newTrack);
|
||||
}
|
||||
|
||||
public getStats() {
|
||||
if (!this.pc) {
|
||||
throw new Error('peer has been destroyed');
|
||||
}
|
||||
return this.pc.getStats();
|
||||
}
|
||||
|
||||
public destroy() {
|
||||
if (!this.pc) {
|
||||
throw new Error('peer has been destroyed already');
|
||||
}
|
||||
|
||||
this.removeAllListeners('candidate');
|
||||
this.removeAllListeners('connect');
|
||||
this.removeAllListeners('error');
|
||||
this.removeAllListeners('close');
|
||||
this.removeAllListeners('offer');
|
||||
this.removeAllListeners('answer');
|
||||
this.removeAllListeners('stream');
|
||||
this.pc.onnegotiationneeded = null;
|
||||
this.pc.onicecandidate = null;
|
||||
this.pc.oniceconnectionstatechange = null;
|
||||
this.pc.onconnectionstatechange = null;
|
||||
this.pc.ontrack = null;
|
||||
this.pc.close();
|
||||
this.pc = null;
|
||||
this.connected = false;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {RTCIceServer} from 'react-native-webrtc';
|
||||
|
||||
export type RTCPeerConfig = {
|
||||
iceServers: RTCIceServer[];
|
||||
}
|
||||
@@ -40,8 +40,6 @@ import {
|
||||
setPluginEnabled,
|
||||
setUserVoiceOn,
|
||||
} from '@calls/state/actions';
|
||||
import {License} from '@constants';
|
||||
|
||||
import {
|
||||
Call,
|
||||
CallsState,
|
||||
@@ -51,8 +49,10 @@ import {
|
||||
DefaultCurrentCall,
|
||||
DefaultGlobalCallsState,
|
||||
GlobalCallsState,
|
||||
RecordingState,
|
||||
} from '../types/calls';
|
||||
} from '@calls/types/calls';
|
||||
import {License} from '@constants';
|
||||
|
||||
import type {CallRecordingState} from '@mmcalls/common/lib/types';
|
||||
|
||||
jest.mock('@calls/alerts');
|
||||
|
||||
@@ -797,6 +797,7 @@ describe('useCallsState', () => {
|
||||
|
||||
it('config', () => {
|
||||
const newConfig = {
|
||||
...DefaultCallsConfig,
|
||||
ICEServers: [],
|
||||
ICEServersConfigs: [
|
||||
{
|
||||
@@ -914,7 +915,7 @@ describe('useCallsState', () => {
|
||||
myUserId: 'myUserId',
|
||||
...call1,
|
||||
};
|
||||
const recState: RecordingState = {
|
||||
const recState: CallRecordingState = {
|
||||
init_at: 123,
|
||||
start_at: 231,
|
||||
end_at: 345,
|
||||
|
||||
@@ -16,17 +16,17 @@ import {
|
||||
} from '@calls/state';
|
||||
import {
|
||||
Call,
|
||||
CallReaction,
|
||||
CallsConfig,
|
||||
CallsConfigState,
|
||||
ChannelsWithCalls,
|
||||
CurrentCall,
|
||||
DefaultCall,
|
||||
DefaultCurrentCall,
|
||||
ReactionStreamEmoji,
|
||||
RecordingState,
|
||||
} from '@calls/types/calls';
|
||||
import {REACTION_LIMIT, REACTION_TIMEOUT} from '@constants/calls';
|
||||
|
||||
import type {CallRecordingState, UserReactionData} from '@mmcalls/common/lib/types';
|
||||
|
||||
export const setCalls = (serverUrl: string, myUserId: string, calls: Dictionary<Call>, enabled: Dictionary<boolean>) => {
|
||||
const channelsWithCalls = Object.keys(calls).reduce(
|
||||
(accum, next) => {
|
||||
@@ -390,7 +390,7 @@ export const setSpeakerPhone = (speakerphoneOn: boolean) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const setConfig = (serverUrl: string, config: Partial<CallsConfig>) => {
|
||||
export const setConfig = (serverUrl: string, config: Partial<CallsConfigState>) => {
|
||||
const callsConfig = getCallsConfig(serverUrl);
|
||||
setCallsConfig(serverUrl, {...callsConfig, ...config});
|
||||
};
|
||||
@@ -423,7 +423,7 @@ export const setMicPermissionsErrorDismissed = () => {
|
||||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
||||
export const userReacted = (serverUrl: string, channelId: string, reaction: CallReaction) => {
|
||||
export const userReacted = (serverUrl: string, channelId: string, reaction: UserReactionData) => {
|
||||
// Note: Simplification for performance:
|
||||
// If you are not in the call with the reaction, ignore it. There could be many calls ongoing in your
|
||||
// servers, do we want to be tracking reactions and setting timeouts for all those calls? No.
|
||||
@@ -474,7 +474,7 @@ export const userReacted = (serverUrl: string, channelId: string, reaction: Call
|
||||
}, REACTION_TIMEOUT);
|
||||
};
|
||||
|
||||
const userReactionTimeout = (serverUrl: string, channelId: string, reaction: CallReaction) => {
|
||||
const userReactionTimeout = (serverUrl: string, channelId: string, reaction: UserReactionData) => {
|
||||
const currentCall = getCurrentCall();
|
||||
if (currentCall?.channelId !== channelId) {
|
||||
return;
|
||||
@@ -498,7 +498,7 @@ const userReactionTimeout = (serverUrl: string, channelId: string, reaction: Cal
|
||||
setCurrentCall(nextCurrentCall);
|
||||
};
|
||||
|
||||
export const setRecordingState = (serverUrl: string, channelId: string, recState: RecordingState) => {
|
||||
export const setRecordingState = (serverUrl: string, channelId: string, recState: CallRecordingState) => {
|
||||
const callsState = getCallsState(serverUrl);
|
||||
if (!callsState.calls[channelId]) {
|
||||
return;
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
|
||||
import {CallsConfig, DefaultCallsConfig} from '@calls/types/calls';
|
||||
import {CallsConfigState, DefaultCallsConfig} from '@calls/types/calls';
|
||||
|
||||
const callsConfigSubjects: Dictionary<BehaviorSubject<CallsConfig>> = {};
|
||||
const callsConfigSubjects: Dictionary<BehaviorSubject<CallsConfigState>> = {};
|
||||
|
||||
const getCallsConfigSubject = (serverUrl: string) => {
|
||||
if (!callsConfigSubjects[serverUrl]) {
|
||||
@@ -20,7 +20,7 @@ export const getCallsConfig = (serverUrl: string) => {
|
||||
return getCallsConfigSubject(serverUrl).value;
|
||||
};
|
||||
|
||||
export const setCallsConfig = (serverUrl: string, callsConfig: CallsConfig) => {
|
||||
export const setCallsConfig = (serverUrl: string, callsConfig: CallsConfigState) => {
|
||||
getCallsConfigSubject(serverUrl).next(callsConfig);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {CallRecordingState, CallsConfig, EmojiData, UserReactionData} from '@mmcalls/common/lib/types';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {RTCIceServer} from 'react-native-webrtc';
|
||||
|
||||
export type GlobalCallsState = {
|
||||
micPermissionsGranted: boolean;
|
||||
@@ -31,7 +31,7 @@ export type Call = {
|
||||
screenOn: string;
|
||||
threadId: string;
|
||||
ownerId: string;
|
||||
recState?: RecordingState;
|
||||
recState?: CallRecordingState;
|
||||
hostId: string;
|
||||
}
|
||||
|
||||
@@ -75,7 +75,7 @@ export type CallParticipant = {
|
||||
muted: boolean;
|
||||
raisedHand: number;
|
||||
userModel?: UserModel;
|
||||
reaction?: CallReaction;
|
||||
reaction?: UserReactionData;
|
||||
}
|
||||
|
||||
export type ChannelsWithCalls = Dictionary<boolean>;
|
||||
@@ -100,7 +100,7 @@ export type ServerCallState = {
|
||||
screen_sharing_id: string;
|
||||
owner_id: string;
|
||||
host_id: string;
|
||||
recording: RecordingState;
|
||||
recording: CallRecordingState;
|
||||
}
|
||||
|
||||
export type CallsConnection = {
|
||||
@@ -111,26 +111,16 @@ export type CallsConnection = {
|
||||
raiseHand: () => void;
|
||||
unraiseHand: () => void;
|
||||
initializeVoiceTrack: () => void;
|
||||
sendReaction: (emoji: CallReactionEmoji) => void;
|
||||
sendReaction: (emoji: EmojiData) => void;
|
||||
}
|
||||
|
||||
export type ServerCallsConfig = {
|
||||
ICEServers?: string[]; // deprecated
|
||||
ICEServersConfigs: RTCIceServer[];
|
||||
export type CallsConfigState = CallsConfig & {
|
||||
AllowEnableCalls: boolean;
|
||||
DefaultEnabled: boolean;
|
||||
NeedsTURNCredentials: boolean;
|
||||
sku_short_name: string;
|
||||
MaxCallParticipants: number;
|
||||
EnableRecordings: boolean;
|
||||
}
|
||||
|
||||
export type CallsConfig = ServerCallsConfig & {
|
||||
pluginEnabled: boolean;
|
||||
last_retrieved_at: number;
|
||||
}
|
||||
|
||||
export const DefaultCallsConfig: CallsConfig = {
|
||||
export const DefaultCallsConfig: CallsConfigState = {
|
||||
pluginEnabled: false,
|
||||
ICEServers: [], // deprecated
|
||||
ICEServersConfigs: [],
|
||||
@@ -141,6 +131,8 @@ export const DefaultCallsConfig: CallsConfig = {
|
||||
sku_short_name: '',
|
||||
MaxCallParticipants: 0,
|
||||
EnableRecordings: false,
|
||||
MaxRecordingDuration: 60,
|
||||
AllowScreenSharing: true,
|
||||
};
|
||||
|
||||
export type ApiResp = {
|
||||
@@ -149,18 +141,6 @@ export type ApiResp = {
|
||||
status_code: number;
|
||||
}
|
||||
|
||||
export type CallReactionEmoji = {
|
||||
name: string;
|
||||
skin?: string;
|
||||
unified: string;
|
||||
}
|
||||
|
||||
export type CallReaction = {
|
||||
user_id: string;
|
||||
emoji: CallReactionEmoji;
|
||||
timestamp: number;
|
||||
}
|
||||
|
||||
export type ReactionStreamEmoji = {
|
||||
name: string;
|
||||
latestTimestamp: number;
|
||||
|
||||
@@ -3,15 +3,15 @@
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import {CallsConfigState, DefaultCallsConfig} from '@calls/types/calls';
|
||||
import {License} from '@constants';
|
||||
|
||||
import {getICEServersConfigs} from './utils';
|
||||
|
||||
import type {CallsConfig} from '@calls/types/calls';
|
||||
|
||||
describe('getICEServersConfigs', () => {
|
||||
it('backwards compatible case, no ICEServersConfigs present', () => {
|
||||
const config: CallsConfig = {
|
||||
const config: CallsConfigState = {
|
||||
...DefaultCallsConfig,
|
||||
pluginEnabled: true,
|
||||
ICEServers: ['stun:stun.example.com:3478'],
|
||||
ICEServersConfigs: [],
|
||||
@@ -33,7 +33,8 @@ describe('getICEServersConfigs', () => {
|
||||
});
|
||||
|
||||
it('ICEServersConfigs set', () => {
|
||||
const config: CallsConfig = {
|
||||
const config: CallsConfigState = {
|
||||
...DefaultCallsConfig,
|
||||
pluginEnabled: true,
|
||||
ICEServersConfigs: [
|
||||
{
|
||||
@@ -64,7 +65,8 @@ describe('getICEServersConfigs', () => {
|
||||
});
|
||||
|
||||
it('Both ICEServers and ICEServersConfigs set', () => {
|
||||
const config: CallsConfig = {
|
||||
const config: CallsConfigState = {
|
||||
...DefaultCallsConfig,
|
||||
pluginEnabled: true,
|
||||
ICEServers: ['stun:stuna.example.com:3478'],
|
||||
ICEServersConfigs: [
|
||||
|
||||
@@ -8,9 +8,11 @@ import Calls from '@constants/calls';
|
||||
import {isMinimumServerVersion} from '@utils/helpers';
|
||||
import {displayUsername} from '@utils/user';
|
||||
|
||||
import type {CallParticipant, ServerCallsConfig} from '@calls/types/calls';
|
||||
import type {CallParticipant} from '@calls/types/calls';
|
||||
import type {CallsConfig} from '@mmcalls/common/lib/types';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
import type {RTCIceServer} from 'react-native-webrtc';
|
||||
|
||||
export function sortParticipants(teammateNameDisplay: string, participants?: Dictionary<CallParticipant>, presenterID?: string): CallParticipant[] {
|
||||
if (!participants) {
|
||||
@@ -106,7 +108,7 @@ export function errorAlert(error: string, intl: IntlShape) {
|
||||
);
|
||||
}
|
||||
|
||||
export function getICEServersConfigs(config: ServerCallsConfig) {
|
||||
export function getICEServersConfigs(config: CallsConfig): RTCIceServer[] {
|
||||
// if ICEServersConfigs is set, we can trust this to be complete and
|
||||
// coming from an updated API.
|
||||
if (config.ICEServersConfigs && config.ICEServersConfigs.length > 0) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$, Observable} from 'rxjs';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {switchMap, combineLatestWith, distinctUntilChanged} from 'rxjs/operators';
|
||||
|
||||
import {Preferences} from '@constants';
|
||||
@@ -19,7 +19,6 @@ import CategoryBody from './category_body';
|
||||
import type {WithDatabaseArgs} from '@typings/database/database';
|
||||
import type CategoryModel from '@typings/database/models/servers/category';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
import type MyChannelModel from '@typings/database/models/servers/my_channel';
|
||||
import type PreferenceModel from '@typings/database/models/servers/preference';
|
||||
|
||||
type EnhanceProps = {
|
||||
@@ -31,7 +30,8 @@ type EnhanceProps = {
|
||||
|
||||
const withUserId = withObservables([], ({database}: WithDatabaseArgs) => ({currentUserId: observeCurrentUserId(database)}));
|
||||
|
||||
const observeCategoryChannels = (category: CategoryModel, myChannels: Observable<MyChannelModel[]>) => {
|
||||
const observeCategoryChannels = (category: CategoryModel) => {
|
||||
const myChannels = category.myChannels.observeWithColumns(['last_post_at', 'is_unread']);
|
||||
const channels = category.channels.observeWithColumns(['create_at', 'display_name']);
|
||||
const manualSort = category.categoryChannelsBySortOrder.observeWithColumns(['sort_order']);
|
||||
return myChannels.pipe(
|
||||
@@ -57,8 +57,7 @@ const observeCategoryChannels = (category: CategoryModel, myChannels: Observable
|
||||
};
|
||||
|
||||
const enhanced = withObservables([], ({category, currentUserId, database, isTablet, locale}: EnhanceProps) => {
|
||||
const categoryMyChannels = category.myChannels.observeWithColumns(['last_post_at', 'is_unread']);
|
||||
const channelsWithMyChannel = observeCategoryChannels(category, categoryMyChannels);
|
||||
const channelsWithMyChannel = observeCategoryChannels(category);
|
||||
const currentChannelId = isTablet ? observeCurrentChannelId(database) : of$('');
|
||||
const lastUnreadId = isTablet ? observeLastUnreadChannelId(database) : of$(undefined);
|
||||
|
||||
@@ -78,9 +77,9 @@ const enhanced = withObservables([], ({category, currentUserId, database, isTabl
|
||||
);
|
||||
}
|
||||
|
||||
const notifyPropsPerChannel = categoryMyChannels.pipe(
|
||||
const notifyPropsPerChannel = channelsWithMyChannel.pipe(
|
||||
// eslint-disable-next-line max-nested-callbacks
|
||||
switchMap((mc) => observeNotifyPropsByChannels(database, mc)),
|
||||
switchMap((cwms) => observeNotifyPropsByChannels(database, cwms.map((c) => c.myChannel))),
|
||||
);
|
||||
|
||||
const hiddenDmPrefs = queryPreferencesByCategoryAndName(database, Preferences.CATEGORIES.DIRECT_CHANNEL_SHOW, undefined, 'false').
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {combineLatest, of as of$} from 'rxjs';
|
||||
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {Permissions} from '@constants';
|
||||
import {observePermissionForTeam} from '@queries/servers/role';
|
||||
@@ -25,7 +25,6 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
|
||||
const canJoinChannels = combineLatest([currentUser, team]).pipe(
|
||||
switchMap(([u, t]) => observePermissionForTeam(database, t, u, Permissions.JOIN_PUBLIC_CHANNELS, true)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const canCreatePublicChannels = combineLatest([currentUser, team]).pipe(
|
||||
@@ -38,7 +37,6 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
|
||||
const canCreateChannels = combineLatest([canCreatePublicChannels, canCreatePrivateChannels]).pipe(
|
||||
switchMap(([open, priv]) => of$(open || priv)),
|
||||
distinctUntilChanged(),
|
||||
);
|
||||
|
||||
const canAddUserToTeam = combineLatest([currentUser, team]).pipe(
|
||||
@@ -50,11 +48,9 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
canJoinChannels,
|
||||
canInvitePeople: combineLatest([enableOpenServer, canAddUserToTeam]).pipe(
|
||||
switchMap(([openServer, addUser]) => of$(openServer && addUser)),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
displayName: team.pipe(
|
||||
switchMap((t) => of$(t?.displayName)),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
pushProxyStatus: observePushVerificationStatus(database),
|
||||
};
|
||||
|
||||
@@ -33,8 +33,8 @@ describe('components/categories_list', () => {
|
||||
it('should render', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<CategoriesList
|
||||
moreThanOneTeam={false}
|
||||
hasChannels={true}
|
||||
teamsCount={1}
|
||||
channelsCount={1}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
@@ -46,8 +46,8 @@ describe('components/categories_list', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<CategoriesList
|
||||
isCRTEnabled={true}
|
||||
moreThanOneTeam={false}
|
||||
hasChannels={true}
|
||||
teamsCount={1}
|
||||
channelsCount={1}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
@@ -67,8 +67,8 @@ describe('components/categories_list', () => {
|
||||
jest.useFakeTimers();
|
||||
const wrapper = renderWithEverything(
|
||||
<CategoriesList
|
||||
moreThanOneTeam={false}
|
||||
hasChannels={true}
|
||||
teamsCount={0}
|
||||
channelsCount={1}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
@@ -89,8 +89,8 @@ describe('components/categories_list', () => {
|
||||
jest.useFakeTimers();
|
||||
const wrapper = renderWithEverything(
|
||||
<CategoriesList
|
||||
moreThanOneTeam={true}
|
||||
hasChannels={false}
|
||||
teamsCount={1}
|
||||
channelsCount={0}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
|
||||
@@ -27,28 +27,28 @@ const getStyleSheet = makeStyleSheetFromTheme((theme: Theme) => ({
|
||||
}));
|
||||
|
||||
type ChannelListProps = {
|
||||
hasChannels: boolean;
|
||||
channelsCount: number;
|
||||
iconPad?: boolean;
|
||||
isCRTEnabled?: boolean;
|
||||
moreThanOneTeam: boolean;
|
||||
teamsCount: number;
|
||||
};
|
||||
|
||||
const getTabletWidth = (moreThanOneTeam: boolean) => {
|
||||
return TABLET_SIDEBAR_WIDTH - (moreThanOneTeam ? TEAM_SIDEBAR_WIDTH : 0);
|
||||
const getTabletWidth = (teamsCount: number) => {
|
||||
return TABLET_SIDEBAR_WIDTH - (teamsCount > 1 ? TEAM_SIDEBAR_WIDTH : 0);
|
||||
};
|
||||
|
||||
const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: ChannelListProps) => {
|
||||
const CategoriesList = ({channelsCount, iconPad, isCRTEnabled, teamsCount}: ChannelListProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const {width} = useWindowDimensions();
|
||||
const isTablet = useIsTablet();
|
||||
const tabletWidth = useSharedValue(isTablet ? getTabletWidth(moreThanOneTeam) : 0);
|
||||
const tabletWidth = useSharedValue(isTablet ? getTabletWidth(teamsCount) : 0);
|
||||
|
||||
useEffect(() => {
|
||||
if (isTablet) {
|
||||
tabletWidth.value = getTabletWidth(moreThanOneTeam);
|
||||
tabletWidth.value = getTabletWidth(teamsCount);
|
||||
}
|
||||
}, [isTablet && moreThanOneTeam]);
|
||||
}, [isTablet && teamsCount]);
|
||||
|
||||
const tabletStyle = useAnimatedStyle(() => {
|
||||
if (!isTablet) {
|
||||
@@ -61,7 +61,7 @@ const CategoriesList = ({hasChannels, iconPad, isCRTEnabled, moreThanOneTeam}: C
|
||||
}, [isTablet, width]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (!hasChannels) {
|
||||
if (channelsCount < 1) {
|
||||
return (<LoadChannelsError/>);
|
||||
}
|
||||
|
||||
|
||||
@@ -29,10 +29,9 @@ import Servers from './servers';
|
||||
import type {LaunchType} from '@typings/launch';
|
||||
|
||||
type ChannelProps = {
|
||||
hasChannels: boolean;
|
||||
channelsCount: number;
|
||||
isCRTEnabled: boolean;
|
||||
hasTeams: boolean;
|
||||
hasMoreThanOneTeam: boolean;
|
||||
teamsCount: number;
|
||||
isLicensed: boolean;
|
||||
showToS: boolean;
|
||||
launchType: LaunchType;
|
||||
@@ -127,10 +126,10 @@ const ChannelListScreen = (props: ChannelProps) => {
|
||||
}, [theme, insets.top]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!props.hasTeams) {
|
||||
if (!props.teamsCount) {
|
||||
resetToTeams();
|
||||
}
|
||||
}, [Boolean(props.hasTeams)]);
|
||||
}, [Boolean(props.teamsCount)]);
|
||||
|
||||
useEffect(() => {
|
||||
const back = BackHandler.addEventListener('hardwareBackPress', handleBackPress);
|
||||
@@ -177,13 +176,13 @@ const ChannelListScreen = (props: ChannelProps) => {
|
||||
>
|
||||
<TeamSidebar
|
||||
iconPad={canAddOtherServers}
|
||||
hasMoreThanOneTeam={props.hasMoreThanOneTeam}
|
||||
teamsCount={props.teamsCount}
|
||||
/>
|
||||
<CategoriesList
|
||||
iconPad={canAddOtherServers && !props.hasMoreThanOneTeam}
|
||||
iconPad={canAddOtherServers && props.teamsCount <= 1}
|
||||
isCRTEnabled={props.isCRTEnabled}
|
||||
moreThanOneTeam={props.hasMoreThanOneTeam}
|
||||
hasChannels={props.hasChannels}
|
||||
teamsCount={props.teamsCount}
|
||||
channelsCount={props.channelsCount}
|
||||
/>
|
||||
{isTablet &&
|
||||
<AdditionalTabletView/>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
import {withDatabase} from '@nozbe/watermelondb/DatabaseProvider';
|
||||
import withObservables from '@nozbe/with-observables';
|
||||
import {of as of$} from 'rxjs';
|
||||
import {distinctUntilChanged, switchMap} from 'rxjs/operators';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {queryAllMyChannelsForTeam} from '@queries/servers/channel';
|
||||
import {observeCurrentTeamId, observeLicense} from '@queries/servers/system';
|
||||
@@ -21,22 +21,11 @@ const enhanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
||||
switchMap((lcs) => (lcs ? of$(lcs.IsLicensed === 'true') : of$(false))),
|
||||
);
|
||||
|
||||
const teamsCount = queryMyTeams(database).observeCount(false);
|
||||
|
||||
return {
|
||||
isCRTEnabled: observeIsCRTEnabled(database),
|
||||
hasTeams: teamsCount.pipe(
|
||||
switchMap((v) => of$(v > 0)),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
hasMoreThanOneTeam: teamsCount.pipe(
|
||||
switchMap((v) => of$(v > 1)),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
hasChannels: observeCurrentTeamId(database).pipe(
|
||||
teamsCount: queryMyTeams(database).observeCount(false),
|
||||
channelsCount: observeCurrentTeamId(database).pipe(
|
||||
switchMap((id) => (id ? queryAllMyChannelsForTeam(database, id).observeCount(false) : of$(0))),
|
||||
switchMap((v) => of$(v > 0)),
|
||||
distinctUntilChanged(),
|
||||
),
|
||||
isLicensed,
|
||||
showToS: observeShowToS(database),
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import {General, Preferences} from '@constants';
|
||||
import {DMS_CATEGORY} from '@constants/categories';
|
||||
import {getPreferenceAsBool} from '@helpers/api/preference';
|
||||
import {getPreferenceAsBool, getPreferenceValue} from '@helpers/api/preference';
|
||||
import {isDMorGM} from '@utils/channel';
|
||||
import {getUserIdFromChannelName} from '@utils/user';
|
||||
|
||||
@@ -42,18 +42,15 @@ export const filterAutoclosedDMs = (
|
||||
// Only autoclose DMs that haven't been assigned to a category
|
||||
return channelsWithMyChannel;
|
||||
}
|
||||
const prefMap = preferences.reduce((acc, v) => {
|
||||
const existing = acc.get(v.name);
|
||||
acc.set(v.name, Math.max((v.value as unknown as number) || 0, existing || 0));
|
||||
return acc;
|
||||
}, new Map<string, number>());
|
||||
|
||||
const getLastViewedAt = (cwm: ChannelWithMyChannel) => {
|
||||
// The server only ever sets the last_viewed_at to the time of the last post in channel, so we may need
|
||||
// to use the preferences added for the previous version of autoclosing DMs.
|
||||
const id = cwm.channel.id;
|
||||
return Math.max(
|
||||
cwm.myChannel.lastViewedAt,
|
||||
prefMap.get(id) || 0,
|
||||
getPreferenceValue<number>(preferences, Preferences.CATEGORIES.CHANNEL_APPROXIMATE_VIEW_TIME, id, 0),
|
||||
getPreferenceValue<number>(preferences, Preferences.CATEGORIES.CHANNEL_OPEN_TIME, id, 0),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -181,7 +178,7 @@ export const sortChannels = (sorting: CategorySorting, channelsWithMyChannel: Ch
|
||||
}).map((cwm) => cwm.channel);
|
||||
} else if (sorting === 'manual') {
|
||||
return channelsWithMyChannel.sort((cwmA, cwmB) => {
|
||||
return cwmA.sortOrder - cwmB.sortOrder;
|
||||
return cwmB.sortOrder - cwmA.sortOrder;
|
||||
}).map((cwm) => cwm.channel);
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
|
||||
import {isMinimumServerVersion} from './helpers';
|
||||
|
||||
export function hasReliableWebsocket(version?: string, reliableWebsocketsConfig?: string) {
|
||||
if (version && isMinimumServerVersion(version, 6, 5)) {
|
||||
export function hasReliableWebsocket(config: ClientConfig) {
|
||||
if (isMinimumServerVersion(config.Version, 6, 5)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return reliableWebsocketsConfig === 'true';
|
||||
return config.EnableReliableWebSockets === 'true';
|
||||
}
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
{
|
||||
"ServiceSettings": {
|
||||
"SiteURL": "http://localhost:8065",
|
||||
"WebsocketURL": "",
|
||||
"LicenseFileLocation": "",
|
||||
"ListenAddress": ":8065",
|
||||
"ConnectionSecurity": "",
|
||||
"TLSCertFile": "",
|
||||
"TLSKeyFile": "",
|
||||
"TLSMinVer": "1.2",
|
||||
"TLSStrictTransport": false,
|
||||
"TLSStrictTransportMaxAge": 63072000,
|
||||
"TLSOverwriteCiphers": [],
|
||||
"UseLetsEncrypt": false,
|
||||
"Forward80To443": false,
|
||||
"TrustedProxyIPHeader": [],
|
||||
"ReadTimeout": 300,
|
||||
"WriteTimeout": 300,
|
||||
"IdleTimeout": 300,
|
||||
"MaximumLoginAttempts": 10,
|
||||
"GoroutineHealthThreshold": -1,
|
||||
"GoogleDeveloperKey": "",
|
||||
"EnableOAuthServiceProvider": false,
|
||||
"EnableIncomingWebhooks": true,
|
||||
"EnableOutgoingWebhooks": true,
|
||||
"EnableCommands": true,
|
||||
"EnableOnlyAdminIntegrations": true,
|
||||
"EnablePostUsernameOverride": false,
|
||||
"EnablePostIconOverride": false,
|
||||
"EnableLinkPreviews": false,
|
||||
"EnableTesting": false,
|
||||
"EnableDeveloper": false,
|
||||
"EnableOpenTracing": false,
|
||||
"EnableSecurityFixAlert": true,
|
||||
"EnableInsecureOutgoingConnections": false,
|
||||
"AllowedUntrustedInternalConnections": "localhost",
|
||||
"EnableMultifactorAuthentication": false,
|
||||
"EnforceMultifactorAuthentication": false,
|
||||
"EnableUserAccessTokens": false,
|
||||
"AllowCorsFrom": "",
|
||||
"CorsExposedHeaders": "",
|
||||
"CorsAllowCredentials": false,
|
||||
"CorsDebug": false,
|
||||
"AllowCookiesForSubdomains": false,
|
||||
"ExtendSessionLengthWithActivity": true,
|
||||
"SessionLengthWebInDays": 30,
|
||||
"SessionLengthMobileInDays": 30,
|
||||
"SessionLengthSSOInDays": 30,
|
||||
"SessionCacheInMinutes": 10,
|
||||
"SessionIdleTimeoutInMinutes": 43200,
|
||||
"WebsocketSecurePort": 443,
|
||||
"WebsocketPort": 80,
|
||||
"WebserverMode": "gzip",
|
||||
"EnableCustomEmoji": false,
|
||||
"EnableEmojiPicker": true,
|
||||
"EnableGifPicker": false,
|
||||
"GfycatApiKey": "2_KtH_W5",
|
||||
"GfycatApiSecret": "3wLVZPiswc3DnaiaFoLkDvB4X0IV6CpMkj4tf2inJRsBY6-FnkT08zGmppWFgeof",
|
||||
"RestrictCustomEmojiCreation": "all",
|
||||
"RestrictPostDelete": "all",
|
||||
"AllowEditPost": "always",
|
||||
"PostEditTimeLimit": -1,
|
||||
"TimeBetweenUserTypingUpdatesMilliseconds": 5000,
|
||||
"EnablePostSearch": true,
|
||||
"MinimumHashtagLength": 3,
|
||||
"EnableUserTypingMessages": true,
|
||||
"EnableChannelViewedMessages": true,
|
||||
"EnableUserStatuses": true,
|
||||
"ExperimentalEnableAuthenticationTransfer": true,
|
||||
"CloseUnusedDirectMessages": false,
|
||||
"EnablePreviewFeatures": true,
|
||||
"ExperimentalEnableDefaultChannelLeaveJoinMessages": true,
|
||||
"ExperimentalGroupUnreadChannels": "disabled",
|
||||
"ExperimentalChannelOrganization": false,
|
||||
"ExperimentalChannelSidebarOrganization": "disabled",
|
||||
"EnableAPIChannelDeletion": true,
|
||||
"EnableAPITeamDeletion": true,
|
||||
"ExperimentalEnableHardenedMode": false,
|
||||
"DisableLegacyMFA": true,
|
||||
"ExperimentalStrictCSRFEnforcement": false,
|
||||
"EnableEmailInvitations": true,
|
||||
"DisableBotsWhenOwnerIsDeactivated": true,
|
||||
"EnableBotAccountCreation": true,
|
||||
"EnableSVGs": true,
|
||||
"EnableLatex": true,
|
||||
"EnableInlineLatex": true,
|
||||
"CollapsedThreads": "always_on"
|
||||
},
|
||||
"TeamSettings": {
|
||||
"SiteName": "Mattermost",
|
||||
"MaxUsersPerTeam": 2000,
|
||||
"EnableTeamCreation": true,
|
||||
"EnableUserCreation": true,
|
||||
"EnableOpenServer": true,
|
||||
"EnableUserDeactivation": false,
|
||||
"EnableCustomUserStatuses": true,
|
||||
"RestrictCreationToDomains": "",
|
||||
"EnableCustomBrand": false,
|
||||
"CustomBrandText": "",
|
||||
"CustomDescriptionText": "",
|
||||
"RestrictDirectMessage": "any",
|
||||
"RestrictTeamInvite": "all",
|
||||
"RestrictPublicChannelManagement": "all",
|
||||
"RestrictPrivateChannelManagement": "all",
|
||||
"RestrictPublicChannelCreation": "all",
|
||||
"RestrictPrivateChannelCreation": "all",
|
||||
"RestrictPublicChannelDeletion": "all",
|
||||
"RestrictPrivateChannelDeletion": "all",
|
||||
"RestrictPrivateChannelManageMembers": "all",
|
||||
"EnableXToLeaveChannelsFromLHS": false,
|
||||
"UserStatusAwayTimeout": 300,
|
||||
"MaxChannelsPerTeam": 2000,
|
||||
"MaxNotificationsPerChannel": 1000,
|
||||
"EnableConfirmNotificationsToChannel": true,
|
||||
"TeammateNameDisplay": "username",
|
||||
"ExperimentalViewArchivedChannels": true,
|
||||
"ExperimentalEnableAutomaticReplies": true,
|
||||
"ExperimentalHideTownSquareinLHS": false,
|
||||
"ExperimentalTownSquareIsReadOnly": false,
|
||||
"LockTeammateNameDisplay": false,
|
||||
"ExperimentalPrimaryTeam": "",
|
||||
"ExperimentalDefaultChannels": []
|
||||
},
|
||||
"ClientRequirements": {
|
||||
"AndroidLatestVersion": "",
|
||||
"AndroidMinVersion": "",
|
||||
"DesktopLatestVersion": "",
|
||||
"DesktopMinVersion": "",
|
||||
"IosLatestVersion": "",
|
||||
"IosMinVersion": ""
|
||||
},
|
||||
"LogSettings": {
|
||||
"EnableConsole": true
|
||||
},
|
||||
"NotificationLogSettings": {
|
||||
"EnableConsole": true
|
||||
},
|
||||
"PasswordSettings": {
|
||||
"MinimumLength": 5,
|
||||
"Lowercase": false,
|
||||
"Number": false,
|
||||
"Uppercase": false,
|
||||
"Symbol": false,
|
||||
"Enable": false
|
||||
},
|
||||
"FileSettings": {
|
||||
"EnableFileAttachments": true,
|
||||
"EnableMobileUpload": true,
|
||||
"EnableMobileDownload": true,
|
||||
"MaxFileSize": 52428800,
|
||||
"DriverName": "local",
|
||||
"Directory": "./data/",
|
||||
"EnablePublicLink": false,
|
||||
"PublicLinkSalt": ""
|
||||
},
|
||||
"EmailSettings": {
|
||||
"EnableSignUpWithEmail": true,
|
||||
"EnableSignInWithEmail": true,
|
||||
"EnableSignInWithUsername": true,
|
||||
"SendEmailNotifications": true,
|
||||
"UseChannelInEmailNotifications": false,
|
||||
"RequireEmailVerification": false,
|
||||
"FeedbackName": "",
|
||||
"FeedbackEmail": "test@example.com",
|
||||
"ReplyToAddress": "test@example.com",
|
||||
"FeedbackOrganization": "",
|
||||
"EnableSMTPAuth": false,
|
||||
"SMTPUsername": "",
|
||||
"SMTPPassword": "",
|
||||
"SMTPServer": "localhost",
|
||||
"SMTPPort": "10025",
|
||||
"SMTPServerTimeout": 10,
|
||||
"ConnectionSecurity": "",
|
||||
"SendPushNotifications": true,
|
||||
"PushNotificationServer": "https://push-test.mattermost.com",
|
||||
"PushNotificationContents": "generic",
|
||||
"EnableEmailBatching": false,
|
||||
"EmailBatchingBufferSize": 256,
|
||||
"EmailBatchingInterval": 30,
|
||||
"EnablePreviewModeBanner": true,
|
||||
"SkipServerCertificateVerification": false,
|
||||
"EmailNotificationContentsType": "full",
|
||||
"LoginButtonColor": "#0000",
|
||||
"LoginButtonBorderColor": "#2389D7",
|
||||
"LoginButtonTextColor": "#2389D7"
|
||||
},
|
||||
"RateLimitSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"PrivacySettings": {
|
||||
"ShowEmailAddress": true,
|
||||
"ShowFullName": true
|
||||
},
|
||||
"SupportSettings": {
|
||||
"TermsOfServiceLink": "https://mattermost.com/terms-of-use/",
|
||||
"PrivacyPolicyLink": "https://mattermost.com/privacy-policy/",
|
||||
"AboutLink": "https://mattermost.com/default-about/",
|
||||
"HelpLink": "https://mattermost.com/default-help/",
|
||||
"ReportAProblemLink": "https://mattermost.com/default-report-a-problem/",
|
||||
"SupportEmail": "feedback@mattermost.com",
|
||||
"CustomTermsOfServiceEnabled": false,
|
||||
"CustomTermsOfServiceReAcceptancePeriod": 365,
|
||||
"EnableAskCommunityLink": true
|
||||
},
|
||||
"AnnouncementSettings": {
|
||||
"EnableBanner": false
|
||||
},
|
||||
"ThemeSettings": {
|
||||
"EnableThemeSelection": true,
|
||||
"DefaultTheme": "default",
|
||||
"AllowCustomThemes": true,
|
||||
"AllowedThemes": []
|
||||
},
|
||||
"GitLabSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"GoogleSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"Office365Settings": {
|
||||
"Enable": false
|
||||
},
|
||||
"LdapSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"ComplianceSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"LocalizationSettings": {
|
||||
"DefaultServerLocale": "en",
|
||||
"DefaultClientLocale": "en",
|
||||
"AvailableLocales": ""
|
||||
},
|
||||
"SamlSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"NativeAppSettings": {
|
||||
"AppDownloadLink": "https://mattermost.com/download/#mattermostApps",
|
||||
"AndroidAppDownloadLink": "https://mattermost.com/mattermost-android-app/",
|
||||
"IosAppDownloadLink": "https://mattermost.com/mattermost-ios-app/"
|
||||
},
|
||||
"ClusterSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"MetricsSettings": {
|
||||
"Enable": false
|
||||
},
|
||||
"ExperimentalSettings": {
|
||||
"ClientSideCertEnable": false,
|
||||
"ClientSideCertCheck": "secondary",
|
||||
"LinkMetadataTimeoutMilliseconds": 5000,
|
||||
"RestrictSystemAdmin": false,
|
||||
"UseNewSAMLLibrary": false
|
||||
},
|
||||
"AnalyticsSettings": {
|
||||
"MaxUsersForStatistics": 2500
|
||||
},
|
||||
"ElasticsearchSettings": {
|
||||
"ConnectionUrl": "http://localhost:9200",
|
||||
"Username": "elastic",
|
||||
"Password": "changeme",
|
||||
"EnableIndexing": false,
|
||||
"EnableSearching": false,
|
||||
"EnableAutocomplete": false,
|
||||
"Sniff": true
|
||||
},
|
||||
"DataRetentionSettings": {
|
||||
"EnableMessageDeletion": false,
|
||||
"EnableFileDeletion": false,
|
||||
"MessageRetentionDays": 365,
|
||||
"FileRetentionDays": 365,
|
||||
"DeletionJobStartTime": "02:00"
|
||||
},
|
||||
"MessageExportSettings": {
|
||||
"EnableExport": false
|
||||
},
|
||||
"JobSettings": {
|
||||
"RunJobs": true,
|
||||
"RunScheduler": true
|
||||
},
|
||||
"PluginSettings": {
|
||||
"Enable": false,
|
||||
"PluginStates": {
|
||||
"com.mattermost.calls": {
|
||||
"Enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"DisplaySettings": {
|
||||
"CustomUrlSchemes": [],
|
||||
"ExperimentalTimezone": true
|
||||
},
|
||||
"GuestAccountsSettings": {
|
||||
"Enable": true,
|
||||
"AllowEmailAccounts": true,
|
||||
"EnforceMultifactorAuthentication": false,
|
||||
"RestrictCreationToDomains": ""
|
||||
},
|
||||
"ImageProxySettings": {
|
||||
"Enable": true,
|
||||
"ImageProxyType": "local",
|
||||
"RemoteImageProxyURL": "",
|
||||
"RemoteImageProxyOptions": ""
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,10 @@
|
||||
|
||||
import path from 'path';
|
||||
|
||||
import {ldapPort, ldapServer} from '@support/test_config';
|
||||
import merge from 'deepmerge';
|
||||
import jestExpect from 'expect';
|
||||
|
||||
import client from './client';
|
||||
import {apiUploadFile, getResponseFromError} from './common';
|
||||
import defaultServerConfig from './default_config.json';
|
||||
|
||||
// ****************************************************************
|
||||
// System
|
||||
@@ -152,29 +149,6 @@ export const apiRequireSMTPServer = async (baseUrl: string) => {
|
||||
jestExpect(status).toEqual(200);
|
||||
};
|
||||
|
||||
/**
|
||||
* Update configuration.
|
||||
* See https://api.mattermost.com/#operation/UpdateConfig
|
||||
* @param {string} baseUrl - the base server URL
|
||||
* @param {Object} newConfig - specific config to update
|
||||
* @return {Object} returns {config} on success or {error, status} on error
|
||||
*/
|
||||
export const apiUpdateConfig = async (baseUrl: string, newConfig: any = {}): Promise<any> => {
|
||||
try {
|
||||
const {config: currentConfig} = await apiGetConfig(baseUrl);
|
||||
const config = merge.all([currentConfig, getDefaultConfig(baseUrl), newConfig]);
|
||||
|
||||
const response = await client.put(
|
||||
`${baseUrl}/api/v4/config`,
|
||||
config,
|
||||
);
|
||||
|
||||
return {config: response.data};
|
||||
} catch (err) {
|
||||
return getResponseFromError(err);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Upload server license with file expected at "/detox/e2e/support/fixtures/mattermost-license.txt"
|
||||
* See https://api.mattermost.com/#operation/UploadLicenseFile
|
||||
@@ -209,18 +183,6 @@ export const getClientLicense = async (baseUrl: string): Promise<any> => {
|
||||
return {license: out.license};
|
||||
};
|
||||
|
||||
export const getDefaultConfig = (siteUrl: string) => {
|
||||
const fromEnv = {
|
||||
LdapSettings: {
|
||||
LdapServer: ldapServer,
|
||||
LdapPort: ldapPort,
|
||||
},
|
||||
ServiceSettings: {SiteURL: siteUrl},
|
||||
};
|
||||
|
||||
return merge(defaultServerConfig, fromEnv);
|
||||
};
|
||||
|
||||
export const System = {
|
||||
apiCheckSystemHealth,
|
||||
apiEmailTest,
|
||||
@@ -230,10 +192,8 @@ export const System = {
|
||||
apiRequireLicense,
|
||||
apiRequireLicenseForFeature,
|
||||
apiRequireSMTPServer,
|
||||
apiUpdateConfig,
|
||||
apiUploadLicense,
|
||||
getClientLicense,
|
||||
getDefaultConfig,
|
||||
};
|
||||
|
||||
export default System;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {isAndroid, timeouts, wait} from '@support/utils';
|
||||
import {isIos, timeouts, wait} from '@support/utils';
|
||||
import {expect} from 'detox';
|
||||
|
||||
class ServerScreen {
|
||||
@@ -47,7 +47,10 @@ class ServerScreen {
|
||||
await this.serverUrlInput.replaceText(serverUrl);
|
||||
await this.serverUrlInput.tapReturnKey();
|
||||
await this.serverDisplayNameInput.replaceText(serverDisplayName);
|
||||
await this.tapConnectButton();
|
||||
await this.serverDisplayNameInput.tapReturnKey();
|
||||
if (isIos()) {
|
||||
await this.tapConnectButton();
|
||||
}
|
||||
};
|
||||
|
||||
close = async () => {
|
||||
@@ -56,10 +59,6 @@ class ServerScreen {
|
||||
};
|
||||
|
||||
tapConnectButton = async () => {
|
||||
if (isAndroid()) {
|
||||
await device.pressBack();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
}
|
||||
await this.connectButton.tap();
|
||||
await wait(timeouts.ONE_SEC);
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@ beforeAll(async () => {
|
||||
// Login as sysadmin and reset server configuration
|
||||
await System.apiCheckSystemHealth(siteOneUrl);
|
||||
await User.apiAdminLogin(siteOneUrl);
|
||||
await System.apiUpdateConfig(siteOneUrl);
|
||||
await Plugin.apiDisableNonPrepackagedPlugins(siteOneUrl);
|
||||
|
||||
await device.launchApp({
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('Teams - Invite', () => {
|
||||
await expect(Invite.teamIcon).toBeVisible();
|
||||
|
||||
// * Verify default Selection
|
||||
await expect(Invite.screenSelection).toBeVisible();
|
||||
await waitFor(Invite.screenSelection).toBeVisible().withTimeout(timeouts.TWO_SEC);
|
||||
|
||||
// * Verify Server data
|
||||
await expect(Invite.serverDisplayName).toHaveText(serverOneDisplayName);
|
||||
@@ -93,10 +93,9 @@ describe('Teams - Invite', () => {
|
||||
});
|
||||
|
||||
it('MM-T5221 - should be able to share a URL invite to the team', async () => {
|
||||
// # Tap on Share link
|
||||
await Invite.shareLinkButton.tap();
|
||||
|
||||
if (isIos()) {
|
||||
// # Tap on Share link
|
||||
await Invite.shareLinkButton.tap();
|
||||
const dialog = systemDialog(`Join the ${testTeam.display_name} team`);
|
||||
|
||||
// * Verify share dialog is open
|
||||
@@ -104,28 +103,28 @@ describe('Teams - Invite', () => {
|
||||
|
||||
// # Close share dialog
|
||||
await dialog.swipe('down');
|
||||
}
|
||||
} // no support for Android system dialogs by detox yet. See https://github.com/wix/Detox/issues/3227
|
||||
});
|
||||
|
||||
it('MM-T5361 - should show no results item in search list', async () => {
|
||||
const noUser = 'qwertyuiop';
|
||||
|
||||
// # Search for a non existent user
|
||||
// # Search for a non-existent user
|
||||
await Invite.searchBarInput.replaceText(noUser);
|
||||
|
||||
// * Validate no results item in search list
|
||||
await expect(Invite.getSearchListNoResults(noUser)).toBeVisible();
|
||||
await waitFor(Invite.getSearchListNoResults(noUser)).toBeVisible().withTimeout(timeouts.TWO_SEC);
|
||||
await expect(Invite.getSearchListNoResultsText(noUser)).toHaveText(noUser);
|
||||
});
|
||||
|
||||
it('MM-T5362 - should be able to send email invite', async () => {
|
||||
const noUserEmailFormat = 'qwerty@ui.op';
|
||||
|
||||
// # Search for a non existent user with email format
|
||||
// # Search for a non-existent user with email format
|
||||
await Invite.searchBarInput.replaceText(noUserEmailFormat);
|
||||
|
||||
// * Validate email invite item in search list
|
||||
await expect(Invite.getSearchListTextItem(noUserEmailFormat)).toBeVisible();
|
||||
await waitFor(Invite.getSearchListTextItem(noUserEmailFormat)).toBeVisible().withTimeout(timeouts.TWO_SEC);
|
||||
await expect(Invite.getSearchListTextItemText(noUserEmailFormat)).toHaveText(noUserEmailFormat);
|
||||
|
||||
// # Select email invite item
|
||||
@@ -139,7 +138,7 @@ describe('Teams - Invite', () => {
|
||||
await Invite.sendButton.tap();
|
||||
|
||||
// * Validate summary report sent
|
||||
await expect(Invite.screenSummary).toBeVisible();
|
||||
await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||
await expect(Invite.getSummaryReportSent()).toBeVisible();
|
||||
await expect(Invite.getSummaryReportNotSent()).not.toExist();
|
||||
await expect(Invite.getSummaryReportTextItem(noUserEmailFormat)).toBeVisible();
|
||||
@@ -149,11 +148,11 @@ describe('Teams - Invite', () => {
|
||||
it('MM-T5363 - should be able to send user invite', async () => {
|
||||
const username = ` @${testUser1.username}`;
|
||||
|
||||
// # Search for a existent user
|
||||
// # Search for an existent user
|
||||
await Invite.searchBarInput.replaceText(testUser1.username);
|
||||
|
||||
// * Validate user item in search list
|
||||
await expect(Invite.getSearchListUserItem(testUser1.id)).toBeVisible();
|
||||
await waitFor(Invite.getSearchListUserItem(testUser1.id)).toBeVisible().withTimeout(timeouts.TWO_SEC);
|
||||
await expect(Invite.getSearchListUserItemText(testUser1.id)).toHaveText(username);
|
||||
|
||||
// # Select user item
|
||||
@@ -167,7 +166,7 @@ describe('Teams - Invite', () => {
|
||||
await Invite.sendButton.tap();
|
||||
|
||||
// * Validate summary report sent
|
||||
await expect(Invite.screenSummary).toBeVisible();
|
||||
await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||
await expect(Invite.getSummaryReportSent()).toBeVisible();
|
||||
await expect(Invite.getSummaryReportNotSent()).not.toExist();
|
||||
await expect(Invite.getSummaryReportUserItem(testUser1.id)).toBeVisible();
|
||||
@@ -177,11 +176,11 @@ describe('Teams - Invite', () => {
|
||||
it('MM-T5364 - should not be able to send user invite to user already in team', async () => {
|
||||
const username = ` @${testUser1.username}`;
|
||||
|
||||
// # Search for a existent user already in team
|
||||
// # Search for an existent user already in team
|
||||
await Invite.searchBarInput.replaceText(testUser1.username);
|
||||
|
||||
// * Validate user item in search list
|
||||
await expect(Invite.getSearchListUserItem(testUser1.id)).toBeVisible();
|
||||
await waitFor(Invite.getSearchListUserItem(testUser1.id)).toBeVisible().withTimeout(timeouts.TWO_SEC);
|
||||
|
||||
// # Select user item
|
||||
await Invite.getSearchListUserItem(testUser1.id).tap();
|
||||
@@ -206,11 +205,11 @@ describe('Teams - Invite', () => {
|
||||
const username1 = ` @${testUser1.username}`;
|
||||
const username2 = ` @${testUser2.username}`;
|
||||
|
||||
// # Search for a existent user
|
||||
// # Search for an existent user
|
||||
await Invite.searchBarInput.replaceText(testUser2.username);
|
||||
|
||||
// * Validate user item in search list
|
||||
await expect(Invite.getSearchListUserItem(testUser2.id)).toBeVisible();
|
||||
await waitFor(Invite.getSearchListUserItem(testUser2.id)).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||
|
||||
// # Select user item
|
||||
await Invite.getSearchListUserItem(testUser2.id).tap();
|
||||
@@ -234,7 +233,7 @@ describe('Teams - Invite', () => {
|
||||
await Invite.sendButton.tap();
|
||||
|
||||
// * Validate summary
|
||||
await expect(Invite.screenSummary).toBeVisible();
|
||||
await waitFor(Invite.screenSummary).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||
|
||||
// * Validate summary report not sent
|
||||
await expect(Invite.getSummaryReportNotSent()).toBeVisible();
|
||||
@@ -242,7 +241,7 @@ describe('Teams - Invite', () => {
|
||||
await expect(Invite.getSummaryReportUserItemText(testUser1.id)).toHaveText(username1);
|
||||
|
||||
// * Validate summary report sent
|
||||
await expect(Invite.getSummaryReportSent()).toBeVisible();
|
||||
await waitFor(Invite.getSummaryReportSent()).toBeVisible().withTimeout(timeouts.TEN_SEC);
|
||||
await expect(Invite.getSummaryReportUserItem(testUser2.id)).toBeVisible();
|
||||
await expect(Invite.getSummaryReportUserItemText(testUser2.id)).toHaveText(username2);
|
||||
});
|
||||
|
||||
@@ -1128,7 +1128,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 459;
|
||||
CURRENT_PROJECT_VERSION = 460;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -1172,7 +1172,7 @@
|
||||
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CURRENT_PROJECT_VERSION = 459;
|
||||
CURRENT_PROJECT_VERSION = 460;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
ENABLE_BITCODE = NO;
|
||||
HEADER_SEARCH_PATHS = (
|
||||
@@ -1315,7 +1315,7 @@
|
||||
CODE_SIGN_IDENTITY = "iPhone Developer";
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 459;
|
||||
CURRENT_PROJECT_VERSION = 460;
|
||||
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
@@ -1366,7 +1366,7 @@
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
COPY_PHASE_STRIP = NO;
|
||||
CURRENT_PROJECT_VERSION = 459;
|
||||
CURRENT_PROJECT_VERSION = 460;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu11;
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
</dict>
|
||||
</array>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>459</string>
|
||||
<string>460</string>
|
||||
<key>ITSAppUsesNonExemptEncryption</key>
|
||||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>459</string>
|
||||
<string>460</string>
|
||||
<key>UIAppFonts</key>
|
||||
<array>
|
||||
<string>OpenSans-Bold.ttf</string>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>2.1.0</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>459</string>
|
||||
<string>460</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
||||
@@ -19,6 +19,6 @@ module.exports = {
|
||||
'\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': '<rootDir>/test/file_transformer.js',
|
||||
},
|
||||
transformIgnorePatterns: [
|
||||
'node_modules/(?!(@react-native|react-native)|jail-monkey|@sentry/react-native|react-clone-referenced-element|@react-native-community|react-navigation|@react-navigation/.*|validator|react-syntax-highlighter/.*|hast-util-from-selector|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|zwitch)',
|
||||
'node_modules/(?!(@react-native|react-native)|jail-monkey|@sentry/react-native|react-clone-referenced-element|@react-native-community|react-navigation|@react-navigation/.*|validator|react-syntax-highlighter/.*|hast-util-from-selector|hastscript|property-information|hast-util-parse-selector|space-separated-tokens|comma-separated-tokens|zwitch|@mmcalls/common)',
|
||||
],
|
||||
};
|
||||
|
||||
39
package-lock.json
generated
39
package-lock.json
generated
@@ -19,10 +19,11 @@
|
||||
"@gorhom/bottom-sheet": "4.4.5",
|
||||
"@mattermost/compass-icons": "0.1.35",
|
||||
"@mattermost/react-native-emm": "1.3.5",
|
||||
"@mattermost/react-native-network-client": "1.3.2",
|
||||
"@mattermost/react-native-network-client": "1.3.1",
|
||||
"@mattermost/react-native-paste-input": "0.6.2",
|
||||
"@mattermost/react-native-turbo-log": "0.2.3",
|
||||
"@mattermost/react-native-turbo-mailer": "0.2.4",
|
||||
"@mmcalls/common": "https://gitpkg.now.sh/mattermost/mattermost-plugin-calls/webapp/packages/common?b5e89f3",
|
||||
"@msgpack/msgpack": "2.8.0",
|
||||
"@nozbe/watermelondb": "0.25.5",
|
||||
"@nozbe/with-observables": "1.4.1",
|
||||
@@ -124,6 +125,7 @@
|
||||
"@types/jest": "29.4.0",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/mime-db": "1.43.1",
|
||||
"@types/pako": "2.0.0",
|
||||
"@types/querystringify": "2.0.0",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-native-background-timer": "2.0.0",
|
||||
@@ -3241,9 +3243,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mattermost/react-native-network-client": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.2.tgz",
|
||||
"integrity": "sha512-3GFNzMXZWlIXXDYQLIJlKRf+HUZKP0F7mpZ1rSTgoTmUeFdqde4uRiU/L96COg34rAdeFRFrgpk0DxEnT7NiVg==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.1.tgz",
|
||||
"integrity": "sha512-DtwVLV/NUE6MkXOlVZG+4QJXou6nHMdmsxnP1+RqhOeSw5jJlQvxmQgxzxvxLpaWOag+wgB1zpDulGNbr/Cz6Q==",
|
||||
"dependencies": {
|
||||
"validator": "13.9.0",
|
||||
"zod": "3.20.6"
|
||||
@@ -3286,6 +3288,13 @@
|
||||
"react-native": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@mmcalls/common": {
|
||||
"name": "@calls/common",
|
||||
"version": "0.12.0",
|
||||
"resolved": "https://gitpkg.now.sh/mattermost/mattermost-plugin-calls/webapp/packages/common?b5e89f3",
|
||||
"integrity": "sha512-lz+T2HHQBfQDpgrI4CkrSNkmBjIaKxJGSxSZHSL0LQ8xAlRUBo3WMG5wJlwwzpxP6rixJjUnoyKBSo8Jn0wWQQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@msgpack/msgpack": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
|
||||
@@ -5957,6 +5966,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz",
|
||||
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA=="
|
||||
},
|
||||
"node_modules/@types/pako": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.0.tgz",
|
||||
"integrity": "sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
@@ -24297,9 +24312,9 @@
|
||||
"requires": {}
|
||||
},
|
||||
"@mattermost/react-native-network-client": {
|
||||
"version": "1.3.2",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.2.tgz",
|
||||
"integrity": "sha512-3GFNzMXZWlIXXDYQLIJlKRf+HUZKP0F7mpZ1rSTgoTmUeFdqde4uRiU/L96COg34rAdeFRFrgpk0DxEnT7NiVg==",
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@mattermost/react-native-network-client/-/react-native-network-client-1.3.1.tgz",
|
||||
"integrity": "sha512-DtwVLV/NUE6MkXOlVZG+4QJXou6nHMdmsxnP1+RqhOeSw5jJlQvxmQgxzxvxLpaWOag+wgB1zpDulGNbr/Cz6Q==",
|
||||
"requires": {
|
||||
"validator": "13.9.0",
|
||||
"zod": "3.20.6"
|
||||
@@ -24325,6 +24340,10 @@
|
||||
"integrity": "sha512-6W37UvLxg7vhP5YJXZfzKvPy4r9bozqSSuB4gbC2EjvWrGB4LfwKWljgw+Gb/E8x3ceMCib2SPPMz+thzs7DHw==",
|
||||
"requires": {}
|
||||
},
|
||||
"@mmcalls/common": {
|
||||
"version": "https://gitpkg.now.sh/mattermost/mattermost-plugin-calls/webapp/packages/common?b5e89f3",
|
||||
"integrity": "sha512-lz+T2HHQBfQDpgrI4CkrSNkmBjIaKxJGSxSZHSL0LQ8xAlRUBo3WMG5wJlwwzpxP6rixJjUnoyKBSo8Jn0wWQQ=="
|
||||
},
|
||||
"@msgpack/msgpack": {
|
||||
"version": "2.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-2.8.0.tgz",
|
||||
@@ -26328,6 +26347,12 @@
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz",
|
||||
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA=="
|
||||
},
|
||||
"@types/pako": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/pako/-/pako-2.0.0.tgz",
|
||||
"integrity": "sha512-10+iaz93qR5WYxTo+PMifD5TSxiOtdRaxBf7INGGXMQgTCu8Z/7GYWYFUOS3q/G0nE5boj1r4FEB+WSy7s5gbA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/parse-json": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.0.tgz",
|
||||
|
||||
@@ -16,10 +16,11 @@
|
||||
"@gorhom/bottom-sheet": "4.4.5",
|
||||
"@mattermost/compass-icons": "0.1.35",
|
||||
"@mattermost/react-native-emm": "1.3.5",
|
||||
"@mattermost/react-native-network-client": "1.3.2",
|
||||
"@mattermost/react-native-network-client": "1.3.1",
|
||||
"@mattermost/react-native-paste-input": "0.6.2",
|
||||
"@mattermost/react-native-turbo-log": "0.2.3",
|
||||
"@mattermost/react-native-turbo-mailer": "0.2.4",
|
||||
"@mmcalls/common": "https://gitpkg.now.sh/mattermost/mattermost-plugin-calls/webapp/packages/common?b5e89f3",
|
||||
"@msgpack/msgpack": "2.8.0",
|
||||
"@nozbe/watermelondb": "0.25.5",
|
||||
"@nozbe/with-observables": "1.4.1",
|
||||
@@ -121,6 +122,7 @@
|
||||
"@types/jest": "29.4.0",
|
||||
"@types/lodash": "4.14.191",
|
||||
"@types/mime-db": "1.43.1",
|
||||
"@types/pako": "2.0.0",
|
||||
"@types/querystringify": "2.0.0",
|
||||
"@types/react": "18.0.28",
|
||||
"@types/react-native-background-timer": "2.0.0",
|
||||
|
||||
Reference in New Issue
Block a user