Ensure full validation of Calls post props (#8279)
This commit is contained in:
parent
e4dc8dbbfe
commit
751bb2dfb1
3 changed files with 135 additions and 8 deletions
|
|
@ -9,6 +9,7 @@ import {Text, TouchableOpacity, View} from 'react-native';
|
|||
import {leaveCallConfirmation} from '@calls/actions/calls';
|
||||
import {leaveAndJoinWithAlert, showLimitRestrictedAlert} from '@calls/alerts';
|
||||
import {setJoiningChannelId} from '@calls/state';
|
||||
import {getCallPropsFromPost} from '@calls/utils';
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import FormattedRelativeTime from '@components/formatted_relative_time';
|
||||
import FormattedText from '@components/formatted_text';
|
||||
|
|
@ -164,13 +165,15 @@ export const CallsCustomMessage = ({
|
|||
leaveCallConfirmation(intl, otherParticipants, isAdmin, isHost, serverUrl, post.channelId);
|
||||
}, [intl, otherParticipants, isAdmin, isHost, serverUrl, post.channelId]);
|
||||
|
||||
const title = post.props.title ? (
|
||||
const callProps = getCallPropsFromPost(post);
|
||||
|
||||
const title = callProps.title ? (
|
||||
<Text style={style.title}>
|
||||
{post.props.title}
|
||||
{callProps.title}
|
||||
</Text>
|
||||
) : null;
|
||||
|
||||
if (post.props.end_at) {
|
||||
if (callProps.start_at > 0 && callProps.end_at > 0) {
|
||||
return (
|
||||
<>
|
||||
{title}
|
||||
|
|
@ -195,7 +198,7 @@ export const CallsCustomMessage = ({
|
|||
<Text>{' '}</Text>
|
||||
<FormattedTime
|
||||
style={style.timeText}
|
||||
value={post.props.end_at}
|
||||
value={callProps.end_at}
|
||||
isMilitaryTime={isMilitaryTime}
|
||||
timezone={timezone}
|
||||
/>
|
||||
|
|
@ -204,7 +207,7 @@ export const CallsCustomMessage = ({
|
|||
id={'mobile.calls_lasted'}
|
||||
style={style.timeText}
|
||||
defaultMessage={'Lasted {duration}'}
|
||||
values={{duration: moment.duration(post.props.end_at - post.props.start_at).humanize(false)}}
|
||||
values={{duration: moment.duration(callProps.end_at - callProps.start_at).humanize(false)}}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
|
|
@ -273,7 +276,7 @@ export const CallsCustomMessage = ({
|
|||
style={style.text}
|
||||
/>
|
||||
<FormattedRelativeTime
|
||||
value={post.props.start_at}
|
||||
value={callProps.start_at}
|
||||
updateIntervalInSeconds={1}
|
||||
style={style.timeText}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import assert from 'assert';
|
|||
import {type CallsConfigState, DefaultCallsConfig} from '@calls/types/calls';
|
||||
import {License} from '@constants';
|
||||
|
||||
import {getICEServersConfigs} from './utils';
|
||||
import {getICEServersConfigs, getCallPropsFromPost} from './utils';
|
||||
|
||||
describe('getICEServersConfigs', () => {
|
||||
it('backwards compatible case, no ICEServersConfigs present', () => {
|
||||
|
|
@ -97,3 +97,109 @@ describe('getICEServersConfigs', () => {
|
|||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getCallPropsFromPost', () => {
|
||||
test('undefined props', () => {
|
||||
const post = {} as Post;
|
||||
|
||||
const props = getCallPropsFromPost(post);
|
||||
|
||||
expect(props.title).toBe('');
|
||||
expect(props.start_at).toBe(0);
|
||||
expect(props.end_at).toBe(0);
|
||||
expect(props.recordings).toStrictEqual({});
|
||||
expect(props.recording_files.length).toBe(0);
|
||||
expect(props.transcriptions).toStrictEqual({});
|
||||
expect(props.participants.length).toBe(0);
|
||||
});
|
||||
|
||||
test('missing props', () => {
|
||||
const post = {
|
||||
props: {},
|
||||
} as Post;
|
||||
|
||||
const props = getCallPropsFromPost(post);
|
||||
|
||||
expect(props.title).toBe('');
|
||||
expect(props.start_at).toBe(0);
|
||||
expect(props.end_at).toBe(0);
|
||||
expect(props.recordings).toStrictEqual({});
|
||||
expect(props.recording_files.length).toBe(0);
|
||||
expect(props.transcriptions).toStrictEqual({});
|
||||
expect(props.participants.length).toBe(0);
|
||||
});
|
||||
|
||||
test('invalid props', () => {
|
||||
const callProps = {
|
||||
title: {},
|
||||
start_at: 'invalid',
|
||||
end_at: [],
|
||||
recordings: null,
|
||||
transcriptions: 45,
|
||||
participants: 'invalid',
|
||||
recording_files: 45,
|
||||
};
|
||||
|
||||
const post = {
|
||||
props: callProps as unknown,
|
||||
} as Post;
|
||||
|
||||
const props = getCallPropsFromPost(post);
|
||||
|
||||
expect(props.title).toBe('');
|
||||
expect(props.start_at).toBe(0);
|
||||
expect(props.end_at).toBe(0);
|
||||
expect(props.recordings).toStrictEqual({});
|
||||
expect(props.recording_files.length).toBe(0);
|
||||
expect(props.transcriptions).toStrictEqual({});
|
||||
expect(props.participants.length).toBe(0);
|
||||
});
|
||||
|
||||
test('full props', () => {
|
||||
const callProps = {
|
||||
title: 'call title',
|
||||
start_at: 1000,
|
||||
end_at: 1045,
|
||||
recordings: {
|
||||
recA: {
|
||||
file_id: 'recAFileID',
|
||||
post_id: 'recAPostID',
|
||||
tr_id: 'trA',
|
||||
},
|
||||
recB: {
|
||||
file_id: 'recBFileID',
|
||||
post_id: 'recBPostID',
|
||||
tr_id: 'trB',
|
||||
},
|
||||
},
|
||||
recording_files: ['recAFileID', 'recBFileID'],
|
||||
transcriptions: {
|
||||
trA: {
|
||||
file_id: 'trAFileID',
|
||||
post_id: 'trAPostID',
|
||||
rec_id: 'recA',
|
||||
},
|
||||
trB: {
|
||||
file_id: 'trBFileID',
|
||||
post_id: 'trBPostID',
|
||||
rec_id: 'recB',
|
||||
},
|
||||
},
|
||||
participants: ['userA', 'userB'],
|
||||
};
|
||||
|
||||
const post = {
|
||||
props: callProps as unknown,
|
||||
} as Post;
|
||||
|
||||
const props = getCallPropsFromPost(post);
|
||||
|
||||
expect(props.title).toBe(post.props.title);
|
||||
expect(props.start_at).toBe(post.props.start_at);
|
||||
expect(props.end_at).toBe(post.props.end_at);
|
||||
expect(props.recordings).toBe(post.props.recordings);
|
||||
expect(props.recording_files).toBe(post.props.recording_files);
|
||||
expect(props.transcriptions).toBe(post.props.transcriptions);
|
||||
expect(props.participants).toBe(post.props.participants);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import type {
|
|||
CallsTheme,
|
||||
CallsVersion,
|
||||
} from '@calls/types/calls';
|
||||
import type {CallsConfig, Caption} from '@mattermost/calls/lib/types';
|
||||
import type {CallsConfig, Caption, CallPostProps} from '@mattermost/calls/lib/types';
|
||||
import type PostModel from '@typings/database/models/servers/post';
|
||||
import type UserModel from '@typings/database/models/servers/user';
|
||||
import type {IntlShape} from 'react-intl';
|
||||
|
|
@ -246,3 +246,21 @@ export const getTranscriptionUri = (serverUrl: string, postProps?: Record<string
|
|||
selected: {type: SelectedTrackType.INDEX, value: 0},
|
||||
};
|
||||
};
|
||||
|
||||
function isValidObject(v: any) {
|
||||
return typeof v === 'object' && !Array.isArray(v) && v !== null;
|
||||
}
|
||||
|
||||
export function getCallPropsFromPost(post: PostModel | Post): CallPostProps {
|
||||
return {
|
||||
title: typeof post.props?.title === 'string' ? post.props.title : '',
|
||||
start_at: typeof post.props?.start_at === 'number' ? post.props.start_at : 0,
|
||||
end_at: typeof post.props?.end_at === 'number' ? post.props.end_at : 0,
|
||||
recordings: isValidObject(post.props?.recordings) ? post.props.recordings : {},
|
||||
transcriptions: isValidObject(post.props?.transcriptions) ? post.props.transcriptions : {},
|
||||
participants: Array.isArray(post.props?.participants) ? post.props.participants : [],
|
||||
|
||||
// DEPRECATED
|
||||
recording_files: Array.isArray(post.props?.recording_files) ? post.props.recording_files : [],
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue