agora-appbuilder-core 4.1.21 → 4.1.22-beta.2

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agora-appbuilder-core",
3
- "version": "4.1.21",
3
+ "version": "4.1.22-beta.2",
4
4
  "description": "React Native template for RTE app builder",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -72,12 +72,24 @@ export class RTMWebClient {
72
72
  private client: RTMClient;
73
73
  private appId: string;
74
74
  private userId: string;
75
- private eventsMap = new Map<keyof NativeRTMClientEventMap, CallbackType>([
76
- ['linkState', () => null],
77
- ['storage', () => null],
78
- ['presence', () => null],
79
- ['message', () => null],
80
- ]);
75
+ private eventsMap = new Map<keyof NativeRTMClientEventMap, Set<CallbackType>>(
76
+ [
77
+ ['linkState', new Set()],
78
+ ['storage', new Set()],
79
+ ['presence', new Set()],
80
+ ['message', new Set()],
81
+ ],
82
+ );
83
+
84
+ private emitEvent(event: keyof NativeRTMClientEventMap, data: any) {
85
+ const listeners = this.eventsMap.get(event);
86
+
87
+ if (!listeners) {
88
+ return;
89
+ }
90
+
91
+ listeners.forEach(listener => listener(data));
92
+ }
81
93
 
82
94
  constructor(appId: string, userId: string) {
83
95
  this.appId = appId;
@@ -97,7 +109,7 @@ export class RTMWebClient {
97
109
  nativeLinkStateMapping.IDLE,
98
110
  reasonCode: linkStatusReasonCodeMapping[data.reasonCode] || 0,
99
111
  };
100
- (this.eventsMap.get('linkState') ?? (() => {}))(nativeState);
112
+ this.emitEvent('linkState', nativeState);
101
113
  });
102
114
 
103
115
  this.client.addEventListener('storage', data => {
@@ -108,7 +120,7 @@ export class RTMWebClient {
108
120
  data: convertWebToNativeMetadata(data.data),
109
121
  timestamp: data.timestamp,
110
122
  };
111
- (this.eventsMap.get('storage') ?? (() => {}))(nativeStorageEvent);
123
+ this.emitEvent('storage', nativeStorageEvent);
112
124
  });
113
125
 
114
126
  this.client.addEventListener('presence', data => {
@@ -119,7 +131,7 @@ export class RTMWebClient {
119
131
  publisher: data.publisher,
120
132
  timestamp: data.timestamp,
121
133
  };
122
- (this.eventsMap.get('presence') ?? (() => {}))(nativePresenceEvent);
134
+ this.emitEvent('presence', nativePresenceEvent);
123
135
  });
124
136
 
125
137
  this.client.addEventListener('message', data => {
@@ -129,7 +141,7 @@ export class RTMWebClient {
129
141
  messageType: nativeMessageEventTypeMapping[data.messageType],
130
142
  message: `${data.message}`,
131
143
  };
132
- (this.eventsMap.get('message') ?? (() => {}))(nativeMessageEvent);
144
+ this.emitEvent('message', nativeMessageEvent);
133
145
  });
