agora-appbuilder-core 4.1.9-beta.3 → 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 (109) hide show
  1. package/package.json +2 -2
  2. package/template/Gulpfile.js +0 -16
  3. package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -3
  4. package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -2
  5. package/template/agora-rn-uikit/src/Reducer/index.ts +0 -2
  6. package/template/agora-rn-uikit/src/Rtc/Join.tsx +11 -25
  7. package/template/agora-rn-uikit/src/RtcConfigure.tsx +1 -14
  8. package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
  9. package/template/bridge/rtm/web/index.ts +30 -0
  10. package/template/customization-api/typeDefinition.ts +1 -0
  11. package/template/defaultConfig.js +3 -2
  12. package/template/global.d.ts +1 -0
  13. package/template/package.json +0 -2
  14. package/template/src/AppRoutes.tsx +3 -3
  15. package/template/src/ai-agent/components/ControlButtons.tsx +1 -1
  16. package/template/src/assets/font-styles.css +36 -0
  17. package/template/src/assets/fonts/icomoon.ttf +0 -0
  18. package/template/src/assets/selection.json +1 -1
  19. package/template/src/atoms/CustomIcon.tsx +8 -0
  20. package/template/src/atoms/Dropdown.tsx +5 -0
  21. package/template/src/atoms/TertiaryButton.tsx +1 -1
  22. package/template/src/atoms/UserAvatar.tsx +1 -1
  23. package/template/src/components/ChatContext.ts +5 -3
  24. package/template/src/components/Controls.tsx +68 -22
  25. package/template/src/components/DeviceConfigure.tsx +1 -1
  26. package/template/src/components/EventsConfigure.tsx +22 -17
  27. package/template/src/components/Navbar.tsx +14 -11
  28. package/template/src/components/RTMConfigure.tsx +31 -1036
  29. package/template/src/components/UserGlobalPreferenceProvider.tsx +227 -0
  30. package/template/src/components/beauty-effect/useBeautyEffects.tsx +50 -13
  31. package/template/src/components/breakout-room/BreakoutRoomPanel.tsx +58 -0
  32. package/template/src/components/breakout-room/context/BreakoutRoomContext.tsx +2508 -0
  33. package/template/src/components/breakout-room/events/BreakoutRoomEventsConfigure.tsx +272 -0
  34. package/template/src/components/breakout-room/events/constants.ts +17 -0
  35. package/template/src/components/breakout-room/hoc/BreakoutRoomNameRenderer.tsx +68 -0
  36. package/template/src/components/breakout-room/hooks/useBreakoutRoomExit.ts +49 -0
  37. package/template/src/components/breakout-room/state/reducer.ts +522 -0
  38. package/template/src/components/breakout-room/state/types.ts +54 -0
  39. package/template/src/components/breakout-room/ui/BreakoutMeetingTitle.tsx +60 -0
  40. package/template/src/components/breakout-room/ui/BreakoutRoomActionMenu.tsx +136 -0
  41. package/template/src/components/breakout-room/ui/BreakoutRoomAnnouncementModal.tsx +135 -0
  42. package/template/src/components/breakout-room/ui/BreakoutRoomGroupSettings.tsx +588 -0
  43. package/template/src/components/breakout-room/ui/BreakoutRoomMainRoomUsers.tsx +142 -0
  44. package/template/src/components/breakout-room/ui/BreakoutRoomMemberActionMenu.tsx +122 -0
  45. package/template/src/components/breakout-room/ui/BreakoutRoomParticipants.tsx +124 -0
  46. package/template/src/components/breakout-room/ui/BreakoutRoomRaiseHand.tsx +65 -0
  47. package/template/src/components/breakout-room/ui/BreakoutRoomRenameModal.tsx +227 -0
  48. package/template/src/components/breakout-room/ui/BreakoutRoomSettings.tsx +140 -0
  49. package/template/src/components/breakout-room/ui/BreakoutRoomTransition.tsx +52 -0
  50. package/template/src/components/breakout-room/ui/BreakoutRoomView.tsx +193 -0
  51. package/template/src/components/breakout-room/ui/ExitBreakoutRoomIconButton.tsx +79 -0
  52. package/template/src/components/breakout-room/ui/ParticipantManualAssignmentModal.tsx +638 -0
  53. package/template/src/components/breakout-room/ui/SelectParticipantAssignmentStrategy.tsx +57 -0
  54. package/template/src/components/chat/chatConfigure.tsx +7 -1
  55. package/template/src/components/chat-messages/useChatMessages.tsx +43 -11
  56. package/template/src/components/common/Dividers.tsx +53 -0
  57. package/template/src/components/controls/toolbar-items/ExitBreakoutRoomToolbarItem.tsx +13 -0
  58. package/template/src/components/controls/useControlPermissionMatrix.tsx +32 -4
  59. package/template/src/components/participants/AllHostParticipants.tsx +10 -2
  60. package/template/src/components/participants/Participant.tsx +7 -1
  61. package/template/src/components/participants/UserActionMenuOptions.tsx +12 -2
  62. package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +12 -8
  63. package/template/src/components/precall/joinWaitingRoomBtn.tsx +14 -10
  64. package/template/src/components/raise-hand/RaiseHandButton.tsx +50 -0
  65. package/template/src/components/raise-hand/RaiseHandProvider.tsx +308 -0
  66. package/template/src/components/raise-hand/index.ts +14 -0
  67. package/template/src/components/recordings/RecordingsDateTable.tsx +3 -2
  68. package/template/src/components/room-info/useCurrentRoomInfo.tsx +42 -0
  69. package/template/src/components/room-info/useSetBreakoutRoomInfo.tsx +64 -0
  70. package/template/src/components/useUserPreference.tsx +39 -12
  71. package/template/src/components/virtual-background/useVB.tsx +18 -0
  72. package/template/src/components/whiteboard/WhiteboardConfigure.tsx +27 -0
  73. package/template/src/language/default-labels/videoCallScreenLabels.ts +7 -0
  74. package/template/src/logger/AppBuilderLogger.tsx +11 -3
  75. package/template/src/pages/VideoCall.tsx +171 -518
  76. package/template/src/pages/video-call/BreakoutVideoCall.tsx +213 -0
  77. package/template/src/pages/video-call/SidePanelHeader.tsx +17 -0
  78. package/template/src/pages/video-call/VideoCallContent.tsx +211 -0
  79. package/template/src/pages/video-call/VideoCallScreen.tsx +18 -0
  80. package/template/src/pages/video-call/VideoCallScreenWrapper.tsx +0 -1
  81. package/template/src/pages/video-call/VideoCallStateWrapper.tsx +495 -0
  82. package/template/src/rtm/RTMConfigureBreakoutRoomProvider.tsx +882 -0
  83. package/template/src/rtm/RTMConfigureMainRoomProvider.tsx +757 -0
  84. package/template/src/rtm/RTMCoreProvider.tsx +419 -0
  85. package/template/src/rtm/RTMEngine.ts +188 -60
  86. package/template/src/rtm/RTMGlobalStateProvider.tsx +706 -0
  87. package/template/src/rtm/RTMStatusBanner.tsx +99 -0
  88. package/template/src/rtm/constants.ts +12 -0
  89. package/template/src/rtm/hooks/useMainRoomUserDisplayName.ts +45 -0
  90. package/template/src/rtm/rtm-presence-utils.ts +344 -0
  91. package/template/src/rtm/utils.ts +68 -1
  92. package/template/src/rtm-events/constants.ts +40 -1
  93. package/template/src/rtm-events-api/Events.ts +62 -19
  94. package/template/src/subComponents/ChatBubble.tsx +3 -3
  95. package/template/src/subComponents/ChatContainer.tsx +19 -9
  96. package/template/src/subComponents/LocalAudioMute.tsx +2 -2
  97. package/template/src/subComponents/LocalVideoMute.tsx +2 -2
  98. package/template/src/subComponents/SidePanelEnum.tsx +1 -0
  99. package/template/src/subComponents/caption/useCaption.tsx +1 -1
  100. package/template/src/subComponents/chat/ChatAnnouncementView.tsx +65 -0
  101. package/template/src/subComponents/chat/ChatSendButton.tsx +1 -0
  102. package/template/src/subComponents/screenshare/ScreenshareButton.tsx +16 -0
  103. package/template/src/subComponents/screenshare/ScreenshareConfigure.native.tsx +1 -1
  104. package/template/src/subComponents/waiting-rooms/WaitingRoomControls.tsx +7 -4
  105. package/template/src/utils/useDebouncedCallback.tsx +20 -0
  106. package/template/src/utils/useEndCall.ts +0 -2
  107. package/template/src/utils/useMuteToggleLocal.ts +14 -10
  108. package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +0 -11
  109. package/template/agora-rn-uikit/src/Reducer/UserBanned.ts +0 -11
