agora-appbuilder-core 4.1.9 → 4.1.10-beta.1

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.
Files changed (107) hide show
  1. package/package.json +2 -2
  2. package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -3
  3. package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -2
  4. package/template/agora-rn-uikit/src/Reducer/index.ts +0 -2
  5. package/template/agora-rn-uikit/src/Rtc/Join.tsx +11 -25
  6. package/template/agora-rn-uikit/src/RtcConfigure.tsx +1 -14
  7. package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
  8. package/template/bridge/rtm/web/index.ts +30 -0
  9. package/template/customization-api/typeDefinition.ts +1 -0
  10. package/template/defaultConfig.js +3 -2
  11. package/template/global.d.ts +1 -0
  12. package/template/src/AppRoutes.tsx +3 -3
  13. package/template/src/ai-agent/components/ControlButtons.tsx +1 -1
  14. package/template/src/assets/font-styles.css +36 -0
  15. package/template/src/assets/fonts/icomoon.ttf +0 -0
  16. package/template/src/assets/selection.json +1 -1
  17. package/template/src/atoms/CustomIcon.tsx +8 -0
  18. package/template/src/atoms/Dropdown.tsx +5 -0
  19. package/template/src/atoms/TertiaryButton.tsx +1 -1
  20. package/template/src/atoms/UserAvatar.tsx +1 -1
  21. package/template/src/components/ChatContext.ts +5 -3
  22. package/template/src/components/Controls.tsx +68 -22
  23. package/template/src/components/DeviceConfigure.tsx +1 -1
  24. package/template/src/components/EventsConfigure.tsx +22 -17
  25. package/template/src/components/Navbar.tsx +14 -11
  26. package/template/src/components/RTMConfigure.tsx +31 -1036
  27. package/template/src/components/UserGlobalPreferenceProvider.tsx +227 -0
  28. package/template/src/components/beauty-effect/useBeautyEffects.tsx +50 -13
  29. package/template/src/components/breakout-room/BreakoutRoomPanel.tsx +58 -0
  30. package/template/src/components/breakout-room/context/BreakoutRoomContext.tsx +2508 -0
  31. package/template/src/components/breakout-room/events/BreakoutRoomEventsConfigure.tsx +272 -0
  32. package/template/src/components/breakout-room/events/constants.ts +17 -0
  33. package/template/src/components/breakout-room/hoc/BreakoutRoomNameRenderer.tsx +68 -0
  34. package/template/src/components/breakout-room/hooks/useBreakoutRoomExit.ts +49 -0
  35. package/template/src/components/breakout-room/state/reducer.ts +522 -0
  36. package/template/src/components/breakout-room/state/types.ts +54 -0
  37. package/template/src/components/breakout-room/ui/BreakoutMeetingTitle.tsx +60 -0
  38. package/template/src/components/breakout-room/ui/BreakoutRoomActionMenu.tsx +136 -0
  39. package/template/src/components/breakout-room/ui/BreakoutRoomAnnouncementModal.tsx +135 -0
  40. package/template/src/components/breakout-room/ui/BreakoutRoomGroupSettings.tsx +588 -0
  41. package/template/src/components/breakout-room/ui/BreakoutRoomMainRoomUsers.tsx +142 -0
  42. package/template/src/components/breakout-room/ui/BreakoutRoomMemberActionMenu.tsx +122 -0
  43. package/template/src/components/breakout-room/ui/BreakoutRoomParticipants.tsx +124 -0
  44. package/template/src/components/breakout-room/ui/BreakoutRoomRaiseHand.tsx +65 -0
  45. package/template/src/components/breakout-room/ui/BreakoutRoomRenameModal.tsx +227 -0
  46. package/template/src/components/breakout-room/ui/BreakoutRoomSettings.tsx +140 -0
  47. package/template/src/components/breakout-room/ui/BreakoutRoomTransition.tsx +52 -0
  48. package/template/src/components/breakout-room/ui/BreakoutRoomView.tsx +193 -0
  49. package/template/src/components/breakout-room/ui/ExitBreakoutRoomIconButton.tsx +79 -0
  50. package/template/src/components/breakout-room/ui/ParticipantManualAssignmentModal.tsx +638 -0
  51. package/template/src/components/breakout-room/ui/SelectParticipantAssignmentStrategy.tsx +57 -0
  52. package/template/src/components/chat/chatConfigure.tsx +7 -1
  53. package/template/src/components/chat-messages/useChatMessages.tsx +43 -11
  54. package/template/src/components/common/Dividers.tsx +53 -0
  55. package/template/src/components/controls/toolbar-items/ExitBreakoutRoomToolbarItem.tsx +13 -0
  56. package/template/src/components/controls/useControlPermissionMatrix.tsx +32 -4
  57. package/template/src/components/participants/AllHostParticipants.tsx +10 -2
  58. package/template/src/components/participants/Participant.tsx +7 -1
  59. package/template/src/components/participants/UserActionMenuOptions.tsx +12 -2
  60. package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +12 -8
  61. package/template/src/components/precall/joinWaitingRoomBtn.tsx +14 -10
  62. package/template/src/components/raise-hand/RaiseHandButton.tsx +50 -0
  63. package/template/src/components/raise-hand/RaiseHandProvider.tsx +308 -0
  64. package/template/src/components/raise-hand/index.ts +14 -0
  65. package/template/src/components/recordings/RecordingsDateTable.tsx +3 -2
  66. package/template/src/components/room-info/useCurrentRoomInfo.tsx +42 -0
  67. package/template/src/components/room-info/useSetBreakoutRoomInfo.tsx +64 -0
  68. package/template/src/components/useUserPreference.tsx +39 -12
  69. package/template/src/components/virtual-background/useVB.tsx +18 -0
  70. package/template/src/components/whiteboard/WhiteboardConfigure.tsx +27 -0
  71. package/template/src/language/default-labels/videoCallScreenLabels.ts +7 -0
  72. package/template/src/logger/AppBuilderLogger.tsx +11 -3
  73. package/template/src/pages/VideoCall.tsx +171 -518
  74. package/template/src/pages/video-call/BreakoutVideoCall.tsx +213 -0
  75. package/template/src/pages/video-call/SidePanelHeader.tsx +17 -0
  76. package/template/src/pages/video-call/VideoCallContent.tsx +211 -0
  77. package/template/src/pages/video-call/VideoCallScreen.tsx +18 -0
  78. package/template/src/pages/video-call/VideoCallScreenWrapper.tsx +0 -1
  79. package/template/src/pages/video-call/VideoCallStateWrapper.tsx +495 -0
  80. package/template/src/rtm/RTMConfigureBreakoutRoomProvider.tsx +882 -0
  81. package/template/src/rtm/RTMConfigureMainRoomProvider.tsx +757 -0
  82. package/template/src/rtm/RTMCoreProvider.tsx +419 -0
  83. package/template/src/rtm/RTMEngine.ts +188 -60
  84. package/template/src/rtm/RTMGlobalStateProvider.tsx +706 -0
  85. package/template/src/rtm/RTMStatusBanner.tsx +99 -0
  86. package/template/src/rtm/constants.ts +12 -0
  87. package/template/src/rtm/hooks/useMainRoomUserDisplayName.ts +45 -0
  88. package/template/src/rtm/rtm-presence-utils.ts +344 -0
  89. package/template/src/rtm/utils.ts +68 -1
  90. package/template/src/rtm-events/constants.ts +40 -1
  91. package/template/src/rtm-events-api/Events.ts +62 -19
  92. package/template/src/subComponents/ChatBubble.tsx +3 -3
  93. package/template/src/subComponents/ChatContainer.tsx +19 -9
  94. package/template/src/subComponents/LocalAudioMute.tsx +2 -2
  95. package/template/src/subComponents/LocalVideoMute.tsx +2 -2
  96. package/template/src/subComponents/SidePanelEnum.tsx +1 -0
  97. package/template/src/subComponents/caption/useCaption.tsx +1 -1
  98. package/template/src/subComponents/chat/ChatAnnouncementView.tsx +65 -0
  99. package/template/src/subComponents/chat/ChatSendButton.tsx +1 -0
  100. package/template/src/subComponents/screenshare/ScreenshareButton.tsx +16 -0
  101. package/template/src/subComponents/screenshare/ScreenshareConfigure.native.tsx +1 -1
  102. package/template/src/subComponents/waiting-rooms/WaitingRoomControls.tsx +7 -4
  103. package/template/src/utils/useDebouncedCallback.tsx +20 -0
  104. package/template/src/utils/useEndCall.ts +0 -2
  105. package/template/src/utils/useMuteToggleLocal.ts +14 -10
  106. package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +0 -11
  107. package/template/agora-rn-uikit/src/Reducer/UserBanned.ts +0 -11
