agora-appbuilder-core 4.1.19-beta.4 → 4.1.20
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/template/bridge/rtc/webNg/RtcEngine.ts +320 -83
- package/template/customization-api/atoms.ts +0 -2
- package/template/customization-api/customEvents.ts +1 -4
- package/template/customization-api/index.ts +0 -1
- package/template/customization-api/typeDefinition.ts +1 -6
- package/template/defaultConfig.js +2 -2
- package/template/src/components/Controls.tsx +3 -1
- package/template/src/components/CustomSidePanel.tsx +13 -13
- package/template/src/components/RTMConfigure.tsx +0 -13
- package/template/src/components/livestream/LiveStreamContext.tsx +6 -2
- package/template/src/components/participants/UserActionMenuOptions.tsx +2 -29
- package/template/src/pages/Create.tsx +19 -31
- package/template/src/pages/video-call/ActionSheet.native.tsx +0 -2
- package/template/src/pages/video-call/ActionSheet.tsx +0 -2
- package/template/src/pages/video-call/ActionSheetHandle.tsx +0 -1
- package/template/src/pages/video-call/SidePanelHeader.tsx +1 -3
- package/template/src/pages/video-call/VideoCallScreen.tsx +0 -3
- package/template/src/pages/video-call/VideoRenderer.tsx +0 -4
- package/template/src/subComponents/LocalEndCall.tsx +1 -1
- package/template/src/subComponents/ScreenShareNotice.tsx +1 -1
- package/template/src/subComponents/SidePanelHeader.tsx +2 -16
- package/template/src/subComponents/recording/__tests__/recordingJourney.test.ts +25 -0
- package/template/src/subComponents/recording/__tests__/recordingLayoutResponse.test.ts +56 -0
- package/template/src/subComponents/recording/recordingJourney.ts +12 -0
- package/template/src/subComponents/recording/recordingLayoutResponse.ts +67 -0
- package/template/src/subComponents/recording/useRecording.tsx +28 -12
- package/template/src/subComponents/recording/useRecordingLayoutQuery.tsx +35 -45
- package/template/src/subComponents/screenshare/ScreenshareButton.tsx +1 -1
- package/template/src/subComponents/screenshare/ScreenshareConfigure.tsx +483 -31
- package/template/src/subComponents/screenshare/__tests__/screenshareJourney.test.ts +63 -0
- package/template/src/subComponents/screenshare/screenshareJourney.ts +43 -0
- package/template/src/subComponents/screenshare/useScreenshare.tsx +18 -2
- package/template/customization-api/ai-agent.ts +0 -4
|
@@ -958,19 +958,6 @@ const RtmConfigure = (props: any) => {
|
|
|
958
958
|
);
|
|
959
959
|
return;
|
|
960
960
|
}
|
|
961
|
-
if (
|
|
962
|
-
!parsedValue ||
|
|
963
|
-
typeof parsedValue !== 'object' ||
|
|
964
|
-
Array.isArray(parsedValue)
|
|
965
|
-
) {
|
|
966
|
-
logger.debug(
|
|
967
|
-
LogSource.Events,
|
|
968
|
-
'CUSTOM_EVENTS',
|
|
969
|
-
'Ignoring non custom-event RTM payload',
|
|
970
|
-
{evt, sender, ts},
|
|
971
|
-
);
|
|
972
|
-
return;
|
|
973
|
-
}
|
|
974
961
|
const {payload, persistLevel, source} = parsedValue;
|
|
975
962
|
// Step 1: Set local attributes
|
|
976
963
|
if (persistLevel === PersistanceLevel.Session) {
|
|
@@ -543,7 +543,9 @@ export const LiveStreamContextProvider: React.FC<
|
|
|
543
543
|
raiseHandApprovedRequestRecallToastHeading?.current(),
|
|
544
544
|
null,
|
|
545
545
|
);
|
|
546
|
-
screenshareContextInstanceRef?.current?.stopScreenshare(
|
|
546
|
+
screenshareContextInstanceRef?.current?.stopScreenshare(
|
|
547
|
+
'livestream_demotion',
|
|
548
|
+
); // This will not exist on ios
|
|
547
549
|
|
|
548
550
|
// Demote user's privileges to audience
|
|
549
551
|
changeClientRoleTo(ClientRoleType.ClientRoleAudience);
|
|
@@ -716,7 +718,9 @@ export const LiveStreamContextProvider: React.FC<
|
|
|
716
718
|
ClientRoleType.ClientRoleBroadcaster &&
|
|
717
719
|
raiseHandList[localUidRef.current]?.raised === RaiseHandValue.TRUE
|
|
718
720
|
) {
|
|
719
|
-
screenshareContextInstanceRef?.current?.stopScreenshare(
|
|
721
|
+
screenshareContextInstanceRef?.current?.stopScreenshare(
|
|
722
|
+
'livestream_recall',
|
|
723
|
+
); // This will not exist on ios
|
|
720
724
|
// Change role
|
|
721
725
|
changeClientRoleTo(ClientRoleType.ClientRoleAudience);
|
|
722
726
|
}
|
|
@@ -87,7 +87,6 @@ interface UserActionMenuOptionsOptionsProps {
|
|
|
87
87
|
spotlightUid?: UidType;
|
|
88
88
|
setSpotlightUid?: (uid: UidType) => void;
|
|
89
89
|
items?: UserActionMenuItemsConfig;
|
|
90
|
-
extraMenuItems?: ActionMenuItem[];
|
|
91
90
|
}
|
|
92
91
|
|
|
93
92
|
export default function UserActionMenuOptionsOptions(
|
|
@@ -103,13 +102,7 @@ export default function UserActionMenuOptionsOptions(
|
|
|
103
102
|
useState(false);
|
|
104
103
|
const [actionMenuitems, setActionMenuitems] = useState<ActionMenuItem[]>([]);
|
|
105
104
|
const {setSidePanel} = useSidePanel();
|
|
106
|
-
const {
|
|
107
|
-
user,
|
|
108
|
-
actionMenuVisible,
|
|
109
|
-
setActionMenuVisible,
|
|
110
|
-
spotlightUid,
|
|
111
|
-
extraMenuItems,
|
|
112
|
-
} = props;
|
|
105
|
+
const {user, actionMenuVisible, setActionMenuVisible, spotlightUid} = props;
|
|
113
106
|
const {currentLayout} = useLayout();
|
|
114
107
|
const {pinnedUid, activeUids, customContent, secondaryPinnedUid} =
|
|
115
108
|
useContent();
|
|
@@ -681,7 +674,7 @@ export default function UserActionMenuOptionsOptions(
|
|
|
681
674
|
removeScreenshareConfig.onAction?.(user.parentUid);
|
|
682
675
|
//for local user directly stop the screenshare
|
|
683
676
|
if (localuid === user.parentUid) {
|
|
684
|
-
stopScreenshare();
|
|
677
|
+
stopScreenshare('participant_menu');
|
|
685
678
|
}
|
|
686
679
|
//for remote user show popup and then user will use cta to stop screenshare
|
|
687
680
|
else {
|
|
@@ -723,24 +716,6 @@ export default function UserActionMenuOptionsOptions(
|
|
|
723
716
|
items.push(...customItems);
|
|
724
717
|
}
|
|
725
718
|
|
|
726
|
-
if (extraMenuItems?.length) {
|
|
727
|
-
items.push(
|
|
728
|
-
...extraMenuItems.map(item => ({
|
|
729
|
-
...item,
|
|
730
|
-
closeActionMenu: () => {
|
|
731
|
-
item.closeActionMenu?.();
|
|
732
|
-
setActionMenuVisible(false);
|
|
733
|
-
},
|
|
734
|
-
onPress: item.onPress
|
|
735
|
-
? () => {
|
|
736
|
-
item.onPress?.();
|
|
737
|
-
setActionMenuVisible(false);
|
|
738
|
-
}
|
|
739
|
-
: item.onPress,
|
|
740
|
-
})),
|
|
741
|
-
);
|
|
742
|
-
}
|
|
743
|
-
|
|
744
719
|
items.sort((a, b) => (a.order ?? 999) - (b.order ?? 999));
|
|
745
720
|
|
|
746
721
|
setActionMenuitems(items);
|
|
@@ -754,8 +729,6 @@ export default function UserActionMenuOptionsOptions(
|
|
|
754
729
|
secondaryPinnedUid,
|
|
755
730
|
currentLayout,
|
|
756
731
|
spotlightUid,
|
|
757
|
-
extraMenuItems,
|
|
758
|
-
setActionMenuVisible,
|
|
759
732
|
]);
|
|
760
733
|
|
|
761
734
|
const {width: globalWidth, height: globalHeight} = useWindowDimensions();
|
|
@@ -29,6 +29,7 @@ import {useCustomization} from 'customization-implementation';
|
|
|
29
29
|
import {useString} from '../utils/useString';
|
|
30
30
|
import useCreateRoom from '../utils/useCreateRoom';
|
|
31
31
|
import {CreateProvider} from './create/useCreate';
|
|
32
|
+
import useJoinRoom from '../utils/useJoinRoom';
|
|
32
33
|
import {
|
|
33
34
|
RoomInfoDefaultValue,
|
|
34
35
|
useRoomInfo,
|
|
@@ -38,10 +39,12 @@ import Toggle from '../atoms/Toggle';
|
|
|
38
39
|
import Card from '../atoms/Card';
|
|
39
40
|
import Spacer from '../atoms/Spacer';
|
|
40
41
|
import LinkButton from '../atoms/LinkButton';
|
|
42
|
+
import StorageContext from '../components/StorageContext';
|
|
41
43
|
import ThemeConfig from '../theme';
|
|
42
44
|
import Tooltip from '../atoms/Tooltip';
|
|
43
45
|
import ImageIcon from '../atoms/ImageIcon';
|
|
44
46
|
import hexadecimalTransparency from '../utils/hexadecimalTransparency';
|
|
47
|
+
import {randomNameGenerator} from '../utils';
|
|
45
48
|
import {useSetRoomInfo} from '../components/room-info/useSetRoomInfo';
|
|
46
49
|
import IDPLogoutComponent from '../auth/IDPLogoutComponent';
|
|
47
50
|
import isSDK from '../utils/isSDK';
|
|
@@ -64,35 +67,25 @@ import {LogSource, logger} from '../logger/AppBuilderLogger';
|
|
|
64
67
|
import SDKEvents from '../utils/SdkEvents';
|
|
65
68
|
|
|
66
69
|
const Create = () => {
|
|
67
|
-
const {CreateComponent
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
const {CreateComponent} = useCustomization(data => {
|
|
71
|
+
let components: {
|
|
72
|
+
CreateComponent?: React.ElementType;
|
|
73
|
+
} = {};
|
|
74
|
+
if (
|
|
75
|
+
data?.components?.create &&
|
|
76
|
+
typeof data?.components?.create !== 'object'
|
|
77
|
+
) {
|
|
73
78
|
if (
|
|
74
79
|
data?.components?.create &&
|
|
75
|
-
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
components.CreateComponent = data?.components?.create;
|
|
82
|
-
}
|
|
83
|
-
if (
|
|
84
|
-
data?.components?.create &&
|
|
85
|
-
typeof data?.components?.create === 'object' &&
|
|
86
|
-
data?.components?.create?.headerRightSlot &&
|
|
87
|
-
isValidReactComponent(data?.components?.create?.headerRightSlot)
|
|
88
|
-
) {
|
|
89
|
-
components.CreateHeaderRightSlotComponent =
|
|
90
|
-
data?.components?.create?.headerRightSlot;
|
|
91
|
-
}
|
|
92
|
-
return components;
|
|
93
|
-
},
|
|
94
|
-
);
|
|
80
|
+
isValidReactComponent(data?.components?.create)
|
|
81
|
+
)
|
|
82
|
+
components.CreateComponent = data?.components?.create;
|
|
83
|
+
}
|
|
84
|
+
return components;
|
|
85
|
+
});
|
|
95
86
|
|
|
87
|
+
const useJoin = useJoinRoom();
|
|
88
|
+
const {setStore} = useContext(StorageContext);
|
|
96
89
|
const {setGlobalErrorMessage} = useContext(ErrorContext);
|
|
97
90
|
const history = useHistory();
|
|
98
91
|
const [loading, setLoading] = useState(false);
|
|
@@ -320,11 +313,6 @@ const Create = () => {
|
|
|
320
313
|
<View>
|
|
321
314
|
<View style={style.logoContainerStyle}>
|
|
322
315
|
<Logo />
|
|
323
|
-
{CreateHeaderRightSlotComponent ? (
|
|
324
|
-
<CreateHeaderRightSlotComponent />
|
|
325
|
-
) : (
|
|
326
|
-
<></>
|
|
327
|
-
)}
|
|
328
316
|
{isMobileUA() ? (
|
|
329
317
|
<IDPLogoutComponent
|
|
330
318
|
containerStyle={{marginTop: 0, marginRight: 0}}
|
|
@@ -291,8 +291,6 @@ const ActionSheet = props => {
|
|
|
291
291
|
isCustomSidePanel={true}
|
|
292
292
|
customSidePanelProps={{
|
|
293
293
|
title: sidePanelArray[customSidePanelIndex]?.title,
|
|
294
|
-
headerRightSlot:
|
|
295
|
-
sidePanelArray[customSidePanelIndex]?.headerRightSlot,
|
|
296
294
|
onClose: sidePanelArray[customSidePanelIndex]?.onClose,
|
|
297
295
|
name: sidePanelArray[customSidePanelIndex]?.name,
|
|
298
296
|
}}
|
|
@@ -326,8 +326,6 @@ const ActionSheet = props => {
|
|
|
326
326
|
isCustomSidePanel={true}
|
|
327
327
|
customSidePanelProps={{
|
|
328
328
|
title: sidePanelArray[customSidePanelIndex]?.title,
|
|
329
|
-
headerRightSlot:
|
|
330
|
-
sidePanelArray[customSidePanelIndex]?.headerRightSlot,
|
|
331
329
|
onClose: sidePanelArray[customSidePanelIndex]?.onClose,
|
|
332
330
|
name: sidePanelArray[customSidePanelIndex]?.name,
|
|
333
331
|
}}
|
|
@@ -208,7 +208,6 @@ export const VBHeader = () => {
|
|
|
208
208
|
|
|
209
209
|
export const CustomSidePanelHeader = (props: {
|
|
210
210
|
title: string;
|
|
211
|
-
headerRightSlot?: React.ReactNode;
|
|
212
211
|
onClose?: () => void;
|
|
213
212
|
name: string;
|
|
214
213
|
}) => {
|
|
@@ -218,7 +217,6 @@ export const CustomSidePanelHeader = (props: {
|
|
|
218
217
|
centerComponent={
|
|
219
218
|
<Text style={SidePanelStyles.heading}>{props?.title}</Text>
|
|
220
219
|
}
|
|
221
|
-
trailingComponent={props?.headerRightSlot}
|
|
222
220
|
trailingIconName="close"
|
|
223
221
|
trailingIconOnPress={() => {
|
|
224
222
|
setSidePanel(SidePanelType.None);
|
|
@@ -226,7 +224,7 @@ export const CustomSidePanelHeader = (props: {
|
|
|
226
224
|
props?.onClose && props?.onClose();
|
|
227
225
|
} catch (error) {
|
|
228
226
|
console.error(
|
|
229
|
-
`Error on calling onClose in custom side panel ${
|
|
227
|
+
`Error on calling onClose in custom side panel ${name}`,
|
|
230
228
|
);
|
|
231
229
|
}
|
|
232
230
|
}}
|
|
@@ -400,9 +400,6 @@ const VideoCallScreen = () => {
|
|
|
400
400
|
<CustomSidePanelView
|
|
401
401
|
content={SidePanelArray[customSidePanelIndex]?.component}
|
|
402
402
|
title={SidePanelArray[customSidePanelIndex]?.title}
|
|
403
|
-
headerRightSlot={
|
|
404
|
-
SidePanelArray[customSidePanelIndex]?.headerRightSlot
|
|
405
|
-
}
|
|
406
403
|
name={SidePanelArray[customSidePanelIndex]?.name}
|
|
407
404
|
onClose={SidePanelArray[customSidePanelIndex]?.onClose}
|
|
408
405
|
/>
|
|
@@ -25,7 +25,6 @@ import {
|
|
|
25
25
|
} from './DefaultLayouts';
|
|
26
26
|
import IconButton from '../../atoms/IconButton';
|
|
27
27
|
import UserActionMenuOptionsOptions from '../../components/participants/UserActionMenuOptions';
|
|
28
|
-
import type {ActionMenuItem} from '../../atoms/ActionMenu';
|
|
29
28
|
import {
|
|
30
29
|
isMobileUA,
|
|
31
30
|
isValidReactComponent,
|
|
@@ -61,7 +60,6 @@ export interface VideoRendererProps {
|
|
|
61
60
|
CustomChild?: React.ComponentType;
|
|
62
61
|
avatarRadius?: number;
|
|
63
62
|
hideMenuOptions?: boolean;
|
|
64
|
-
extraMenuItems?: ActionMenuItem[];
|
|
65
63
|
containerStyle?: ViewStyle;
|
|
66
64
|
innerContainerStyle?: ViewStyle;
|
|
67
65
|
}
|
|
@@ -71,7 +69,6 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
|
|
|
71
69
|
CustomChild,
|
|
72
70
|
avatarRadius = 100,
|
|
73
71
|
hideMenuOptions = false,
|
|
74
|
-
extraMenuItems,
|
|
75
72
|
containerStyle = {},
|
|
76
73
|
innerContainerStyle = {},
|
|
77
74
|
}) => {
|
|
@@ -217,7 +214,6 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
|
|
|
217
214
|
from={'video-tile'}
|
|
218
215
|
setSpotlightUid={setSpotlightUid}
|
|
219
216
|
spotlightUid={spotlightUid}
|
|
220
|
-
extraMenuItems={extraMenuItems}
|
|
221
217
|
/>
|
|
222
218
|
<PlatformWrapper isHovered={isHovered} setIsHovered={setIsHovered}>
|
|
223
219
|
<View
|
|
@@ -57,7 +57,7 @@ const LocalEndcall = (props: LocalEndcallProps) => {
|
|
|
57
57
|
props.customExit();
|
|
58
58
|
} else {
|
|
59
59
|
if ((isAndroid() || isIOS()) && isScreenshareActive) {
|
|
60
|
-
stopScreenshare();
|
|
60
|
+
stopScreenshare('end_call_cleanup');
|
|
61
61
|
setEndCallState(true);
|
|
62
62
|
} else {
|
|
63
63
|
executeEndCall();
|
|
@@ -59,7 +59,7 @@ function ScreenShareNotice({uid, isMax}: {uid: UidType; isMax: boolean}) {
|
|
|
59
59
|
) : (
|
|
60
60
|
<TouchableOpacity
|
|
61
61
|
style={styles.btnContainer}
|
|
62
|
-
onPress={() => stopScreenshare()}>
|
|
62
|
+
onPress={() => stopScreenshare('overlay_control')}>
|
|
63
63
|
<View style={styles.iconContainer}>
|
|
64
64
|
<ImageIcon
|
|
65
65
|
iconType="plain"
|
|
@@ -13,7 +13,6 @@ export interface SidePanelHeaderProps {
|
|
|
13
13
|
leadingIconOnPress?: () => void;
|
|
14
14
|
trailingIconName?: keyof IconsInterface;
|
|
15
15
|
trailingIconOnPress?: () => void;
|
|
16
|
-
trailingComponent?: React.ReactNode;
|
|
17
16
|
trailingIconName2?: keyof IconsInterface;
|
|
18
17
|
trailingIconOnPress2?: () => void;
|
|
19
18
|
isChat?: boolean;
|
|
@@ -57,21 +56,9 @@ const SidePanelHeader = React.forwardRef<View, SidePanelHeaderProps>(
|
|
|
57
56
|
<View style={{width: 30, height: 'auto'}}></View>
|
|
58
57
|
) : null}
|
|
59
58
|
{props?.centerComponent ? props.centerComponent : null}
|
|
60
|
-
<View
|
|
61
|
-
style={
|
|
62
|
-
(props?.trailingIconName2 || props?.trailingComponent) &&
|
|
63
|
-
SidePanelStyles.row
|
|
64
|
-
}>
|
|
65
|
-
{props?.trailingComponent ? props.trailingComponent : null}
|
|
59
|
+
<View style={props?.trailingIconName2 && SidePanelStyles.row}>
|
|
66
60
|
{props?.trailingIconName ? (
|
|
67
|
-
<View
|
|
68
|
-
ref={ref}
|
|
69
|
-
collapsable={false}
|
|
70
|
-
style={
|
|
71
|
-
props?.trailingIconName2 || props?.trailingComponent
|
|
72
|
-
? {}
|
|
73
|
-
: {flex: 1}
|
|
74
|
-
}>
|
|
61
|
+
<View ref={ref} collapsable={false} style={{flex: 1}}>
|
|
75
62
|
<IconButton
|
|
76
63
|
hoverEffect={true}
|
|
77
64
|
hoverEffectStyle={{
|
|
@@ -129,7 +116,6 @@ export const SidePanelStyles = StyleSheet.create({
|
|
|
129
116
|
borderBottomWidth: 1,
|
|
130
117
|
borderBottomColor: $config.CARD_LAYER_3_COLOR,
|
|
131
118
|
position: 'relative',
|
|
132
|
-
zIndex: 1000,
|
|
133
119
|
},
|
|
134
120
|
chatPadding: {
|
|
135
121
|
paddingHorizontal: 16,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import {runPostStartRecordingLayoutUpdate} from '../recordingJourney';
|
|
2
|
+
|
|
3
|
+
describe('recording journey', () => {
|
|
4
|
+
it('does not reject recording start when the post-start layout update fails', async () => {
|
|
5
|
+
const layoutError = new Error('layout update failed');
|
|
6
|
+
const onFailure = jest.fn();
|
|
7
|
+
|
|
8
|
+
await expect(
|
|
9
|
+
runPostStartRecordingLayoutUpdate(
|
|
10
|
+
() => Promise.reject(layoutError),
|
|
11
|
+
onFailure,
|
|
12
|
+
),
|
|
13
|
+
).resolves.toBe(false);
|
|
14
|
+
expect(onFailure).toHaveBeenCalledWith(layoutError);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('reports a successful post-start layout update', async () => {
|
|
18
|
+
const onFailure = jest.fn();
|
|
19
|
+
|
|
20
|
+
await expect(
|
|
21
|
+
runPostStartRecordingLayoutUpdate(() => Promise.resolve(), onFailure),
|
|
22
|
+
).resolves.toBe(true);
|
|
23
|
+
expect(onFailure).not.toHaveBeenCalled();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import {parseRecordingLayoutResponse} from '../recordingLayoutResponse';
|
|
2
|
+
|
|
3
|
+
const response = (
|
|
4
|
+
body: string,
|
|
5
|
+
options: {ok?: boolean; status?: number; statusText?: string} = {},
|
|
6
|
+
) =>
|
|
7
|
+
({
|
|
8
|
+
ok: options.ok ?? true,
|
|
9
|
+
status: options.status ?? 200,
|
|
10
|
+
statusText: options.statusText ?? 'OK',
|
|
11
|
+
text: jest.fn().mockResolvedValue(body),
|
|
12
|
+
} as Pick<Response, 'ok' | 'status' | 'statusText' | 'text'>);
|
|
13
|
+
|
|
14
|
+
describe('parseRecordingLayoutResponse', () => {
|
|
15
|
+
it('accepts an empty successful response', async () => {
|
|
16
|
+
await expect(
|
|
17
|
+
parseRecordingLayoutResponse(
|
|
18
|
+
response('', {status: 204, statusText: 'No Content'}),
|
|
19
|
+
),
|
|
20
|
+
).resolves.toBeUndefined();
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('returns a successful JSON response', async () => {
|
|
24
|
+
await expect(
|
|
25
|
+
parseRecordingLayoutResponse(response('{"success":true}')),
|
|
26
|
+
).resolves.toEqual({success: true});
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('rejects an empty HTTP error with status details', async () => {
|
|
30
|
+
await expect(
|
|
31
|
+
parseRecordingLayoutResponse(
|
|
32
|
+
response('', {
|
|
33
|
+
ok: false,
|
|
34
|
+
status: 500,
|
|
35
|
+
statusText: 'Internal Server Error',
|
|
36
|
+
}),
|
|
37
|
+
),
|
|
38
|
+
).rejects.toMatchObject({
|
|
39
|
+
name: 'Error',
|
|
40
|
+
code: 'HTTP_500',
|
|
41
|
+
httpStatus: 500,
|
|
42
|
+
message:
|
|
43
|
+
'Recording layout API failed with HTTP 500 Internal Server Error',
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('rejects invalid non-empty JSON with the response details', async () => {
|
|
48
|
+
await expect(
|
|
49
|
+
parseRecordingLayoutResponse(response('<html>')),
|
|
50
|
+
).rejects.toMatchObject({
|
|
51
|
+
name: 'Error',
|
|
52
|
+
code: 'HTTP_200',
|
|
53
|
+
responseBody: '<html>',
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
});
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
type RecordingLayoutResponse = Pick<
|
|
2
|
+
Response,
|
|
3
|
+
'ok' | 'status' | 'statusText' | 'text'
|
|
4
|
+
>;
|
|
5
|
+
|
|
6
|
+
const createResponseError = (
|
|
7
|
+
message: string,
|
|
8
|
+
response: RecordingLayoutResponse,
|
|
9
|
+
responseBody: string,
|
|
10
|
+
) =>
|
|
11
|
+
Object.assign(new Error(message), {
|
|
12
|
+
code: `HTTP_${response.status}`,
|
|
13
|
+
httpStatus: response.status,
|
|
14
|
+
httpStatusText: response.statusText,
|
|
15
|
+
responseBody,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
export const parseRecordingLayoutResponse = async (
|
|
19
|
+
response: RecordingLayoutResponse,
|
|
20
|
+
): Promise<unknown> => {
|
|
21
|
+
const responseBody = await response.text();
|
|
22
|
+
let responseData: any;
|
|
23
|
+
|
|
24
|
+
if (responseBody.trim()) {
|
|
25
|
+
try {
|
|
26
|
+
responseData = JSON.parse(responseBody);
|
|
27
|
+
} catch (error) {
|
|
28
|
+
throw createResponseError(
|
|
29
|
+
`Recording layout API returned invalid JSON: ${
|
|
30
|
+
error instanceof Error ? error.message : String(error)
|
|
31
|
+
}`,
|
|
32
|
+
response,
|
|
33
|
+
responseBody,
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!response.ok) {
|
|
39
|
+
const backendMessage =
|
|
40
|
+
responseData?.error?.message ||
|
|
41
|
+
responseData?.error ||
|
|
42
|
+
responseData?.message ||
|
|
43
|
+
responseBody;
|
|
44
|
+
throw createResponseError(
|
|
45
|
+
`Recording layout API failed with HTTP ${response.status}${
|
|
46
|
+
response.statusText ? ` ${response.statusText}` : ''
|
|
47
|
+
}${backendMessage ? `: ${String(backendMessage)}` : ''}`,
|
|
48
|
+
response,
|
|
49
|
+
responseBody,
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (responseData?.error) {
|
|
54
|
+
const backendMessage =
|
|
55
|
+
responseData.error?.message ||
|
|
56
|
+
(typeof responseData.error === 'string'
|
|
57
|
+
? responseData.error
|
|
58
|
+
: JSON.stringify(responseData.error));
|
|
59
|
+
throw createResponseError(
|
|
60
|
+
`Recording layout API returned an error: ${backendMessage}`,
|
|
61
|
+
response,
|
|
62
|
+
responseBody,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return responseData;
|
|
67
|
+
};
|
|
@@ -50,6 +50,7 @@ import {useScreenContext} from '../../components/contexts/ScreenShareContext';
|
|
|
50
50
|
import {useLiveStreamDataContext} from '../../components/contexts/LiveStreamDataContext';
|
|
51
51
|
import {fetchRetry} from '../../utils/fetch-retry';
|
|
52
52
|
import {LogSource, logger} from '../../logger/AppBuilderLogger';
|
|
53
|
+
import {runPostStartRecordingLayoutUpdate} from './recordingJourney';
|
|
53
54
|
import getUniqueID from '../../utils/getUniqueID';
|
|
54
55
|
import {useControlPermissionMatrix} from '../../components/controls/useControlPermissionMatrix';
|
|
55
56
|
|
|
@@ -316,7 +317,7 @@ const RecordingProvider = (props: RecordingProviderProps) => {
|
|
|
316
317
|
mode: recordingMode.toLowerCase(),
|
|
317
318
|
}),
|
|
318
319
|
})
|
|
319
|
-
.then((res: any) => {
|
|
320
|
+
.then(async (res: any) => {
|
|
320
321
|
const endRequestTs = Date.now();
|
|
321
322
|
const latency = endRequestTs - startReqTs;
|
|
322
323
|
if (res.status === 200) {
|
|
@@ -361,17 +362,32 @@ const RecordingProvider = (props: RecordingProviderProps) => {
|
|
|
361
362
|
.sort((a, b) => b[1].ts - a[1].ts);
|
|
362
363
|
|
|
363
364
|
const activeScreenshareUid = sorted.length > 0 ? sorted[0][0] : 0;
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
365
|
+
await runPostStartRecordingLayoutUpdate(
|
|
366
|
+
() => {
|
|
367
|
+
if (activeScreenshareUid) {
|
|
368
|
+
logger.debug(
|
|
369
|
+
LogSource.Internals,
|
|
370
|
+
'RECORDING',
|
|
371
|
+
'screenshare: Executing presenter query for screenuid',
|
|
372
|
+
activeScreenshareUid,
|
|
373
|
+
);
|
|
374
|
+
return executePresenterQuery(parseInt(activeScreenshareUid));
|
|
375
|
+
}
|
|
376
|
+
return executeNormalQuery();
|
|
377
|
+
},
|
|
378
|
+
layoutError => {
|
|
379
|
+
logger.error(
|
|
380
|
+
LogSource.NetworkRest,
|
|
381
|
+
'recording_start',
|
|
382
|
+
'Recording started successfully, but the initial recording layout update failed; recording remains active',
|
|
383
|
+
layoutError,
|
|
384
|
+
{
|
|
385
|
+
requestId,
|
|
386
|
+
activeScreenshareUid,
|
|
387
|
+
},
|
|
388
|
+
);
|
|
389
|
+
},
|
|
390
|
+
);
|
|
375
391
|
}
|
|
376
392
|
} else if (res.status === 500) {
|
|
377
393
|
showErrorToast(headingStartError, subheadingError);
|