134
146
  } catch (error) {
135
147
  const contextError = new Error(
@@ -396,21 +408,18 @@ export class RTMWebClient {
396
408
  listener: (event: any) => void,
397
409
  ) {
398
410
  if (this.client) {
399
- // Simply replace the handler in our map - web client listeners are fixed in constructor
400
- this.eventsMap.set(event, listener as CallbackType);
411
+ // Web SDK listeners are fixed in the constructor; keep app-level
412
+ // subscribers multiplexed here.
413
+ this.eventsMap.get(event)?.add(listener as CallbackType);
401
414
  }
402
415
  }
403
416
 
404
417
  removeEventListener(
405
418
  event: keyof NativeRTMClientEventMap,
406
- _listener: (event: any) => void,
419
+ listener: (event: any) => void,
407
420
  ) {
408
421
  if (this.client && this.eventsMap.has(event)) {
409
- const prevListener = this.eventsMap.get(event);
410
- if (prevListener) {
411
- this.client.removeEventListener(event, prevListener);
412
- }
413
- this.eventsMap.set(event, () => null); // reset to no-op
422
+ this.eventsMap.get(event)?.delete(listener as CallbackType);
414
423
  }
415
424
  }
416
425
 
@@ -473,10 +482,10 @@ export class RTMWebClient {
473
482
 
474
483
  removeAllListeners() {
475
484
  this.eventsMap = new Map([
476
- ['linkState', () => null],
477
- ['storage', () => null],
478
- ['presence', () => null],
479
- ['message', () => null],
485
+ ['linkState', new Set()],
486
+ ['storage', new Set()],
487
+ ['presence', new Set()],
488
+ ['message', new Set()],
480
489
  ]);
481
490
  return this.client.removeAllListeners();
482
491
  }
@@ -0,0 +1,4 @@
1
+ export {
2
+ default as AIAgentAudioVisualizer,
3
+ DisconnectedView as AIAgentDisconnectedView,
4
+ } from '../src/ai-agent/components/AudioVisualizer';
@@ -8,3 +8,5 @@ export {default as TertiaryButton} from '../src/atoms/TertiaryButton';
8
8
  export {default as ActionMenu} from '../src/atoms/ActionMenu';
9
9
  export {default as IconButton} from '../src/atoms/IconButton';
10
10
  export {default as Dropdown} from '../src/atoms/Dropdown';
11
+ export {default as Popup} from '../src/atoms/Popup';
12
+ export {default as Tooltip} from '../src/atoms/Tooltip';
@@ -5,10 +5,13 @@ import {
5
5
  PersistanceLevel,
6
6
  EventCallback,
7
7
  } from '../src/rtm-events-api';
8
+ import LocalEventEmitter, {
9
+ LocalEventsEnum,
10
+ } from '../src/rtm-events-api/LocalEvents';
8
11
 
9
12
  // 2. Initialize with source "fpe"
10
13
  const customEvents = new Events(EventSource.fpe);
11
14
 
12
15
  // 3. export
13
- export {customEvents, PersistanceLevel};
16
+ export {customEvents, PersistanceLevel, LocalEventEmitter, LocalEventsEnum};
14
17
  export type {EventCallback};
@@ -26,6 +26,7 @@ export * from './typeDefinition';
26
26
  export * from './utils';
27
27
  export * from './types';
28
28
  export * from './atoms';
29
+ export * from './ai-agent';
29
30
 
30
31
  //TODO: hari remove later - used for simple-practice demo
31
32
  export * from './temp';
@@ -53,6 +53,7 @@ export interface SidePanelItem {
53
53
  name: string;
54
54
  title: string;
55
55
  component: React.ComponentType;
56
+ headerRightSlot?: React.ReactNode;
56
57
  onClose?: () => void;
57
58
  }
58
59
 
@@ -74,6 +75,10 @@ export type CustomLogger = (
74
75
  export interface CustomAgentInterfaceProps {
75
76
  connectionState: AIAgentState;
76
77
  }
78
+ export interface CreateInterface extends BeforeAndAfterInterface {
79
+ headerRightSlot?: React.ComponentType;
80
+ }
81
+
77
82
  export interface VideoCallInterface extends BeforeAndAfterInterface {
78
83
  // commented for v1 release
79
84
  topToolBar?: ToolbarType;
@@ -109,7 +114,7 @@ export type ComponentsInterface = {
109
114
  precall?: PreCallInterface;
110
115
  preferenceWrapper?: React.ComponentType;
111
116
  //precall?: React.ComponentType;
112
- create?: React.ComponentType;
117
+ create?: React.ComponentType | CreateInterface;
113
118
  //share?: React.ComponentType;
114
119
  //join?: React.ComponentType;
115
120
  videoCall?: VideoCallInterface;
@@ -77,8 +77,8 @@ const DefaultConfig = {
77
77
  CHAT_ORG_NAME: "",
78
78
  CHAT_APP_NAME: "",
79
79
  CHAT_URL: "",
80
- CLI_VERSION: "3.1.21",
81
- CORE_VERSION: "4.1.21",
80
+ CLI_VERSION: "3.1.22-beta.2",
81
+ CORE_VERSION: "4.1.22-beta.2",
82
82
  DISABLE_LANDSCAPE_MODE: false,
83
83
  STT_AUTO_START: false,
84
84
  CLOUD_RECORDING_AUTO_START: false,
@@ -10,23 +10,18 @@
10
10
  *********************************************
11
11
  */
12
12
  import React from 'react';
13
- import {View, Text, StyleSheet, ScrollView} from 'react-native';
13
+ import {View, StyleSheet, ScrollView} from 'react-native';
14
14
  import {isMobileUA, isWebInternal, useIsSmall} from '../utils/common';
15
15
  import CommonStyles from './CommonStyles';
16
- import SidePanelHeader, {
17
- SidePanelStyles,
18
- } from '../subComponents/SidePanelHeader';
19
- import {useLayout} from '../utils/useLayout';
20
- import {getGridLayoutName} from '../pages/video-call/DefaultLayouts';
21
16
  import useCaptionWidth from '../subComponents/caption/useCaptionWidth';
22
- import {useSidePanel} from '../utils/useSidePanel';
23
- import {SidePanelType} from '../subComponents/SidePanelEnum';
24
17
  import {CustomSidePanelHeader} from '../pages/video-call/SidePanelHeader';
25
-
18
+ import {useLayout} from '../utils/useLayout';
19
+ import {getGridLayoutName} from '../pages/video-call/DefaultLayouts';
26
20
  export interface CustomSidePanelViewInterface {
27
21
  name: string;
28
22
  title?: string;
29
23
  content: React.ComponentType;
24
+ headerRightSlot?: React.ReactNode;
30
25
  onClose?: () => void;
31
26
  showHeader?: boolean;
32
27
  }
@@ -37,12 +32,12 @@ const CustomSidePanelView = (props: CustomSidePanelViewInterface) => {
37
32
  showHeader = true,
38
33
  name,
39
34
  title,
35
+ headerRightSlot,
40
36
  onClose,
41
37
  } = props;
42
- const {currentLayout} = useLayout();
43
38
  const {transcriptHeight} = useCaptionWidth();
44
- const {setSidePanel} = useSidePanel();
45
39
  const isSmall = useIsSmall();
40
+ const {currentLayout} = useLayout();
46
41
 
47
42
  return (
48
43
  <View
@@ -57,13 +52,18 @@ const CustomSidePanelView = (props: CustomSidePanelViewInterface) => {
57
52
  : // desktop maximized
58
53
  CommonStyles.sidePanelContainerWeb,
59
54
  isWebInternal() && !isSmall() && currentLayout === getGridLayoutName()
60
- ? {marginVertical: 4}
55
+ ? {marginTop: 4}
61
56
  : {},
62
57
  //@ts-ignore
63
58
  transcriptHeight && !isMobileUA() && {height: transcriptHeight},
64
59
  ]}>
65
60
  {showHeader && (
66
- <CustomSidePanelHeader name={name} title={title} onClose={onClose} />
61
+ <CustomSidePanelHeader
62
+ name={name}
63
+ title={title}
64
+ headerRightSlot={headerRightSlot}
65
+ onClose={onClose}
66
+ />
67
67
  )}
68
68
  <ScrollView contentContainerStyle={[style.bodyContainer]}>
69
69
  {CustomSidePanelContent ? <CustomSidePanelContent /> : <></>}
@@ -950,6 +950,20 @@ const RtmConfigure = (props: any) => {
950
950
  }
951
951
  }
952
952
 
953
+ if (
954
+ typeof evt !== 'string' ||
955
+ evt.trim() === '' ||
956
+ typeof value === 'undefined'
957
+ ) {
958
+ logger.debug(
959
+ LogSource.Events,
960
+ 'CUSTOM_EVENTS',
961
+ 'Ignoring RTM payload: not an AppBuilder custom-event envelope',
962
+ {data, sender, ts},
963
+ );
964
+ return;
965
+ }
966
+
953
967
  try {
954
968
  let parsedValue;
955
969
  try {
@@ -87,6 +87,7 @@ interface UserActionMenuOptionsOptionsProps {
87
87
  spotlightUid?: UidType;
88
88
  setSpotlightUid?: (uid: UidType) => void;
89
89
  items?: UserActionMenuItemsConfig;
90
+ extraMenuItems?: ActionMenuItem[];
90
91
  }
91
92
 
92
93
  export default function UserActionMenuOptionsOptions(
@@ -102,7 +103,13 @@ export default function UserActionMenuOptionsOptions(
102
103
  useState(false);
103
104
  const [actionMenuitems, setActionMenuitems] = useState<ActionMenuItem[]>([]);
104
105
  const {setSidePanel} = useSidePanel();
105
- const {user, actionMenuVisible, setActionMenuVisible, spotlightUid} = props;
106
+ const {
107
+ user,
108
+ actionMenuVisible,
109
+ setActionMenuVisible,
110
+ spotlightUid,
111
+ extraMenuItems,
112
+ } = props;
106
113
  const {currentLayout} = useLayout();
107
114
  const {pinnedUid, activeUids, customContent, secondaryPinnedUid} =
108
115
  useContent();
@@ -716,6 +723,24 @@ export default function UserActionMenuOptionsOptions(
716
723
  items.push(...customItems);
717
724
  }
718
725
 
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
+
719
744
  items.sort((a, b) => (a.order ?? 999) - (b.order ?? 999));
720
745
 
721
746
  setActionMenuitems(items);
@@ -729,6 +754,8 @@ export default function UserActionMenuOptionsOptions(
729
754
  secondaryPinnedUid,
730
755
  currentLayout,
731
756
  spotlightUid,
757
+ extraMenuItems,
758
+ setActionMenuVisible,
732
759
  ]);
733
760
 
734
761
  const {width: globalWidth, height: globalHeight} = useWindowDimensions();
@@ -29,7 +29,6 @@ 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';
33
32
  import {
34
33
  RoomInfoDefaultValue,
35
34
  useRoomInfo,
@@ -39,12 +38,10 @@ import Toggle from '../atoms/Toggle';
39
38
  import Card from '../atoms/Card';
40
39
  import Spacer from '../atoms/Spacer';
41
40
  import LinkButton from '../atoms/LinkButton';
42
- import StorageContext from '../components/StorageContext';
43
41
  import ThemeConfig from '../theme';
44
42
  import Tooltip from '../atoms/Tooltip';
45
43
  import ImageIcon from '../atoms/ImageIcon';
46
44
  import hexadecimalTransparency from '../utils/hexadecimalTransparency';
47
- import {randomNameGenerator} from '../utils';
48
45
  import {useSetRoomInfo} from '../components/room-info/useSetRoomInfo';
49
46
  import IDPLogoutComponent from '../auth/IDPLogoutComponent';
50
47
  import isSDK from '../utils/isSDK';
@@ -67,25 +64,35 @@ import {LogSource, logger} from '../logger/AppBuilderLogger';
67
64
  import SDKEvents from '../utils/SdkEvents';
68
65
 
69
66
  const Create = () => {
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
- ) {
67
+ const {CreateComponent, CreateHeaderRightSlotComponent} = useCustomization(
68
+ data => {
69
+ let components: {
70
+ CreateComponent?: React.ElementType;
71
+ CreateHeaderRightSlotComponent?: React.ElementType;
72
+ } = {};
78
73
  if (
79
74
  data?.components?.create &&
80
- isValidReactComponent(data?.components?.create)
81
- )
82
- components.CreateComponent = data?.components?.create;
83
- }
84
- return components;
85
- });
75
+ typeof data?.components?.create !== 'object'
76
+ ) {
77
+ if (
78
+ data?.components?.create &&
79
+ isValidReactComponent(data?.components?.create)
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
+ );
86
95
 
87
- const useJoin = useJoinRoom();
88
- const {setStore} = useContext(StorageContext);
89
96
  const {setGlobalErrorMessage} = useContext(ErrorContext);
90
97
  const history = useHistory();
91
98
  const [loading, setLoading] = useState(false);
@@ -313,6 +320,11 @@ const Create = () => {
313
320
  <View>
314
321
  <View style={style.logoContainerStyle}>
315
322
  <Logo />
323
+ {CreateHeaderRightSlotComponent ? (
324
+ <CreateHeaderRightSlotComponent />
325
+ ) : (
326
+ <></>
327
+ )}
316
328
  {isMobileUA() ? (
317
329
  <IDPLogoutComponent
318
330
  containerStyle={{marginTop: 0, marginRight: 0}}
@@ -291,6 +291,8 @@ const ActionSheet = props => {
291
291
  isCustomSidePanel={true}
292
292
  customSidePanelProps={{
293
293
  title: sidePanelArray[customSidePanelIndex]?.title,
294
+ headerRightSlot:
295
+ sidePanelArray[customSidePanelIndex]?.headerRightSlot,
294
296
  onClose: sidePanelArray[customSidePanelIndex]?.onClose,
295
297
  name: sidePanelArray[customSidePanelIndex]?.name,
296
298
  }}
@@ -326,6 +326,8 @@ const ActionSheet = props => {
326
326
  isCustomSidePanel={true}
327
327
  customSidePanelProps={{
328
328
  title: sidePanelArray[customSidePanelIndex]?.title,
329
+ headerRightSlot:
330
+ sidePanelArray[customSidePanelIndex]?.headerRightSlot,
329
331
  onClose: sidePanelArray[customSidePanelIndex]?.onClose,
330
332
  name: sidePanelArray[customSidePanelIndex]?.name,
331
333
  }}
@@ -16,6 +16,7 @@ const ActionSheetHandle = (props: {
16
16
  isCustomSidePanel?: boolean;
17
17
  customSidePanelProps?: {
18
18
  title: string;
19
+ headerRightSlot?: React.ReactNode;
19
20
  onClose?: () => void;
20
21
  name: string;
21
22
  };
@@ -208,6 +208,7 @@ export const VBHeader = () => {
208
208
 
209
209
  export const CustomSidePanelHeader = (props: {
210
210
  title: string;
211
+ headerRightSlot?: React.ReactNode;
211
212
  onClose?: () => void;
212
213
  name: string;
213
214
  }) => {
@@ -217,6 +218,7 @@ export const CustomSidePanelHeader = (props: {
217
218
  centerComponent={
218
219
  <Text style={SidePanelStyles.heading}>{props?.title}</Text>
219
220
  }
221
+ trailingComponent={props?.headerRightSlot}
220
222
  trailingIconName="close"
221
223
  trailingIconOnPress={() => {
222
224
  setSidePanel(SidePanelType.None);
@@ -224,7 +226,7 @@ export const CustomSidePanelHeader = (props: {
224
226
  props?.onClose && props?.onClose();
225
227
  } catch (error) {
226
228
  console.error(
227
- `Error on calling onClose in custom side panel ${name}`,
229
+ `Error on calling onClose in custom side panel ${props.name}`,
228
230
  );
229
231
  }
230
232
  }}
@@ -400,6 +400,9 @@ const VideoCallScreen = () => {
400
400
  <CustomSidePanelView
401
401
  content={SidePanelArray[customSidePanelIndex]?.component}
402
402
  title={SidePanelArray[customSidePanelIndex]?.title}
403
+ headerRightSlot={
404
+ SidePanelArray[customSidePanelIndex]?.headerRightSlot
405
+ }
403
406
  name={SidePanelArray[customSidePanelIndex]?.name}
404
407
  onClose={SidePanelArray[customSidePanelIndex]?.onClose}
405
408
  />
@@ -25,6 +25,7 @@ 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';
28
29
  import {
29
30
  isMobileUA,
30
31
  isValidReactComponent,
@@ -60,6 +61,7 @@ export interface VideoRendererProps {
60
61
  CustomChild?: React.ComponentType;
61
62
  avatarRadius?: number;
62
63
  hideMenuOptions?: boolean;
64
+ extraMenuItems?: ActionMenuItem[];
63
65
  containerStyle?: ViewStyle;
64
66
  innerContainerStyle?: ViewStyle;
65
67
  }
@@ -69,6 +71,7 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
69
71
  CustomChild,
70
72
  avatarRadius = 100,
71
73
  hideMenuOptions = false,
74
+ extraMenuItems,
72
75
  containerStyle = {},
73
76
  innerContainerStyle = {},
74
77
  }) => {
@@ -214,6 +217,7 @@ const VideoRenderer: React.FC<VideoRendererProps> = ({
214
217
  from={'video-tile'}
215
218
  setSpotlightUid={setSpotlightUid}
216
219
  spotlightUid={spotlightUid}
220
+ extraMenuItems={extraMenuItems}
217
221
  />
218
222
  <PlatformWrapper isHovered={isHovered} setIsHovered={setIsHovered}>
219
223
  <View
@@ -13,6 +13,7 @@ export interface SidePanelHeaderProps {
13
13
  leadingIconOnPress?: () => void;
14
14
  trailingIconName?: keyof IconsInterface;
15
15
  trailingIconOnPress?: () => void;
16
+ trailingComponent?: React.ReactNode;
16
17
  trailingIconName2?: keyof IconsInterface;
17
18
  trailingIconOnPress2?: () => void;
18
19
  isChat?: boolean;
@@ -56,9 +57,21 @@ const SidePanelHeader = React.forwardRef<View, SidePanelHeaderProps>(
56
57
  <View style={{width: 30, height: 'auto'}}></View>
57
58
  ) : null}
58
59
  {props?.centerComponent ? props.centerComponent : null}
59
- <View style={props?.trailingIconName2 && SidePanelStyles.row}>
60
+ <View
61
+ style={
62
+ (props?.trailingIconName2 || props?.trailingComponent) &&
63
+ SidePanelStyles.row
64
+ }>
65
+ {props?.trailingComponent ? props.trailingComponent : null}
60
66
  {props?.trailingIconName ? (
61
- <View ref={ref} collapsable={false} style={{flex: 1}}>
67
+ <View
68
+ ref={ref}
69
+ collapsable={false}
70
+ style={
71
+ props?.trailingIconName2 || props?.trailingComponent
72
+ ? {}
73
+ : {flex: 1}
74
+ }>
62
75
  <IconButton
63
76
  hoverEffect={true}
64
77
  hoverEffectStyle={{
@@ -116,6 +129,7 @@ export const SidePanelStyles = StyleSheet.create({
116
129
  borderBottomWidth: 1,
117
130
  borderBottomColor: $config.CARD_LAYER_3_COLOR,
118
131
  position: 'relative',
132
+ zIndex: 1000,
119
133
  },
120
134
  chatPadding: {
121
135
  paddingHorizontal: 16,