@@ -2,1061 +2,56 @@
2
2
  ********************************************
3
3
  Copyright © 2021 Agora Lab, Inc., all rights reserved.
4
4
  AppBuilder and all associated components, source code, APIs, services, and documentation
5
- (the Materials) are owned by Agora Lab, Inc. and its licensors. The Materials may not be
5
+ (the "Materials") are owned by Agora Lab, Inc. and its licensors. The Materials may not be
6
6
  accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc.
7
7
  Use without a license or in violation of any license terms and conditions (including use for
8
- any purpose competitive to Agora Lab, Inc.’s business) is strictly prohibited. For more
8
+ any purpose competitive to Agora Lab, Inc.'s business) is strictly prohibited. For more
9
9
  information visit https://appbuilder.agora.io.
10
10
  *********************************************
11
11
  */
12
12
 
13
- import React, {useState, useContext, useEffect, useRef} from 'react';
14
- import {
15
- type GetChannelMetadataResponse,
16
- type GetOnlineUsersResponse,
17
- type LinkStateEvent,
18
- type MessageEvent,
19
- type Metadata,
20
- type PresenceEvent,
21
- type SetOrUpdateUserMetadataOptions,
22
- type StorageEvent,
23
- type RTMClient,
24
- type GetUserMetadataResponse,
25
- } from 'agora-react-native-rtm';
26
- import {
27
- ContentInterface,
28
- DispatchContext,
29
- PropsContext,
30
- UidType,
31
- useLocalUid,
32
- } from '../../agora-rn-uikit';
13
+ import React from 'react';
14
+ import {useLocalUid} from '../../agora-rn-uikit';
33
15
  import ChatContext from './ChatContext';