@@ -0,0 +1,882 @@
1
+ /*
2
+ ********************************************
3
+ Copyright © 2021 Agora Lab, Inc., all rights reserved.
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
6
+ accessed, used, modified, or distributed for any purpose without a license from Agora Lab, Inc.
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
9
+ information visit https://appbuilder.agora.io.
10
+ *********************************************
11
+ */
12
+
13
+ import React, {
14
+ useState,
15
+ useContext,
16
+ useEffect,
17
+ useRef,
18
+ createContext,
19
+ useCallback,
20
+ } from 'react';
21
+ import {
22
+ type MessageEvent,
23
+ type PresenceEvent,
24
+ type SetOrUpdateUserMetadataOptions,
25
+ type StorageEvent,
26
+ type RTMClient,
27
+ } from 'agora-react-native-rtm';
28
+ import {
29
+ ContentInterface,
30
+ DispatchContext,
31
+ useLocalUid,
32
+ } from '../../agora-rn-uikit';
33
+ import {Platform} from 'react-native';
34
+ import {isAndroid, isIOS} from '../utils/common';
35
+ import {useContent} from 'customization-api';
36
+ import {
37
+ safeJsonParse,
38
+ timeNow,
39
+ hasJsonStructure,
40
+ getMessageTime,
41
+ get32BitUid,
42
+ isEventForActiveChannel,
43
+ } from '../rtm/utils';
44
+ import {
45
+ fetchChannelAttributesWithRetries,
46
+ clearRoomScopedUserAttributes,
47
+ processUserAttributeForQueue,
48
+ } from './rtm-presence-utils';
49
+ import {EventUtils, EventsQueue} from '../rtm-events';
50
+ import {PersistanceLevel} from '../rtm-events-api';
51
+ import RTMEngine from '../rtm/RTMEngine';
52
+ import {filterObject} from '../utils';
53
+ import {useAsyncEffect} from '../utils/useAsyncEffect';
54
+ import {
55
+ WaitingRoomStatus,
56
+ useRoomInfo,
57
+ } from '../components/room-info/useRoomInfo';
58
+ import LocalEventEmitter, {
59
+ LocalEventsEnum,
60
+ } from '../rtm-events-api/LocalEvents';
61
+ import {controlMessageEnum} from '../components/ChatContext';
62
+ import {LogSource, logger} from '../logger/AppBuilderLogger';
63
+ import {RECORDING_BOT_UID} from '../utils/constants';
64
+ import {
65
+ nativeChannelTypeMapping,
66
+ nativePresenceEventTypeMapping,
67
+ nativeStorageEventTypeMapping,
68
+ } from '../../bridge/rtm/web/Types';
69
+ import {useRTMCore} from '../rtm/RTMCoreProvider';
70
+ import {
71
+ RTM_ROOMS,
72
+ RTM_EVENTS_ATTRIBUTES_TO_RESET_WHEN_ROOM_CHANGES,
73
+ } from './constants';
74
+ import {useUserGlobalPreferences} from '../components/UserGlobalPreferenceProvider';
75
+ import {ToggleState} from '../../agora-rn-uikit';
76
+ import useMuteToggleLocal from '../utils/useMuteToggleLocal';
77
+ import {useRtc} from 'customization-api';
78
+ import {
79
+ fetchOnlineMembersWithRetries,
80
+ fetchUserAttributesWithRetries,
81
+ mapUserAttributesToState,
82
+ } from './rtm-presence-utils';
83
+
84
+ export enum UserType {
85
+ ScreenShare = 'screenshare',
86
+ }
87
+
88
+ const eventTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
89
+
90
+ // RTM Breakout Room Context
91
+ export interface RTMBreakoutRoomData {
92
+ hasUserJoinedRTM: boolean;
93
+ isInitialQueueCompleted: boolean;
94
+ onlineUsersCount: number;
95
+ rtmInitTimstamp: number;
96
+ syncUserState: (uid: number, data: Partial<ContentInterface>) => void;
97
+ }
98
+
99
+ const RTMBreakoutRoomContext = createContext<RTMBreakoutRoomData>({
100
+ hasUserJoinedRTM: false,
101
+ isInitialQueueCompleted: false,
102
+ onlineUsersCount: 0,
103
+ rtmInitTimstamp: 0,
104
+ syncUserState: () => {},
105
+ });
106
+
107
+ export const useRTMConfigureBreakout = () => {
108
+ const context = useContext(RTMBreakoutRoomContext);
109
+ if (!context) {
110
+ throw new Error(
111
+ 'useRTMConfigureBreakout must be used within RTMConfigureBreakoutRoomProvider',
112
+ );
113
+ }
114
+ return context;
115
+ };
116
+
117
+ interface RTMConfigureBreakoutRoomProviderProps {
118
+ callActive: boolean;
119
+ children: React.ReactNode;
120
+ currentChannel: string;
121
+ }
122
+
123
+ const RTMConfigureBreakoutRoomProvider = (
124
+ props: RTMConfigureBreakoutRoomProviderProps,
125
+ ) => {
126
+ const rtmInitTimstamp = new Date().getTime();
127
+ const localUid = useLocalUid();
128
+ const {callActive, currentChannel} = props;
129
+ const {dispatch} = useContext(DispatchContext);
130
+ const {defaultContent, activeUids} = useContent();
131
+ console.log('rudra-core-client: activeUids: ', activeUids);
132
+ const {
133
+ waitingRoomStatus,
134
+ data: {isHost},
135
+ } = useRoomInfo();
136
+ const {applyUserPreferences, syncUserPreferences} =
137
+ useUserGlobalPreferences();
138
+ const toggleMute = useMuteToggleLocal();
139
+ const [hasUserJoinedRTM, setHasUserJoinedRTM] = useState<boolean>(false);
140
+ const [isInitialQueueCompleted, setIsInitialQueueCompleted] = useState(false);
141
+ const [onlineUsersCount, setTotalOnlineUsers] = useState<number>(0);
142
+
143
+ // Track RTM connection state (equivalent to v1.5x connectionState check)
144
+ const {client, isLoggedIn, registerCallbacks, unregisterCallbacks} =
145
+ useRTMCore();
146
+ const {rtcTracksReady} = useRtc();
147
+
148
+ /**
149
+ * Refs
150
+ */
151
+
152
+ const isRTMMounted = useRef(true);
153
+
154
+ const hasInitRef = useRef(false);
155
+ const subscribeTimerRef: any = useRef(5);
156
+ const subscribeTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(
157
+ null,
158
+ );
159
+
160
+ const channelAttributesTimerRef: any = useRef(5);
161
+ const channelAttributesTimeoutRef = useRef<ReturnType<
162
+ typeof setTimeout
163
+ > | null>(null);
164
+
165
+ const membersTimerRef: any = useRef(5);
166
+ const membersTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
167
+
168
+ const isHostRef = useRef({isHost: isHost});
169
+ useEffect(() => {
170
+ isHostRef.current.isHost = isHost;
171
+ }, [isHost]);
172
+
173
+ const waitingRoomStatusRef = useRef({waitingRoomStatus: waitingRoomStatus});
174
+ useEffect(() => {
175
+ waitingRoomStatusRef.current.waitingRoomStatus = waitingRoomStatus;
176
+ }, [waitingRoomStatus]);
177
+
178
+ const activeUidsRef = useRef({activeUids: activeUids});
179
+ useEffect(() => {
180
+ activeUidsRef.current.activeUids = activeUids;
181
+ }, [activeUids]);
182
+
183
+ const defaultContentRef = useRef(defaultContent);
184
+ useEffect(() => {
185
+ defaultContentRef.current = defaultContent;
186
+ }, [defaultContent]);
187
+
188
+ // Apply user preferences when breakout room mounts
189
+ useEffect(() => {
190
+ if (rtcTracksReady) {
191
+ console.log('supriya-permissions', defaultContentRef.current[localUid]);
192
+ applyUserPreferences(defaultContentRef.current[localUid], toggleMute);
193
+ }
194
+ }, [rtcTracksReady]);
195
+
196
+ // Sync current audio/video state audio video changes
197
+ useEffect(() => {
198
+ const userData = defaultContent[localUid];
199
+ if (rtcTracksReady && userData) {
200
+ console.log('UP: syncing userData: ', userData);
201
+ const preferences = {
202
+ audioMuted: userData.audio === ToggleState.disabled,
203
+ videoMuted: userData.video === ToggleState.disabled,
204
+ };
205
+ console.log('UP: saved preferences: ', preferences);
206
+ syncUserPreferences(preferences);
207
+ }
208
+ }, [defaultContent, localUid, syncUserPreferences, rtcTracksReady]);
209
+
210
+ const syncUserState = useCallback(
211
+ (uid: number, data: Partial<ContentInterface>) => {
212
+ dispatch({type: 'UpdateRenderList', value: [uid, data]});
213
+ },
214
+ [dispatch],
215
+ );
216
+
217
+ // Set online users
218
+ React.useEffect(() => {
219
+ setTotalOnlineUsers(
220
+ Object.keys(
221
+ filterObject(
222
+ defaultContent,
223
+ ([k, v]) =>
224
+ v?.type === 'rtc' &&
225
+ !v.offline &&
226
+ activeUidsRef.current.activeUids.indexOf(v?.uid) !== -1,
227
+ ),
228
+ ).length,
229
+ );
230
+ }, [defaultContent, activeUids]);
231
+
232
+ const init = async () => {
233
+ await subscribeChannel();
234
+ await getMembersWithAttributes();
235
+ await getChannelAttributes();
236
+ const result = await RTMEngine.getInstance().engine.presence.whereNow(
237
+ `${localUid}`,
238
+ );
239
+ console.log('rudra-core-client: user is now at channels ', result);
240
+ setHasUserJoinedRTM(true);
241
+ await runQueuedEvents();
242
+ setIsInitialQueueCompleted(true);
243
+ logger.log(LogSource.AgoraSDK, 'Log', 'RTM queued events finished running');
244
+ };
245
+
246
+ const subscribeChannel = async () => {
247
+ try {
248
+ if (RTMEngine.getInstance().allChannelIds.includes(currentChannel)) {
249
+ logger.debug(
250
+ LogSource.AgoraSDK,
251
+ 'Log',
252
+ '🚫 RTM already subscribed channel skipping',
253
+ currentChannel,
254
+ );
255
+ const channelids = RTMEngine.getInstance().allChannelIds;
256
+ console.log('rudra-core-client: alreadt subscribed', channelids);
257
+ } else {
258
+ await client.subscribe(currentChannel, {
259
+ withMessage: true,
260
+ withPresence: true,
261
+ withMetadata: true,
262
+ withLock: false,
263
+ });
264
+ logger.log(LogSource.AgoraSDK, 'API', 'RTM subscribeChannel', {
265
+ data: currentChannel,
266
+ });
267
+
268
+ // Set channel ID AFTER successful subscribe (like v1.5x)
269
+ console.log('setting primary channel', currentChannel);
270
+ RTMEngine.getInstance().addChannel(RTM_ROOMS.BREAKOUT, currentChannel);
271
+ RTMEngine.getInstance().setActiveChannelName(RTM_ROOMS.BREAKOUT);
272
+
273
+ // Clear any pending retry timeout since we succeeded
274
+ if (subscribeTimeoutRef.current) {
275
+ clearTimeout(subscribeTimeoutRef.current);
276
+ subscribeTimeoutRef.current = null;
277
+ }
278
+ }
279
+ } catch (error) {
280
+ logger.error(
281
+ LogSource.AgoraSDK,
282
+ 'Log',
283
+ 'RTM subscribeChannel failed..Trying again',
284
+ {error},
285
+ );
286
+ subscribeTimeoutRef.current = setTimeout(async () => {
287
+ // Cap the timer to prevent excessive delays (max 30 seconds)
288
+ subscribeTimerRef.current = Math.min(subscribeTimerRef.current * 2, 30);
289
+ subscribeChannel();
290
+ }, subscribeTimerRef.current * 1000);
291
+ }
292
+ };
293
+
294
+ const getMembersWithAttributes = async () => {
295
+ try {
296
+ logger.log(
297
+ LogSource.AgoraSDK,
298
+ 'API',
299
+ 'RTM presence.getOnlineUsers(getMembers) start',
300
+ );
301
+ console.log(
302
+ 'rudra-core-client: fetchOnlineMembersWithRetries inside breakout room ',
303
+ client,
304
+ currentChannel,
305
+ );
306
+ const {allMembers, totalOccupancy} = await fetchOnlineMembersWithRetries(
307
+ client,
308
+ currentChannel,
309
+ {
310
+ onPage: async ({occupants, pageToken}) => {
311
+ console.log(
312
+ 'rudra-core-client: fetching user attributes for page: ',
313
+ pageToken,
314
+ occupants,
315
+ );
316
+ await Promise.all(
317
+ occupants.map(async member => {
318
+ try {
319
+ const userAttributes = await fetchUserAttributesWithRetries(
320
+ client,
321
+ member.userId,
322
+ {
323
+ isMounted: () => isRTMMounted.current,
324
+ // 👈 called later if name arrives
325
+ onNameFound: retriedAttributesWithName =>
326
+ mapUserAttributesToState(
327
+ retriedAttributesWithName,
328
+ member.userId,
329
+ syncUserState,
330
+ ),
331
+ },
332
+ );
333
+ console.log(
334
+ `rudra-core-client: getting user attributes for user ${member.userId}`,
335
+ userAttributes,
336
+ );
337
+ mapUserAttributesToState(
338
+ userAttributes,
339
+ member.userId,
340
+ syncUserState,
341
+ );
342
+ // setting screenshare data
343
+ // name of the screenUid, isActive: false, (when the user starts screensharing it becomes true)
344
+ // isActive to identify all active screenshare users in the call
345
+ userAttributes?.items?.forEach(item => {
346
+ processUserAttributeForQueue(
347
+ item,
348
+ member.userId,
349
+ RTM_ROOMS.BREAKOUT,
350
+ (eventKey, value, userId) => {
351
+ const data = {evt: eventKey, value};
352
+ EventsQueue.enqueue({
353
+ data,
354
+ uid: userId,
355
+ ts: timeNow(),
356
+ });
357
+ },
358
+ );
359
+ });
360
+ } catch (e) {
361
+ logger.error(
362
+ LogSource.AgoraSDK,
363
+ 'Log',
364
+ `RTM Could not retrieve name of ${member.userId}`,
365
+ {error: e},
366
+ );
367
+ }
368
+ }),
369
+ );
370
+ },
371
+ },
372
+ );
373
+ console.log(
374
+ 'rudra-core-client: totalOccupancy',
375
+ allMembers,
376
+ totalOccupancy,
377
+ );
378
+ logger.debug(
379
+ LogSource.AgoraSDK,
380
+ 'Log',
381
+ 'RTM fetched all data and user attr...RTM init done',
382
+ );
383
+ membersTimerRef.current = 5;
384
+ // Clear any pending retry timeout since we succeeded
385
+ if (membersTimeoutRef.current) {
386
+ clearTimeout(membersTimeoutRef.current);
387
+ membersTimeoutRef.current = null;
388
+ }
389
+ } catch (error) {
390
+ membersTimeoutRef.current = setTimeout(async () => {
391
+ // Cap the timer to prevent excessive delays (max 30 seconds)
392
+ membersTimerRef.current = Math.min(membersTimerRef.current * 2, 30);
393
+ await getMembersWithAttributes();
394
+ }, membersTimerRef.current * 1000);
395
+ }
396
+ };
397
+
398
+ const getChannelAttributes = async () => {
399
+ try {
400
+ await fetchChannelAttributesWithRetries(
401
+ client,
402
+ currentChannel,
403
+ eventData => EventsQueue.enqueue(eventData),
404
+ );
405
+ channelAttributesTimerRef.current = 5;
406
+ // Clear any pending retry timeout since we succeeded
407
+ if (channelAttributesTimeoutRef.current) {
408
+ clearTimeout(channelAttributesTimeoutRef.current);
409
+ channelAttributesTimeoutRef.current = null;
410
+ }
411
+ } catch (error) {
412
+ channelAttributesTimeoutRef.current = setTimeout(async () => {
413
+ // Cap the timer to prevent excessive delays (max 30 seconds)
414
+ channelAttributesTimerRef.current = Math.min(
415
+ channelAttributesTimerRef.current * 2,
416
+ 30,
417
+ );
418
+ getChannelAttributes();
419
+ }, channelAttributesTimerRef.current * 1000);
420
+ }
421
+ };
422
+
423
+ const runQueuedEvents = async () => {
424
+ try {
425
+ while (!EventsQueue.isEmpty()) {
426
+ const currEvt = EventsQueue.dequeue();
427
+ await eventDispatcher(currEvt.data, `${currEvt.uid}`, currEvt.ts);
428
+ }
429
+ } catch (error) {
430
+ logger.error(
431
+ LogSource.Events,
432
+ 'CUSTOM_EVENTS',
433
+ 'error while running queue events',
434
+ {error},
435
+ );
436
+ }
437
+ };
438
+
439
+ const eventDispatcher = async (
440
+ data: {
441
+ evt: string;
442
+ value: string;
443
+ feat?: string;
444
+ etyp?: string;
445
+ },
446
+ sender: string,
447
+ ts: number,
448
+ ) => {
449
+ console.log(
450
+ LogSource.Events,
451
+ 'CUSTOM_EVENTS',
452
+ 'inside eventDispatcher ',
453
+ data,
454
+ );
455
+ console.log('supriya rtm [BREAKOUT] dispatcher: ', data);
456
+
457
+ let evt = '',
458
+ value = '';
459
+
460
+ if (data?.feat === 'BREAKOUT_ROOM') {
461
+ const outputData = {
462
+ evt: `${data.feat}_${data.etyp}`,
463
+ payload: JSON.stringify({
464
+ data: data.data,
465
+ action: data.act,
466
+ }),
467
+ persistLevel: 1,
468
+ source: 'core',
469
+ };
470
+ const formattedData = JSON.stringify(outputData);
471
+ evt = data.feat + '_' + data.etyp;
472
+ value = formattedData;
473
+ } else if (data?.feat === 'WAITING_ROOM') {
474
+ if (data?.etyp === 'REQUEST') {
475
+ const outputData = {
476
+ evt: `${data.feat}_${data.etyp}`,
477
+ payload: JSON.stringify({
478
+ attendee_uid: data.data.data.attendee_uid,
479
+ attendee_screenshare_uid: data.data.data.attendee_screenshare_uid,
480
+ }),
481
+ persistLevel: 1,
482
+ source: 'core',
483
+ };
484
+ const formattedData = JSON.stringify(outputData);
485
+ evt = data.feat + '_' + data.etyp;
486
+ value = formattedData;
487
+ }
488
+ if (data?.etyp === 'RESPONSE') {
489
+ const outputData = {
490
+ evt: `${data.feat}_${data.etyp}`,
491
+ payload: JSON.stringify({
492
+ approved: data.data.data.approved,
493
+ channelName: data.data.data.channel_name,
494
+ mainUser: data.data.data.mainUser,
495
+ screenShare: data.data.data.screenShare,
496
+ whiteboard: data.data.data.whiteboard,
497
+ chat: data.data.data?.chat,
498
+ }),
499
+ persistLevel: 1,
500
+ source: 'core',
501
+ };
502
+ const formattedData = JSON.stringify(outputData);
503
+ evt = data.feat + '_' + data.etyp;
504
+ value = formattedData;
505
+ }
506
+ } else {
507
+ if (
508
+ $config.ENABLE_WAITING_ROOM &&
509
+ !isHostRef.current?.isHost &&
510
+ waitingRoomStatusRef.current?.waitingRoomStatus !==
511
+ WaitingRoomStatus.APPROVED
512
+ ) {
513
+ if (
514
+ data.evt === controlMessageEnum.muteAudio ||
515
+ data.evt === controlMessageEnum.muteVideo
516
+ ) {
517
+ return;
518
+ } else {
519
+ evt = data.evt;
520
+ value = data.value;
521
+ }
522
+ } else {
523
+ evt = data.evt;
524
+ value = data.value;
525
+ }
526
+ }
527
+
528
+ try {
529
+ let parsedValue;
530
+ try {
531
+ parsedValue = typeof value === 'string' ? JSON.parse(value) : value;
532
+ } catch (error) {
533
+ logger.error(
534
+ LogSource.Events,
535
+ 'CUSTOM_EVENTS',
536
+ 'RTM Failed to parse event value in event dispatcher:',
537
+ {error},
538
+ );
539
+ return;
540
+ }
541
+ const {payload, persistLevel, source, _scope, _channelId} = parsedValue;
542
+
543
+ console.log('supriya rtm [BREAKOUT] event data', data);
544
+ console.log(
545
+ 'supriya rtm [BREAKOUT] _scope and _channelId: ',
546
+ _scope,
547
+ _channelId,
548
+ currentChannel,
549
+ );
550
+ // Filter if its for this channel
551
+ if (!isEventForActiveChannel(_scope, _channelId, currentChannel)) {
552
+ return;
553
+ }
554
+
555
+ // Step 1: Set local attributes
556
+ if (persistLevel === PersistanceLevel.Session) {
557
+ // const roomKey = RTM_ROOMS.BREAKOUT;
558
+ // const roomAwareKey = `${roomKey}_${evt}`;
559
+ const rtmAttribute = {key: evt, value: value};
560
+ const options: SetOrUpdateUserMetadataOptions = {
561
+ userId: `${localUid}`,
562
+ };
563
+ await client.storage.setUserMetadata(
564
+ {
565
+ items: [rtmAttribute],
566
+ },
567
+ options,
568
+ );
569
+ }
570
+ // Step 2: Emit the event
571
+ console.log(LogSource.Events, 'CUSTOM_EVENTS', 'emiting event..: ', evt);
572
+ EventUtils.emitEvent(evt, source, {payload, persistLevel, sender, ts});
573
+ // Because async gets evaluated in a different order when in an sdk
574
+ if (evt === 'name') {
575
+ // 1. Cancel existing timeout for this sender
576
+ if (eventTimeouts.has(sender)) {
577
+ clearTimeout(eventTimeouts.get(sender)!);
578
+ }
579
+ // 2. Create new timeout with tracking
580
+ const timeout = setTimeout(() => {
581
+ // 3. Guard against unmounted component
582
+ if (!isRTMMounted.current) {
583
+ return;
584
+ }
585
+ EventUtils.emitEvent(evt, source, {
586
+ payload,
587
+ persistLevel,
588
+ sender,
589
+ ts,
590
+ });
591
+ // 4. Clean up after execution
592
+ eventTimeouts.delete(sender);
593
+ }, 200);
594
+ // 5. Track the timeout for cleanup
595
+ eventTimeouts.set(sender, timeout);
596
+ }
597
+ } catch (error) {
598
+ console.error(
599
+ LogSource.Events,
600
+ 'CUSTOM_EVENTS',
601
+ 'error while emiting event:',
602
+ {error},
603
+ );
604
+ }
605
+ };
606
+
607
+ // Listeners
608
+ const handleStorageEvent = (storage: StorageEvent) => {
609
+ // when remote user sets/updates metadata - 3
610
+ if (
611
+ storage.eventType === nativeStorageEventTypeMapping.SET ||
612
+ storage.eventType === nativeStorageEventTypeMapping.UPDATE
613
+ ) {
614
+ const storageTypeStr = storage.storageType === 1 ? 'user' : 'channel';
615
+ const eventTypeStr = storage.eventType === 2 ? 'SET' : 'UPDATE';
616
+ logger.log(
617
+ LogSource.AgoraSDK,
618
+ 'Event',
619
+ `RTM storage event of type: [${eventTypeStr} ${storageTypeStr} metadata]`,
620
+ storage,
621
+ );
622
+ try {
623
+ if (storage.data?.items && Array.isArray(storage.data.items)) {
624
+ storage.data.items.forEach(item => {
625
+ try {
626
+ if (!item || !item.key) {
627
+ logger.warn(
628
+ LogSource.Events,
629
+ 'CUSTOM_EVENTS',
630
+ 'Invalid storage item:',
631
+ item,
632
+ );
633
+ return;
634
+ }
635
+
636
+ const {key, value, authorUserId, updateTs} = item;
637
+ console.log('supriya-eventDispatcher item: ', item);
638
+ const timestamp = getMessageTime(updateTs);
639
+ const sender = Platform.OS
640
+ ? get32BitUid(authorUserId)
641
+ : parseInt(authorUserId, 10);
642
+ eventDispatcher(
643
+ {
644
+ evt: key,
645
+ value,
646
+ },
647
+ `${sender}`,
648
+ timestamp,
649
+ );
650
+ } catch (error) {
651
+ logger.error(
652
+ LogSource.Events,
653
+ 'CUSTOM_EVENTS',
654
+ `Failed to process storage item: ${JSON.stringify(item)}`,
655
+ {error},
656
+ );
657
+ }
658
+ });
659
+ }
660
+ } catch (error) {
661
+ logger.error(
662
+ LogSource.Events,
663
+ 'CUSTOM_EVENTS',
664
+ 'error while dispatching through eventDispatcher',
665
+ {error},
666
+ );
667
+ }
668
+ }
669
+ };
670
+
671
+ const handlePresenceEvent = async (presence: PresenceEvent) => {
672
+ if (presence.type === nativePresenceEventTypeMapping.REMOTE_JOIN) {
673
+ logger.log(
674
+ LogSource.AgoraSDK,
675
+ 'Event',
676
+ 'RTM presenceEvent of type [3 - remoteJoin] (channelMemberJoined)',
677
+ );
678
+ const useAttributes = await fetchUserAttributesWithRetries(
679
+ client,
680
+ presence.publisher,
681
+ {
682
+ isMounted: () => isRTMMounted.current,
683
+ // This is called later if name arrives and hence we process that attribute
684
+ onNameFound: retriedAttributesWithName =>
685
+ mapUserAttributesToState(
686
+ retriedAttributesWithName,
687
+ presence.publisher,
688
+ syncUserState,
689
+ ),
690
+ },
691
+ );
692
+ // This is called as soon as we receive any attributes
693
+ mapUserAttributesToState(
694
+ useAttributes,
695
+ presence.publisher,
696
+ syncUserState,
697
+ );
698
+ }
699
+ // remoteLeaveChannel
700
+ if (presence.type === nativePresenceEventTypeMapping.REMOTE_LEAVE) {
701
+ logger.log(
702
+ LogSource.AgoraSDK,
703
+ 'Event',
704
+ 'RTM presenceEvent of type [4 - remoteLeave] (channelMemberLeft)',
705
+ presence,
706
+ );
707
+ // Chat of left user becomes undefined. So don't cleanup
708
+ const uid = presence?.publisher
709
+ ? parseInt(presence.publisher, 10)
710
+ : undefined;
711
+
712
+ if (!uid) {
713
+ return;
714
+ }
715
+ // updating the rtc data
716
+ syncUserState(uid, {
717
+ offline: true,
718
+ });
719
+ }
720
+ };
721
+
722
+ const handleMessageEvent = (message: MessageEvent) => {
723
+ console.log('supriya current message channel: ', currentChannel);
724
+ console.log('supriya message event is', message);
725
+ // message - 1 (channel)
726
+ if (message.channelType === nativeChannelTypeMapping.MESSAGE) {
727
+ // here the channel name will be the channel name
728
+ logger.debug(
729
+ LogSource.Events,
730
+ 'CUSTOM_EVENTS',
731
+ 'messageEvent of type [1 - CHANNEL] (channelMessageReceived)',
732
+ message,
733
+ );
734
+ const {publisher: uid, message: text, timestamp: ts} = message;
735
+ //whiteboard upload
736
+ if (parseInt(uid, 10) === 1010101) {
737
+ const [err, res] = safeJsonParse(text);
738
+ if (err) {
739
+ logger.error(
740
+ LogSource.Events,
741
+ 'CUSTOM_EVENTS',
742
+ 'JSON payload incorrect, Error while parsing the payload',
743
+ {error: err},
744
+ );
745
+ }
746
+ if (res?.data?.data?.images) {
747
+ LocalEventEmitter.emit(
748
+ LocalEventsEnum.WHITEBOARD_FILE_UPLOAD,
749
+ res?.data?.data?.images,
750
+ );
751
+ }
752
+ } else {
753
+ const [err, msg] = safeJsonParse(text);
754
+ if (err) {
755
+ logger.error(
756
+ LogSource.Events,
757
+ 'CUSTOM_EVENTS',
758
+ 'JSON payload incorrect, Error while parsing the payload',
759
+ {error: err},
760
+ );
761
+ }
762
+
763
+ const timestamp = getMessageTime(ts);
764
+ const sender = Platform.OS ? get32BitUid(uid) : parseInt(uid, 10);
765
+ try {
766
+ eventDispatcher(msg, `${sender}`, timestamp);
767
+ } catch (error) {
768
+ logger.error(
769
+ LogSource.Events,
770
+ 'CUSTOM_EVENTS',
771
+ 'error while dispatching through eventDispatcher',
772
+ {error},
773
+ );
774
+ }
775
+ }
776
+ }
777
+
778
+ // message - 3 (user)
779
+ if (message.channelType === nativeChannelTypeMapping.USER) {
780
+ logger.debug(
781
+ LogSource.Events,
782
+ 'CUSTOM_EVENTS',
783
+ 'messageEvent of type [3- USER] (messageReceived)',
784
+ message,
785
+ );
786
+ // here the (message.channelname) channel name will be the to UID
787
+ const {publisher: peerId, timestamp: ts, message: text} = message;
788
+ const [err, msg] = safeJsonParse(text);
789
+ if (err) {
790
+ logger.error(
791
+ LogSource.Events,
792
+ 'CUSTOM_EVENTS',
793
+ 'JSON payload incorrect, Error while parsing the payload',
794
+ {error: err},
795
+ );
796
+ }
797
+
798
+ const timestamp = getMessageTime(ts);
799
+
800
+ const sender = isAndroid() ? get32BitUid(peerId) : parseInt(peerId, 10);
801
+
802
+ try {
803
+ eventDispatcher(msg, `${sender}`, timestamp);
804
+ } catch (error) {
805
+ logger.error(
806
+ LogSource.Events,
807
+ 'CUSTOM_EVENTS',
808
+ 'error while dispatching through eventDispatcher',
809
+ {error},
810
+ );
811
+ }
812
+ }
813
+ };
814
+
815
+ const unsubscribeAndCleanup = async (
816
+ currentClient: RTMClient,
817
+ channel: string,
818
+ ) => {
819
+ try {
820
+ setHasUserJoinedRTM(false);
821
+ setIsInitialQueueCompleted(false);
822
+ currentClient.unsubscribe(channel);
823
+ RTMEngine.getInstance().removeChannel(RTM_ROOMS.BREAKOUT);
824
+ logger.log(LogSource.AgoraSDK, 'API', 'RTM destroy done');
825
+ if (isIOS() || isAndroid()) {
826
+ EventUtils.clear();
827
+ }
828
+ logger.debug(LogSource.AgoraSDK, 'Log', 'RTM cleanup done');
829
+ } catch (unsubscribeError) {
830
+ console.log('supriya error while unsubscribing: ', unsubscribeError);
831
+ }
832
+ };
833
+
834
+ useAsyncEffect(async () => {
835
+ try {
836
+ if (client && isLoggedIn && callActive && currentChannel) {
837
+ hasInitRef.current = true;
838
+ registerCallbacks(currentChannel, {
839
+ storage: handleStorageEvent,
840
+ presence: handlePresenceEvent,
841
+ message: handleMessageEvent,
842
+ });
843
+ await init();
844
+ }
845
+ } catch (error) {
846
+ logger.error(LogSource.AgoraSDK, 'Log', 'RTM init failed', {error});
847
+ }
848
+ return async () => {
849
+ console.log('rudra-core-client: cleaning up for channel', currentChannel);
850
+ const currentClient = RTMEngine.getInstance().engine;
851
+ hasInitRef.current = false;
852
+ isRTMMounted.current = false;
853
+ // Clear all pending timeouts on unmount
854
+ for (const timeout of eventTimeouts.values()) {
855
+ clearTimeout(timeout);
856
+ }
857
+ eventTimeouts.clear();
858
+ if (currentChannel) {
859
+ unregisterCallbacks(currentChannel);
860
+ }
861
+ if (currentClient && callActive && isLoggedIn) {
862
+ await unsubscribeAndCleanup(currentClient, currentChannel);
863
+ }
864
+ };
865
+ }, [isLoggedIn, callActive, currentChannel, client]);
866
+
867
+ const contextValue: RTMBreakoutRoomData = {
868
+ hasUserJoinedRTM,
869
+ isInitialQueueCompleted,
870
+ onlineUsersCount,
871
+ rtmInitTimstamp,
872
+ syncUserState,
873
+ };
874
+
875
+ return (
876
+ <RTMBreakoutRoomContext.Provider value={contextValue}>
877
+ {props.children}
878
+ </RTMBreakoutRoomContext.Provider>
879
+ );
880
+ };
881
+
882
+ export default RTMConfigureBreakoutRoomProvider;