34
- import {Platform} from 'react-native';
35
- import {backOff} from 'exponential-backoff';
36
- import {isAndroid, isIOS, isWeb, isWebInternal} from '../utils/common';
37
- import {useContent} from 'customization-api';
38
- import {
39
- safeJsonParse,
40
- timeNow,
41
- hasJsonStructure,
42
- getMessageTime,
43
- get32BitUid,
44
- } from '../rtm/utils';
45
- import {EventUtils, EventsQueue} from '../rtm-events';
46
- import {PersistanceLevel} from '../rtm-events-api';
47
- import RTMEngine from '../rtm/RTMEngine';
48
- import {filterObject} from '../utils';
49
- import SDKEvents from '../utils/SdkEvents';
50
- import isSDK from '../utils/isSDK';
51
- import {useAsyncEffect} from '../utils/useAsyncEffect';
52
- import {
53
- WaitingRoomStatus,
54
- useRoomInfo,
55
- } from '../components/room-info/useRoomInfo';
56
- import LocalEventEmitter, {
57
- LocalEventsEnum,
58
- } from '../rtm-events-api/LocalEvents';
59
- import {controlMessageEnum} from '../components/ChatContext';
60
- import {LogSource, logger} from '../logger/AppBuilderLogger';
61
- import {RECORDING_BOT_UID} from '../utils/constants';
62
- import {
63
- nativeChannelTypeMapping,
64
- nativeLinkStateMapping,
65
- nativePresenceEventTypeMapping,
66
- nativeStorageEventTypeMapping,
67
- } from '../../bridge/rtm/web/Types';
68
-
69
- export enum UserType {
70
- ScreenShare = 'screenshare',
16
+ import {useRTMCore} from '../rtm/RTMCoreProvider';
17
+ import {useRTMConfigureMain} from '../rtm/RTMConfigureMainRoomProvider';
18
+ import {useRTMConfigureBreakout} from '../rtm/RTMConfigureBreakoutRoomProvider';
19
+ import {RTM_ROOMS} from '../rtm/constants';
20
+
21
+ interface Props {
22
+ room: RTM_ROOMS;
23
+ children: React.ReactNode;
71
24
  }
72
25
 
73
- const eventTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
74
-
75
- const RtmConfigure = (props: any) => {
76
- let engine = useRef<RTMClient>(null!);
77
- const rtmInitTimstamp = new Date().getTime();
26
+ const RtmConfigure = (props: Props) => {
78
27
  const localUid = useLocalUid();
79
- const {callActive} = props;
80
- const {rtcProps} = useContext(PropsContext);
81
- const {dispatch} = useContext(DispatchContext);
82
- const {defaultContent, activeUids} = useContent();
83
- const {
84
- waitingRoomStatus,
85
- data: {isHost},
86
- } = useRoomInfo();
87
- const [hasUserJoinedRTM, setHasUserJoinedRTM] = useState<boolean>(false);
88
- const [isInitialQueueCompleted, setIsInitialQueueCompleted] = useState(false);
89
- const [onlineUsersCount, setTotalOnlineUsers] = useState<number>(0);
90
- const timerValueRef: any = useRef(5);
91
- // Track RTM connection state (equivalent to v1.5x connectionState check)
92
- const [rtmConnectionState, setRtmConnectionState] = useState<number>(0); // 0=IDLE, 2=CONNECTED
93
-
94
- /**
95
- * inside event callback state won't have latest value.
96
- * so creating ref to access the state
97
- */
98
- const isHostRef = useRef({isHost: isHost});
99
- useEffect(() => {
100
- isHostRef.current.isHost = isHost;
101
- }, [isHost]);
102
-
103
- const waitingRoomStatusRef = useRef({waitingRoomStatus: waitingRoomStatus});
104
- useEffect(() => {
105
- waitingRoomStatusRef.current.waitingRoomStatus = waitingRoomStatus;
106
- }, [waitingRoomStatus]);
107
-
108
- const activeUidsRef = useRef({activeUids: activeUids});
109
- useEffect(() => {
110
- activeUidsRef.current.activeUids = activeUids;
111
- }, [activeUids]);
112
-
113
- const defaultContentRef = useRef({defaultContent: defaultContent});
114
- useEffect(() => {
115
- defaultContentRef.current.defaultContent = defaultContent;
116
- }, [defaultContent]);
117
-
118
- // Eventdispatcher timeout refs clean
119
- const isRTMMounted = useRef(true);
120
- useEffect(() => {
121
- return () => {
122
- isRTMMounted.current = false;
123
- // Clear all pending timeouts on unmount
124
- for (const timeout of eventTimeouts.values()) {
125
- clearTimeout(timeout);
126
- }
127
- eventTimeouts.clear();
128
- };
129
- }, []);
130
-
131
- // Set online users
132
- React.useEffect(() => {
133
- setTotalOnlineUsers(
134
- Object.keys(
135
- filterObject(
136
- defaultContent,
137
- ([k, v]) =>
138
- v?.type === 'rtc' &&
139
- !v.offline &&
140
- activeUidsRef.current.activeUids.indexOf(v?.uid) !== -1,
141
- ),
142
- ).length,
143
- );
144
- }, [defaultContent]);
145
-
146
- React.useEffect(() => {
147
- // If its not a convo ai project and
148
- // the platform is web execute the window listeners
149
- if (!$config.ENABLE_CONVERSATIONAL_AI && isWebInternal()) {
150
- const handBrowserClose = ev => {
151
- ev.preventDefault();
152
- return (ev.returnValue = 'Are you sure you want to exit?');
153
- };
154
- const logoutRtm = () => {
155
- try {
156
- if (engine.current && RTMEngine.getInstance().channelUid) {
157
- // First unsubscribe from channel (like v1.5x leaveChannel)
158
- engine.current.unsubscribe(RTMEngine.getInstance().channelUid);
159
- // Then logout
160
- engine.current.logout();
161
- }
162
- } catch (error) {
163
- console.error('Error during browser close RTM cleanup:', error);
164
- }
165
- };
166
-
167
- // Set up window listeners
168
- window.addEventListener(
169
- 'beforeunload',
170
- isWeb() && !isSDK() ? handBrowserClose : () => {},
171
- );
172
-
173
- window.addEventListener('pagehide', logoutRtm);
174
- return () => {
175
- // Remove listeners on unmount
176
- window.removeEventListener(
177
- 'beforeunload',
178
- isWeb() && !isSDK() ? handBrowserClose : () => {},
179
- );
180
- window.removeEventListener('pagehide', logoutRtm);
181
- };
182
- }
183
- }, []);
184
-
185
- const init = async (rtcUid: UidType) => {
186
- //on sdk due to multiple re-render we are getting rtm error code 8
187
- //you are joining the same channel too frequently, exceeding the allowed rate of joining the same channel multiple times within a short period
188
- //so checking rtm connection state before proceed
189
-
190
- // Check if already connected (equivalent to v1.5x connectionState === 'CONNECTED')
191
- if (
192
- rtmConnectionState === nativeLinkStateMapping.CONNECTED &&
193
- RTMEngine.getInstance().isEngineReady
194
- ) {
195
- logger.log(
196
- LogSource.AgoraSDK,
197
- 'Log',
198
- '🚫 RTM already connected, skipping initialization',
199
- );
200
- return;
201
- }
202
-
203
- try {
204
- if (!RTMEngine.getInstance().isEngineReady) {
205
- RTMEngine.getInstance().setLocalUID(rtcUid);
206
- logger.log(LogSource.AgoraSDK, 'API', 'RTM local Uid set', rtcUid);
207
- }
208
- engine.current = RTMEngine.getInstance().engine;
209
- // Logout any opened sessions if any
210
- engine.current.logout();
211
- logger.log(LogSource.AgoraSDK, 'Log', 'RTM client creation done');
212
- } catch (error) {
213
- logger.error(
214
- LogSource.AgoraSDK,
215
- 'Log',
216
- 'RTM engine initialization failed:',
217
- {error},
218
- );
219
- throw error;
220
- }
221
-
222
- engine.current.addEventListener(
223
- 'linkState',
224
- async (data: LinkStateEvent) => {
225
- // Update connection state for duplicate initialization prevention
226
- setRtmConnectionState(data.currentState);
227
- logger.log(
228
- LogSource.AgoraSDK,
229
- 'Event',
230
- `RTM linkState changed: ${data.previousState} -> ${data.currentState}`,
231
- data,
232
- );
233
- if (data.currentState === nativeLinkStateMapping.CONNECTED) {
234
- // CONNECTED state
235
- logger.log(LogSource.AgoraSDK, 'Event', 'RTM connected', {
236
- previousState: data.previousState,
237
- currentState: data.currentState,
238
- });
239
- }
240
- if (data.currentState === nativeLinkStateMapping.FAILED) {
241
- // FAILED state
242
- logger.error(LogSource.AgoraSDK, 'Event', 'RTM connection failed', {
243
- error: {
244
- reasonCode: data.reasonCode,
245
- currentState: data.currentState,
246
- },
247
- });
248
- }
249
- },
250
- );
251
-
252
- engine.current.addEventListener('storage', (storage: StorageEvent) => {
253
- // when remote user sets/updates metadata - 3
254
- if (
255
- storage.eventType === nativeStorageEventTypeMapping.SET ||
256
- storage.eventType === nativeStorageEventTypeMapping.UPDATE
257
- ) {
258
- const storageTypeStr = storage.storageType === 1 ? 'user' : 'channel';
259
- const eventTypeStr = storage.eventType === 2 ? 'SET' : 'UPDATE';
260
- logger.log(
261
- LogSource.AgoraSDK,
262
- 'Event',
263
- `RTM storage event of type: [${eventTypeStr} ${storageTypeStr} metadata]`,
264
- storage,
265
- );
266
- try {
267
- if (storage.data?.items && Array.isArray(storage.data.items)) {
268
- storage.data.items.forEach(item => {
269
- try {
270
- if (!item || !item.key) {
271
- logger.warn(
272
- LogSource.Events,
273
- 'CUSTOM_EVENTS',
274
- 'Invalid storage item:',
275
- item,
276
- );
277
- return;
278
- }
279
-
280
- const {key, value, authorUserId, updateTs} = item;
281
- const timestamp = getMessageTime(updateTs);
282
- const sender = Platform.OS
283
- ? get32BitUid(authorUserId)
284
- : parseInt(authorUserId, 10);
285
- eventDispatcher(
286
- {
287
- evt: key,
288
- value,
289
- },
290
- `${sender}`,
291
- timestamp,
292
- );
293
- } catch (error) {
294
- logger.error(
295
- LogSource.Events,
296
- 'CUSTOM_EVENTS',
297
- `Failed to process storage item: ${JSON.stringify(item)}`,
298
- {error},
299
- );
300
- }
301
- });
302
- }
303
- } catch (error) {
304
- logger.error(
305
- LogSource.Events,
306
- 'CUSTOM_EVENTS',
307
- 'error while dispatching through eventDispatcher',
308
- {error},
309
- );
310
- }
311
- }
312
- });
313
-
314
- engine.current.addEventListener(
315
- 'presence',
316
- async (presence: PresenceEvent) => {
317
- if (`${localUid}` === presence.publisher) {
318
- return;
319
- }
320
- // remoteJoinChannel
321
- if (presence.type === nativePresenceEventTypeMapping.REMOTE_JOIN) {
322
- logger.log(
323
- LogSource.AgoraSDK,
324
- 'Event',
325
- 'RTM presenceEvent of type [3 - remoteJoin] (channelMemberJoined)',
326
- );
327
- const backoffAttributes = await fetchUserAttributesWithBackoffRetry(
328
- presence.publisher,
329
- );
330
- await processUserUidAttributes(backoffAttributes, presence.publisher);
331
- }
332
- // remoteLeaveChannel
333
- if (presence.type === nativePresenceEventTypeMapping.REMOTE_LEAVE) {
334
- logger.log(
335
- LogSource.AgoraSDK,
336
- 'Event',
337
- 'RTM presenceEvent of type [4 - remoteLeave] (channelMemberLeft)',
338
- presence,
339
- );
340
- // Chat of left user becomes undefined. So don't cleanup
341
- const uid = presence?.publisher
342
- ? parseInt(presence.publisher, 10)
343
- : undefined;
344
-
345
- if (!uid) {
346
- return;
347
- }
348
- SDKEvents.emit('_rtm-left', uid);
349
- // updating the rtc data
350
- updateRenderListState(uid, {
351
- offline: true,
352
- });
353
- }
354
- },
355
- );
356
-
357
- engine.current.addEventListener('message', (message: MessageEvent) => {
358
- if (`${localUid}` === message.publisher) {
359
- return;
360
- }
361
- // message - 1 (channel)
362
- if (message.channelType === nativeChannelTypeMapping.MESSAGE) {
363
- logger.debug(
364
- LogSource.Events,
365
- 'CUSTOM_EVENTS',
366
- 'messageEvent of type [1 - CHANNEL] (channelMessageReceived)',
367
- message,
368
- );
369
- const {
370
- publisher: uid,
371
- channelName: channelId,
372
- message: text,
373
- timestamp: ts,
374
- } = message;
375
- //whiteboard upload
376
- if (parseInt(uid, 10) === 1010101) {
377
- const [err, res] = safeJsonParse(text);
378
- if (err) {
379
- logger.error(
380
- LogSource.Events,
381
- 'CUSTOM_EVENTS',
382
- 'JSON payload incorrect, Error while parsing the payload',
383
- {error: err},
384
- );
385
- }
386
- if (res?.data?.data?.images) {
387
- LocalEventEmitter.emit(
388
- LocalEventsEnum.WHITEBOARD_FILE_UPLOAD,
389
- res?.data?.data?.images,
390
- );
391
- }
392
- } else {
393
- const [err, msg] = safeJsonParse(text);
394
- if (err) {
395
- logger.error(
396
- LogSource.Events,
397
- 'CUSTOM_EVENTS',
398
- 'JSON payload incorrect, Error while parsing the payload',
399
- {error: err},
400
- );
401
- }
402
-
403
- const timestamp = getMessageTime(ts);
404
- const sender = Platform.OS ? get32BitUid(uid) : parseInt(uid, 10);
405
-
406
- if (channelId === rtcProps.channel) {
407
- try {
408
- eventDispatcher(msg, `${sender}`, timestamp);
409
- } catch (error) {
410
- logger.error(
411
- LogSource.Events,
412
- 'CUSTOM_EVENTS',
413
- 'error while dispatching through eventDispatcher',
414
- {error},
415
- );
416
- }
417
- }
418
- }
419
- }
420
-
421
- // message - 3 (user)
422
- if (message.channelType === nativeChannelTypeMapping.USER) {
423
- logger.debug(
424
- LogSource.Events,
425
- 'CUSTOM_EVENTS',
426
- 'messageEvent of type [3- USER] (messageReceived)',
427
- message,
428
- );
429
- const {publisher: peerId, timestamp: ts, message: text} = message;
430
- const [err, msg] = safeJsonParse(text);
431
- if (err) {
432
- logger.error(
433
- LogSource.Events,
434
- 'CUSTOM_EVENTS',
435
- 'JSON payload incorrect, Error while parsing the payload',
436
- {error: err},
437
- );
438
- }
28
+ const {room} = props;
29
+ const {client} = useRTMCore();
439
30
 
440
- const timestamp = getMessageTime(ts);
31
+ // Call hooks unconditionally, but only use data based on room type
32
+ let rtmMainData = null;
33
+ let rtmBreakoutData = null;
34
+ rtmMainData = useRTMConfigureMain();
35
+ rtmBreakoutData = useRTMConfigureBreakout();
441
36
 
442
- const sender = isAndroid() ? get32BitUid(peerId) : parseInt(peerId, 10);
37
+ const rtmData = room === RTM_ROOMS.MAIN ? rtmMainData : rtmBreakoutData;
443
38
 
444
- try {
445
- eventDispatcher(msg, `${sender}`, timestamp);
446
- } catch (error) {
447
- logger.error(
448
- LogSource.Events,
449
- 'CUSTOM_EVENTS',
450
- 'error while dispatching through eventDispatcher',
451
- {error},
452
- );
453
- }
454
- }
455
- });
456
-
457
- await doLoginAndSetupRTM();
458
- };
459
-
460
- const doLoginAndSetupRTM = async () => {
461
- try {
462
- logger.log(LogSource.AgoraSDK, 'API', 'RTM login starts');
463
- await engine.current.login({
464
- // @ts-ignore
465
- token: rtcProps.rtm,
466
- });
467
- logger.log(LogSource.AgoraSDK, 'API', 'RTM login done');
468
- timerValueRef.current = 5;
469
- // waiting for login to be fully connected
470
- await new Promise(resolve => setTimeout(resolve, 500));
471
- await setAttribute();
472
- } catch (error) {
473
- logger.error(
474
- LogSource.AgoraSDK,
475
- 'Log',
476
- 'RTM login failed..Trying again',
477
- {error},
478
- );
479
- setTimeout(async () => {
480
- // Cap the timer to prevent excessive delays (max 30 seconds)
481
- timerValueRef.current = Math.min(timerValueRef.current * 2, 30);
482
- doLoginAndSetupRTM();
483
- }, timerValueRef.current * 1000);
484
- }
485
- };
486
-
487
- const setAttribute = async () => {
488
- const rtmAttributes = [
489
- {key: 'screenUid', value: String(rtcProps.screenShareUid)},
490
- {key: 'isHost', value: String(isHostRef.current.isHost)},
491
- ];
492
- try {
493
- const data: Metadata = {
494
- items: rtmAttributes,
495
- };
496
- const options: SetOrUpdateUserMetadataOptions = {
497
- userId: `${localUid}`,
498
- };
499
- await engine.current.storage.setUserMetadata(data, options);
500
- logger.log(
501
- LogSource.AgoraSDK,
502
- 'API',
503
- 'RTM setting local user attributes',
504
- {
505
- attr: rtmAttributes,
506
- },
507
- );
508
- timerValueRef.current = 5;
509
- await subscribeChannel();
510
- logger.log(
511
- LogSource.AgoraSDK,
512
- 'Log',
513
- 'RTM subscribe, fetch members, reading channel atrributes all done',
514
- {
515
- data: rtmAttributes,
516
- },
517
- );
518
- setHasUserJoinedRTM(true);
519
- await runQueuedEvents();
520
- setIsInitialQueueCompleted(true);
521
- logger.log(
522
- LogSource.AgoraSDK,
523
- 'Log',
524
- 'RTM queued events finished running',
525
- {
526
- attr: rtmAttributes,
527
- },
528
- );
529
- } catch (error) {
530
- logger.error(
531
- LogSource.AgoraSDK,
532
- 'Log',
533
- 'RTM setAttribute failed..Trying again',
534
- {error},
535
- );
536
- setTimeout(async () => {
537
- // Cap the timer to prevent excessive delays (max 30 seconds)
538
- timerValueRef.current = Math.min(timerValueRef.current * 2, 30);
539
- setAttribute();
540
- }, timerValueRef.current * 1000);
541
- }
542
- };
543
-
544
- const subscribeChannel = async () => {
545
- try {
546
- if (RTMEngine.getInstance().channelUid === rtcProps.channel) {
547
- logger.debug(
548
- LogSource.AgoraSDK,
549
- 'Log',
550
- '🚫 RTM already subscribed channel skipping',
551
- rtcProps.channel,
552
- );
553
- } else {
554
- await engine.current.subscribe(rtcProps.channel, {
555
- withMessage: true,
556
- withPresence: true,
557
- withMetadata: true,
558
- withLock: false,
559
- });
560
- logger.log(LogSource.AgoraSDK, 'API', 'RTM subscribeChannel', {
561
- data: rtcProps.channel,
562
- });
563
-
564
- // Set channel ID AFTER successful subscribe (like v1.5x)
565
- RTMEngine.getInstance().setChannelId(rtcProps.channel);
566
- logger.log(
567
- LogSource.AgoraSDK,
568
- 'API',
569
- 'RTM setChannelId as subscribe is successful',
570
- rtcProps.channel,
571
- );
572
-
573
- logger.debug(
574
- LogSource.SDK,
575
- 'Event',
576
- 'Emitting rtm joined',
577
- rtcProps.channel,
578
- );
579
- // @ts-ignore
580
- SDKEvents.emit('_rtm-joined', rtcProps.channel);
581
- timerValueRef.current = 5;
582
- await getMembers();
583
- await readAllChannelAttributes();
584
- logger.log(
585
- LogSource.AgoraSDK,
586
- 'Log',
587
- 'RTM readAllChannelAttributes and getMembers done',
588
- );
589
- }
590
- } catch (error) {
591
- logger.error(
592
- LogSource.AgoraSDK,
593
- 'Log',
594
- 'RTM subscribeChannel failed..Trying again',
595
- {error},
596
- );
597
- setTimeout(async () => {
598
- // Cap the timer to prevent excessive delays (max 30 seconds)
599
- timerValueRef.current = Math.min(timerValueRef.current * 2, 30);
600
- subscribeChannel();
601
- }, timerValueRef.current * 1000);
602
- }
603
- };
604
-
605
- const getMembers = async () => {
606
- try {
607
- logger.log(
608
- LogSource.AgoraSDK,
609
- 'API',
610
- 'RTM presence.getOnlineUsers(getMembers) start',
611
- );
612
- await engine.current.presence
613
- .getOnlineUsers(rtcProps.channel, 1)
614
- .then(async (data: GetOnlineUsersResponse) => {
615
- logger.log(
616
- LogSource.AgoraSDK,
617
- 'API',
618
- 'RTM presence.getOnlineUsers data received',
619
- data,
620
- );
621
- await Promise.all(
622
- data.occupants?.map(async member => {
623
- try {
624
- const backoffAttributes =
625
- await fetchUserAttributesWithBackoffRetry(member.userId);
626
-
627
- await processUserUidAttributes(
628
- backoffAttributes,
629
- member.userId,
630
- );
631
- // setting screenshare data
632
- // name of the screenUid, isActive: false, (when the user starts screensharing it becomes true)
633
- // isActive to identify all active screenshare users in the call
634
- backoffAttributes?.items?.forEach(item => {
635
- try {
636
- if (hasJsonStructure(item.value as string)) {
637
- const data = {
638
- evt: item.key, // Use item.key instead of key
639
- value: item.value, // Use item.value instead of value
640
- };
641
- // TODOSUP: Add the data to queue, dont add same mulitple events, use set so as to not repeat events
642
- EventsQueue.enqueue({
643
- data: data,
644
- uid: member.userId,
645
- ts: timeNow(),
646
- });
647
- }
648
- } catch (error) {
649
- logger.error(
650
- LogSource.AgoraSDK,
651
- 'Log',
652
- `RTM Failed to process user attribute item for ${
653
- member.userId
654
- }: ${JSON.stringify(item)}`,
655
- {error},
656
- );
657
- // Continue processing other items
658
- }
659
- });
660
- } catch (e) {
661
- logger.error(
662
- LogSource.AgoraSDK,
663
- 'Log',
664
- `RTM Could not retrieve name of ${member.userId}`,
665
- {error: e},
666
- );
667
- }
668
- }),
669
- );
670
- logger.debug(
671
- LogSource.AgoraSDK,
672
- 'Log',
673
- 'RTM fetched all data and user attr...RTM init done',
674
- );
675
- });
676
- timerValueRef.current = 5;
677
- } catch (error) {
678
- setTimeout(async () => {
679
- // Cap the timer to prevent excessive delays (max 30 seconds)
680
- timerValueRef.current = Math.min(timerValueRef.current * 2, 30);
681
- await getMembers();
682
- }, timerValueRef.current * 1000);
683
- }
684
- };
685
-
686
- const readAllChannelAttributes = async () => {
687
- try {
688
- await engine.current.storage
689
- .getChannelMetadata(rtcProps.channel, 1)
690
- .then(async (data: GetChannelMetadataResponse) => {
691
- for (const item of data.items) {
692
- try {
693
- const {key, value, authorUserId, updateTs} = item;
694
- if (hasJsonStructure(value as string)) {
695
- const evtData = {
696
- evt: key,
697
- value,
698
- };
699
- // TODOSUP: Add the data to queue, dont add same mulitple events, use set so as to not repeat events
700
- EventsQueue.enqueue({
701
- data: evtData,
702
- uid: authorUserId,
703
- ts: updateTs,
704
- });
705
- }
706
- } catch (error) {
707
- logger.error(
708
- LogSource.AgoraSDK,
709
- 'Log',
710
- `RTM Failed to process channel attribute item: ${JSON.stringify(
711
- item,
712
- )}`,
713
- {error},
714
- );
715
- // Continue processing other items
716
- }
717
- }
718
- logger.log(
719
- LogSource.AgoraSDK,
720
- 'API',
721
- 'RTM storage.getChannelMetadata data received',
722
- data,
723
- );
724
- });
725
- timerValueRef.current = 5;
726
- } catch (error) {
727
- setTimeout(async () => {
728
- // Cap the timer to prevent excessive delays (max 30 seconds)
729
- timerValueRef.current = Math.min(timerValueRef.current * 2, 30);
730
- await readAllChannelAttributes();
731
- }, timerValueRef.current * 1000);
732
- }
733
- };
734
-
735
- const fetchUserAttributesWithBackoffRetry = async (
736
- userId: string,
737
- ): Promise<GetUserMetadataResponse> => {
738
- return backOff(
739
- async () => {
740
- logger.log(
741
- LogSource.AgoraSDK,
742
- 'API',
743
- `RTM fetching getUserMetadata for member ${userId}`,
744
- );
745
-
746
- const attr: GetUserMetadataResponse =
747
- await engine.current.storage.getUserMetadata({
748
- userId: userId,
749
- });
750
-
751
- if (!attr || !attr.items) {
752
- logger.log(
753
- LogSource.AgoraSDK,
754
- 'API',
755
- 'RTM attributes for member not found',
756
- );
757
- throw attr;
758
- }
759
-
760
- logger.log(
761
- LogSource.AgoraSDK,
762
- 'API',
763
- `RTM getUserMetadata for member ${userId} received`,
764
- {attr},
765
- );
766
-
767
- if (attr.items && attr.items.length > 0) {
768
- return attr;
769
- } else {
770
- throw attr;
771
- }
772
- },
773
- {
774
- retry: (e, idx) => {
775
- logger.debug(
776
- LogSource.AgoraSDK,
777
- 'Log',
778
- `RTM [retrying] Attempt ${idx}. Fetching ${userId}'s attributes`,
779
- e,
780
- );
781
- return true;
782
- },
783
- },
39
+ if (!rtmData) {
40
+ throw new Error(
41
+ `RTMConfigure: Invalid room prop '${room}' or missing context provider`,
784
42
  );
785
- };
786
-
787
- const processUserUidAttributes = async (
788
- attr: GetUserMetadataResponse,
789
- userId: string,
790
- ) => {
791
- try {
792
- console.log('[user attributes]:', {attr});
793
- const uid = parseInt(userId, 10);
794
- const screenUidItem = attr?.items?.find(item => item.key === 'screenUid');
795
- const isHostItem = attr?.items?.find(item => item.key === 'isHost');
796
- const screenUid = screenUidItem?.value
797
- ? parseInt(screenUidItem.value, 10)
798
- : undefined;
799
-
800
- //start - updating user data in rtc
801
- const userData = {
802
- screenUid: screenUid,
803
- //below thing for livestreaming
804
- type: uid === parseInt(RECORDING_BOT_UID, 10) ? 'bot' : 'rtc',
805
- uid,
806
- offline: false,
807
- isHost: isHostItem?.value || false,
808
- lastMessageTimeStamp: 0,
809
- };
810
- updateRenderListState(uid, userData);
811
- //end- updating user data in rtc
812
-
813
- //start - updating screenshare data in rtc
814
- if (screenUid) {
815
- const screenShareUser = {
816
- type: UserType.ScreenShare,
817
- parentUid: uid,
818
- };
819
- updateRenderListState(screenUid, screenShareUser);
820
- }
821
- //end - updating screenshare data in rtc
822
- } catch (e) {
823
- logger.error(
824
- LogSource.AgoraSDK,
825
- 'Event',
826
- `RTM Failed to process user data for ${userId}`,
827
- {error: e},
828
- );
829
- }
830
- };
831
-
832
- const updateRenderListState = (
833
- uid: number,
834
- data: Partial<ContentInterface>,
835
- ) => {
836
- dispatch({type: 'UpdateRenderList', value: [uid, data]});
837
- };
838
-
839
- const runQueuedEvents = async () => {
840
- try {
841
- while (!EventsQueue.isEmpty()) {
842
- const currEvt = EventsQueue.dequeue();
843
- await eventDispatcher(currEvt.data, `${currEvt.uid}`, currEvt.ts);
844
- }
845
- } catch (error) {
846
- logger.error(
847
- LogSource.Events,
848
- 'CUSTOM_EVENTS',
849
- 'error while running queue events',
850
- {error},
851
- );
852
- }
853
- };
854
-
855
- const eventDispatcher = async (
856
- data: {
857
- evt: string;
858
- value: string;
859
- feat?: string;
860
- etyp?: string;
861
- },
862
- sender: string,
863
- ts: number,
864
- ) => {
865
- console.log(
866
- LogSource.Events,
867
- 'CUSTOM_EVENTS',
868
- 'inside eventDispatcher ',
869
- data,
870
- );
871
-
872
- let evt = '',
873
- value = '';
874
-
875
- if (data?.feat === 'WAITING_ROOM') {
876
- if (data?.etyp === 'REQUEST') {
877
- const outputData = {
878
- evt: `${data.feat}_${data.etyp}`,
879
- payload: JSON.stringify({
880
- attendee_uid: data.data.data.attendee_uid,
881
- attendee_screenshare_uid: data.data.data.attendee_screenshare_uid,
882
- }),
883
- persistLevel: 1,
884
- source: 'core',
885
- };
886
- const formattedData = JSON.stringify(outputData);
887
- evt = data.feat + '_' + data.etyp; //rename if client side RTM meessage is to be sent for approval
888
- value = formattedData;
889
- }
890
- if (data?.etyp === 'RESPONSE') {
891
- const outputData = {
892
- evt: `${data.feat}_${data.etyp}`,
893
- payload: JSON.stringify({
894
- approved: data.data.data.approved,
895
- channelName: data.data.data.channel_name,
896
- mainUser: data.data.data.mainUser,
897
- screenShare: data.data.data.screenShare,
898
- whiteboard: data.data.data.whiteboard,
899
- chat: data.data.data?.chat,
900
- }),
901
- persistLevel: 1,
902
- source: 'core',
903
- };
904
- const formattedData = JSON.stringify(outputData);
905
- evt = data.feat + '_' + data.etyp;
906
- value = formattedData;
907
- }
908
- } else {
909
- if (
910
- $config.ENABLE_WAITING_ROOM &&
911
- !isHostRef.current?.isHost &&
912
- waitingRoomStatusRef.current?.waitingRoomStatus !==
913
- WaitingRoomStatus.APPROVED
914
- ) {
915
- if (
916
- data.evt === controlMessageEnum.muteAudio ||
917
- data.evt === controlMessageEnum.muteVideo
918
- ) {
919
- return;
920
- } else {
921
- evt = data.evt;
922
- value = data.value;
923
- }
924
- } else {
925
- evt = data.evt;
926
- value = data.value;
927
- }
928
- }
929
-
930
- try {
931
- let parsedValue;
932
- try {
933
- parsedValue = typeof value === 'string' ? JSON.parse(value) : value;
934
- } catch (error) {
935
- logger.error(
936
- LogSource.Events,
937
- 'CUSTOM_EVENTS',
938
- 'RTM Failed to parse event value in event dispatcher:',
939
- {error},
940
- );
941
- return;
942
- }
943
- const {payload, persistLevel, source} = parsedValue;
944
- // Step 1: Set local attributes
945
- if (persistLevel === PersistanceLevel.Session) {
946
- const rtmAttribute = {key: evt, value: value};
947
- const options: SetOrUpdateUserMetadataOptions = {
948
- userId: `${localUid}`,
949
- };
950
- await engine.current.storage.setUserMetadata(
951
- {
952
- items: [rtmAttribute],
953
- },
954
- options,
955
- );
956
- }
957
- // Step 2: Emit the event
958
- console.log(LogSource.Events, 'CUSTOM_EVENTS', 'emiting event..: ');
959
- EventUtils.emitEvent(evt, source, {payload, persistLevel, sender, ts});
960
- // Because async gets evaluated in a different order when in an sdk
961
- if (evt === 'name') {
962
- // 1. Cancel existing timeout for this sender
963
- if (eventTimeouts.has(sender)) {
964
- clearTimeout(eventTimeouts.get(sender)!);
965
- }
966
- // 2. Create new timeout with tracking
967
- const timeout = setTimeout(() => {
968
- // 3. Guard against unmounted component
969
- if (!isRTMMounted.current) {
970
- return;
971
- }
972
- EventUtils.emitEvent(evt, source, {
973
- payload,
974
- persistLevel,
975
- sender,
976
- ts,
977
- });
978
- // 4. Clean up after execution
979
- eventTimeouts.delete(sender);
980
- }, 200);
981
- // 5. Track the timeout for cleanup
982
- eventTimeouts.set(sender, timeout);
983
- }
984
- } catch (error) {
985
- console.error(
986
- LogSource.Events,
987
- 'CUSTOM_EVENTS',
988
- 'error while emiting event:',
989
- {error},
990
- );
991
- }
992
- };
993
-
994
- const end = async () => {
995
- if (!callActive) {
996
- return;
997
- }
998
- // Destroy and clean up RTM state
999
- await RTMEngine.getInstance().destroy();
1000
- // Set the engine as null
1001
- engine.current = null;
1002
- logger.log(LogSource.AgoraSDK, 'API', 'RTM destroy done');
1003
- if (isIOS() || isAndroid()) {
1004
- EventUtils.clear();
1005
- }
1006
- setHasUserJoinedRTM(false);
1007
- setIsInitialQueueCompleted(false);
1008
- logger.debug(LogSource.AgoraSDK, 'Log', 'RTM cleanup done');
1009
- };
1010
-
1011
- useAsyncEffect(async () => {
1012
- //waiting room attendee -> rtm login will happen on page load
1013
- try {
1014
- if ($config.ENABLE_WAITING_ROOM) {
1015
- //attendee
1016
- //for waiting room attendee rtm login will happen on mount
1017
- if (!isHost && !callActive) {
1018
- await init(localUid);
1019
- }
1020
- //host
1021
- if (
1022
- isHost &&
1023
- ($config.AUTO_CONNECT_RTM ||
1024
- (!$config.AUTO_CONNECT_RTM && callActive))
1025
- ) {
1026
- await init(localUid);
1027
- }
1028
- } else {
1029
- //non waiting room case
1030
- //host and attendee
1031
- if (
1032
- $config.AUTO_CONNECT_RTM ||
1033
- (!$config.AUTO_CONNECT_RTM && callActive)
1034
- ) {
1035
- await init(localUid);
1036
- }
1037
- }
1038
- } catch (error) {
1039
- logger.error(LogSource.AgoraSDK, 'Log', 'RTM init failed', {error});
1040
- }
1041
- return async () => {
1042
- logger.log(
1043
- LogSource.AgoraSDK,
1044
- 'Log',
1045
- 'RTM unmounting calling end(destroy) ',
1046
- );
1047
- await end();
1048
- };
1049
- }, [rtcProps.channel, rtcProps.appId, callActive, localUid]);
43
+ }
1050
44
 
1051
45
  return (
1052
46
  <ChatContext.Provider
1053
47
  value={{
1054
- isInitialQueueCompleted,
1055
- rtmInitTimstamp,
1056
- hasUserJoinedRTM,
1057
- engine: engine.current,
48
+ isInitialQueueCompleted: rtmData.isInitialQueueCompleted,
49
+ rtmInitTimstamp: rtmData.rtmInitTimstamp,
50
+ hasUserJoinedRTM: rtmData.hasUserJoinedRTM,
51
+ engine: client,
1058
52
  localUid: localUid,
1059
- onlineUsersCount,
53
+ onlineUsersCount: rtmData.onlineUsersCount,
54
+ syncUserState: rtmData.syncUserState,
1060
55
  }}>
1061
56
  {props.children}
1062
57
  </ChatContext.Provider>