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
@@ -0,0 +1,757 @@
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 {type MessageEvent, type StorageEvent} from 'agora-react-native-rtm';
22
+ import {
23
+ ContentInterface,
24
+ DispatchContext,
25
+ useLocalUid,
26
+ } from '../../agora-rn-uikit';
27
+ import {Platform} from 'react-native';
28
+ import {isAndroid, isIOS} from '../utils/common';
29
+ import {useContent} from 'customization-api';
30
+ import {
31
+ safeJsonParse,
32
+ getMessageTime,
33
+ get32BitUid,
34
+ isEventForActiveChannel,
35
+ } from './utils';
36
+ import {EventUtils, EventsQueue} from '../rtm-events';
37
+ import {PersistanceLevel} from '../rtm-events-api';
38
+ import RTMEngine from './RTMEngine';
39
+ import {filterObject} from '../utils';
40
+ import {useAsyncEffect} from '../utils/useAsyncEffect';
41
+ import {
42
+ WaitingRoomStatus,
43
+ useRoomInfo,
44
+ } from '../components/room-info/useRoomInfo';
45
+ import LocalEventEmitter, {
46
+ LocalEventsEnum,
47
+ } from '../rtm-events-api/LocalEvents';
48
+ import {controlMessageEnum} from '../components/ChatContext';
49
+ import {LogSource, logger} from '../logger/AppBuilderLogger';
50
+ import {
51
+ nativeChannelTypeMapping,
52
+ nativeStorageEventTypeMapping,
53
+ } from '../../bridge/rtm/web/Types';
54
+ import {useRTMCore} from './RTMCoreProvider';
55
+ import {RTMUserData, useRTMGlobalState} from './RTMGlobalStateProvider';
56
+ import {useUserGlobalPreferences} from '../components/UserGlobalPreferenceProvider';
57
+ import {ToggleState} from '../../agora-rn-uikit';
58
+ import useMuteToggleLocal from '../utils/useMuteToggleLocal';
59
+ import {
60
+ RTM_ROOMS,
61
+ RTM_EVENTS_ATTRIBUTES_TO_RESET_WHEN_ROOM_CHANGES,
62
+ } from './constants';
63
+ import {useRtc} from 'customization-api';
64
+ import {
65
+ fetchChannelAttributesWithRetries,
66
+ clearRoomScopedUserAttributes,
67
+ } from './rtm-presence-utils';
68
+
69
+ const eventTimeouts = new Map<string, ReturnType<typeof setTimeout>>();
70
+
71
+ // RTM Main Room Context
72
+ export interface RTMMainRoomData {
73
+ hasUserJoinedRTM: boolean;
74
+ isInitialQueueCompleted: boolean;
75
+ onlineUsersCount: number;
76
+ rtmInitTimstamp: number;
77
+ syncUserState: (uid: number, data: Partial<ContentInterface>) => void;
78
+ }
79
+
80
+ const RTMMainRoomContext = createContext<RTMMainRoomData>({
81
+ hasUserJoinedRTM: false,
82
+ isInitialQueueCompleted: false,
83
+ onlineUsersCount: 0,
84
+ rtmInitTimstamp: 0,
85
+ syncUserState: () => {},
86
+ });
87
+
88
+ export const useRTMConfigureMain = () => {
89
+ const context = useContext(RTMMainRoomContext);
90
+ if (!context) {
91
+ throw new Error(
92
+ 'useRTMConfigureMain must be used within RTMConfigureMainRoomProvider',
93
+ );
94
+ }
95
+ return context;
96
+ };
97
+
98
+ interface RTMConfigureMainRoomProviderProps {
99
+ callActive: boolean;
100
+ currentChannel: string;
101
+ children: React.ReactNode;
102
+ }
103
+
104
+ const RTMConfigureMainRoomProvider: React.FC<
105
+ RTMConfigureMainRoomProviderProps
106
+ > = ({callActive, currentChannel, children}) => {
107
+ const rtmInitTimstamp = new Date().getTime();
108
+ const {dispatch} = useContext(DispatchContext);
109
+ const {defaultContent, activeUids} = useContent();
110
+ const {
111
+ waitingRoomStatus,
112
+ data: {isHost},
113
+ } = useRoomInfo();
114
+ const localUid = useLocalUid();
115
+ const {applyUserPreferences, syncUserPreferences} =
116
+ useUserGlobalPreferences();
117
+ const toggleMute = useMuteToggleLocal();
118
+ const {rtcTracksReady} = useRtc();
119
+ const [hasUserJoinedRTM, setHasUserJoinedRTM] = useState<boolean>(false);
120
+ const [isInitialQueueCompleted, setIsInitialQueueCompleted] = useState(false);
121
+ const [onlineUsersCount, setTotalOnlineUsers] = useState<number>(0);
122
+
123
+ // RTM
124
+ const {client, isLoggedIn} = useRTMCore();
125
+ const {mainRoomRTMUsers, setMainRoomRTMUsers} = useRTMGlobalState();
126
+
127
+ // Main channel message registration (RTMConfigureMainRoom is always for main channel)
128
+ const {
129
+ registerMainChannelMessageHandler,
130
+ unregisterMainChannelMessageHandler,
131
+ registerMainChannelStorageHandler,
132
+ unregisterMainChannelStorageHandler,
133
+ } = useRTMGlobalState();
134
+
135
+ // refs
136
+ const isRTMMounted = useRef(true);
137
+ const channelAttributesTimerRef: any = useRef(5);
138
+ const channelAttributesTimeoutRef = useRef<ReturnType<
139
+ typeof setTimeout
140
+ > | null>(null);
141
+
142
+ const isHostRef = useRef({isHost: isHost});
143
+ useEffect(() => {
144
+ isHostRef.current.isHost = isHost;
145
+ }, [isHost]);
146
+
147
+ const waitingRoomStatusRef = useRef({waitingRoomStatus: waitingRoomStatus});
148
+ useEffect(() => {
149
+ waitingRoomStatusRef.current.waitingRoomStatus = waitingRoomStatus;
150
+ }, [waitingRoomStatus]);
151
+
152
+ const activeUidsRef = useRef({activeUids: activeUids});
153
+ useEffect(() => {
154
+ activeUidsRef.current.activeUids = activeUids;
155
+ }, [activeUids]);
156
+
157
+ const defaultContentRef = useRef(defaultContent);
158
+
159
+ useEffect(() => {
160
+ defaultContentRef.current = defaultContent;
161
+ }, [defaultContent]);
162
+
163
+ // Set online users
164
+ React.useEffect(() => {
165
+ setTotalOnlineUsers(
166
+ Object.keys(
167
+ filterObject(
168
+ defaultContent,
169
+ ([k, v]) =>
170
+ v?.type === 'rtc' &&
171
+ !v.offline &&
172
+ activeUidsRef.current.activeUids.indexOf(v?.uid) !== -1,
173
+ ),
174
+ ).length,
175
+ );
176
+ }, [defaultContent, activeUids]);
177
+
178
+ // Set user preferences when main room mounts
179
+ useEffect(() => {
180
+ if (rtcTracksReady && localUid) {
181
+ console.log(
182
+ 'UP: trackesready',
183
+ JSON.stringify(defaultContentRef.current[localUid]),
184
+ );
185
+ applyUserPreferences(defaultContentRef.current[localUid], toggleMute);
186
+ }
187
+ }, [rtcTracksReady, localUid]);
188
+
189
+ // Sync current audio/video state audio video changes
190
+ useEffect(() => {
191
+ const userData = defaultContent[localUid];
192
+ if (rtcTracksReady && userData) {
193
+ console.log('UP: syncing userData: ', userData);
194
+ const preferences = {
195
+ audioMuted: userData.audio === ToggleState.disabled,
196
+ videoMuted: userData.video === ToggleState.disabled,
197
+ };
198
+ console.log('UP: saved preferences: ', preferences);
199
+ syncUserPreferences(preferences);
200
+ }
201
+ }, [defaultContent, localUid, syncUserPreferences, rtcTracksReady]);
202
+
203
+ // Set Main room specific syncUserState function
204
+ const syncUserState = useCallback(
205
+ (uid: number, data: any) => {
206
+ // Extract only RTM-related fields that are actually passed
207
+ const rtmData: Partial<RTMUserData> = {};
208
+
209
+ // Only add fields if they exist in the passed data
210
+ if ('name' in data) {
211
+ rtmData.name = data.name;
212
+ }
213
+ if ('screenUid' in data) {
214
+ rtmData.screenUid = data.screenUid;
215
+ }
216
+ if ('offline' in data) {
217
+ rtmData.offline = data.offline;
218
+ }
219
+ if ('lastMessageTimeStamp' in data) {
220
+ rtmData.lastMessageTimeStamp = data.lastMessageTimeStamp;
221
+ }
222
+ if ('isInWaitingRoom' in data) {
223
+ rtmData.isInWaitingRoom = data.isInWaitingRoom;
224
+ }
225
+ if ('isHost' in data) {
226
+ rtmData.isHost = data.isHost;
227
+ }
228
+ if ('type' in data) {
229
+ rtmData.type = data.type;
230
+ }
231
+ if ('parentUid' in data) {
232
+ rtmData.parentUid = data.parentUid;
233
+ }
234
+ if ('uid' in data) {
235
+ rtmData.uid = data.uid;
236
+ }
237
+ // Only update if we have RTM data to update
238
+ if (Object.keys(rtmData).length > 0) {
239
+ setMainRoomRTMUsers(prev => {
240
+ return {
241
+ ...prev,
242
+ [uid]: {
243
+ ...prev[uid],
244
+ ...rtmData,
245
+ },
246
+ };
247
+ });
248
+ }
249
+ },
250
+ [setMainRoomRTMUsers],
251
+ );
252
+
253
+ useEffect(() => {
254
+ Object.entries(mainRoomRTMUsers).forEach(([uidStr, rtmUser]) => {
255
+ const uid = parseInt(uidStr, 10);
256
+
257
+ let userData: Partial<RTMUserData> = {};
258
+ // screenshare RTM data
259
+ if (rtmUser.type === 'screenshare') {
260
+ userData = {
261
+ // RTM data
262
+ name: rtmUser.name || '',
263
+ parentUid: rtmUser.parentUid || 0,
264
+ type: rtmUser.type,
265
+ };
266
+ } else {
267
+ userData = {
268
+ // user RTM data
269
+ name: rtmUser.name || '',
270
+ screenUid: rtmUser.screenUid || 0,
271
+ offline: !!rtmUser.offline,
272
+ lastMessageTimeStamp: rtmUser.lastMessageTimeStamp || 0,
273
+ isInWaitingRoom: rtmUser?.isInWaitingRoom || false,
274
+ isHost: rtmUser.isHost,
275
+ type: rtmUser.type,
276
+ };
277
+ }
278
+
279
+ // Dispatch directly for each user
280
+ dispatch({type: 'UpdateRenderList', value: [uid, userData]});
281
+ });
282
+ }, [mainRoomRTMUsers, dispatch]);
283
+
284
+ const rehydrateSessionAttributes = async () => {
285
+ try {
286
+ const uid = localUid.toString();
287
+ const attr = await client.storage.getUserMetadata({userId: uid});
288
+ console.log('supriya-wasInBreakoutRoom: attr: ', attr);
289
+
290
+ if (!attr?.items) {
291
+ return;
292
+ }
293
+
294
+ attr.items.forEach(item => {
295
+ try {
296
+ // Check if this is a room-aware session attribute for current room
297
+ // if (item.key && item.key.startsWith(`${RTM_ROOMS.MAIN}__`)) {
298
+ const parsed = JSON.parse(item.value);
299
+ if (parsed.persistLevel === PersistanceLevel.Session) {
300
+ // Put into queuue
301
+ const data = {evt: item.key, value: item.value};
302
+ EventsQueue.enqueue({
303
+ data,
304
+ uid,
305
+ ts: Date.now(),
306
+ });
307
+ }
308
+ // }
309
+ } catch (e) {
310
+ console.log('Failed to rehydrate session attribute', item.key, e);
311
+ }
312
+ });
313
+ } catch (error) {
314
+ console.log('Failed to rehydrate session attributes', error);
315
+ }
316
+ };
317
+
318
+ const init = async () => {
319
+ // Set main room as active channel when this provider mounts again active
320
+ const currentActiveChannel = RTMEngine.getInstance().getActiveChannelName();
321
+ const wasInBreakoutRoom = currentActiveChannel === RTM_ROOMS.BREAKOUT;
322
+
323
+ if (currentActiveChannel !== RTM_ROOMS.MAIN) {
324
+ RTMEngine.getInstance().setActiveChannelName(RTM_ROOMS.MAIN);
325
+ }
326
+ // Clear room-scoped RTM attributes to ensure fresh state
327
+ await clearRoomScopedUserAttributes(
328
+ client,
329
+ RTM_EVENTS_ATTRIBUTES_TO_RESET_WHEN_ROOM_CHANGES,
330
+ );
331
+
332
+ // // Rehydrate session attributes ONLY when returning from breakout room
333
+ if (wasInBreakoutRoom) {
334
+ await rehydrateSessionAttributes();
335
+ }
336
+
337
+ await getChannelAttributes();
338
+
339
+ setHasUserJoinedRTM(true);
340
+ await runQueuedEvents();
341
+ setIsInitialQueueCompleted(true);
342
+ };
343
+
344
+ const getChannelAttributes = async () => {
345
+ try {
346
+ await fetchChannelAttributesWithRetries(
347
+ client,
348
+ currentChannel,
349
+ eventData => EventsQueue.enqueue(eventData),
350
+ );
351
+ channelAttributesTimerRef.current = 5;
352
+ // Clear any pending retry timeout since we succeeded
353
+ if (channelAttributesTimeoutRef.current) {
354
+ clearTimeout(channelAttributesTimeoutRef.current);
355
+ channelAttributesTimeoutRef.current = null;
356
+ }
357
+ } catch (error) {
358
+ console.log(
359
+ 'rudra-core-client: RTM getchannelattributes failed..Trying again',
360
+ error,
361
+ );
362
+ channelAttributesTimeoutRef.current = setTimeout(async () => {
363
+ // Cap the timer to prevent excessive delays (max 30 seconds)
364
+ channelAttributesTimerRef.current = Math.min(
365
+ channelAttributesTimerRef.current * 2,
366
+ 30,
367
+ );
368
+ getChannelAttributes();
369
+ }, channelAttributesTimerRef.current * 1000);
370
+ }
371
+ };
372
+
373
+ const runQueuedEvents = async () => {
374
+ try {
375
+ while (!EventsQueue.isEmpty()) {
376
+ const currEvt = EventsQueue.dequeue();
377
+ console.log('supriya-session inside queue currEvt: ', currEvt);
378
+ await eventDispatcher(currEvt.data, `${currEvt.uid}`, currEvt.ts);
379
+ }
380
+ } catch (error) {
381
+ logger.error(
382
+ LogSource.Events,
383
+ 'CUSTOM_EVENTS',
384
+ 'error while running queue events',
385
+ {error},
386
+ );
387
+ }
388
+ };
389
+
390
+ const eventDispatcher = async (
391
+ data: {
392
+ evt: string;
393
+ value: string;
394
+ feat?: string;
395
+ etyp?: string;
396
+ },
397
+ sender: string,
398
+ ts: number,
399
+ ) => {
400
+ console.log(
401
+ LogSource.Events,
402
+ 'CUSTOM_EVENTS',
403
+ 'inside eventDispatcher ',
404
+ data,
405
+ );
406
+
407
+ let evt = '',
408
+ value = '';
409
+
410
+ if (data?.feat === 'BREAKOUT_ROOM') {
411
+ const outputData = {
412
+ evt: `${data.feat}_${data.etyp}`,
413
+ payload: JSON.stringify({
414
+ data: data.data,
415
+ action: data.act,
416
+ }),
417
+ persistLevel: 1,
418
+ source: 'core',
419
+ };
420
+ const formattedData = JSON.stringify(outputData);
421
+ evt = data.feat + '_' + data.etyp;
422
+ value = formattedData;
423
+ } else if (data?.feat === 'WAITING_ROOM') {
424
+ if (data?.etyp === 'REQUEST') {
425
+ const outputData = {
426
+ evt: `${data.feat}_${data.etyp}`,
427
+ payload: JSON.stringify({
428
+ attendee_uid: data.data.data.attendee_uid,
429
+ attendee_screenshare_uid: data.data.data.attendee_screenshare_uid,
430
+ }),
431
+ persistLevel: 1,
432
+ source: 'core',
433
+ };
434
+ const formattedData = JSON.stringify(outputData);
435
+ evt = data.feat + '_' + data.etyp;
436
+ value = formattedData;
437
+ }
438
+ if (data?.etyp === 'RESPONSE') {
439
+ const outputData = {
440
+ evt: `${data.feat}_${data.etyp}`,
441
+ payload: JSON.stringify({
442
+ approved: data.data.data.approved,
443
+ channelName: data.data.data.channel_name,
444
+ mainUser: data.data.data.mainUser,
445
+ screenShare: data.data.data.screenShare,
446
+ whiteboard: data.data.data.whiteboard,
447
+ chat: data.data.data?.chat,
448
+ }),
449
+ persistLevel: 1,
450
+ source: 'core',
451
+ };
452
+ const formattedData = JSON.stringify(outputData);
453
+ evt = data.feat + '_' + data.etyp;
454
+ value = formattedData;
455
+ }
456
+ } else {
457
+ if (
458
+ $config.ENABLE_WAITING_ROOM &&
459
+ !isHostRef.current?.isHost &&
460
+ waitingRoomStatusRef.current?.waitingRoomStatus !==
461
+ WaitingRoomStatus.APPROVED
462
+ ) {
463
+ if (
464
+ data.evt === controlMessageEnum.muteAudio ||
465
+ data.evt === controlMessageEnum.muteVideo
466
+ ) {
467
+ return;
468
+ } else {
469
+ evt = data.evt;
470
+ value = data.value;
471
+ }
472
+ } else {
473
+ evt = data.evt;
474
+ value = data.value;
475
+ }
476
+ }
477
+
478
+ try {
479
+ let parsedValue;
480
+ try {
481
+ parsedValue = typeof value === 'string' ? JSON.parse(value) : value;
482
+ } catch (error) {
483
+ logger.error(
484
+ LogSource.Events,
485
+ 'CUSTOM_EVENTS',
486
+ 'RTM Failed to parse event value in event dispatcher:',
487
+ {error},
488
+ );
489
+ return;
490
+ }
491
+ const {payload, persistLevel, source, _scope, _channelId} = parsedValue;
492
+ console.log(
493
+ 'supriya-session-attributes [MAIN] _scope and _channelId: ',
494
+ source,
495
+ _scope,
496
+ _channelId,
497
+ currentChannel,
498
+ payload,
499
+ );
500
+ // Filter if its for this channel
501
+ if (!isEventForActiveChannel(_scope, _channelId, currentChannel)) {
502
+ console.log('supriya-session-attributes SKIPPING', payload);
503
+ return;
504
+ }
505
+
506
+ // Step 2: Emit the event (no metadata persistence - handled by RTMGlobalStateProvider)
507
+ console.log(LogSource.Events, 'CUSTOM_EVENTS', 'emiting event..: ', evt);
508
+ EventUtils.emitEvent(evt, source, {payload, persistLevel, sender, ts});
509
+ // Because async gets evaluated in a different order when in an sdk
510
+ if (evt === 'name') {
511
+ // 1. Cancel existing timeout for this sender
512
+ if (eventTimeouts.has(sender)) {
513
+ clearTimeout(eventTimeouts.get(sender)!);
514
+ }
515
+ // 2. Create new timeout with tracking
516
+ const timeout = setTimeout(() => {
517
+ // 3. Guard against unmounted component
518
+ if (!isRTMMounted.current) {
519
+ return;
520
+ }
521
+ EventUtils.emitEvent(evt, source, {
522
+ payload,
523
+ persistLevel,
524
+ sender,
525
+ ts,
526
+ });
527
+ // 4. Clean up after execution
528
+ eventTimeouts.delete(sender);
529
+ }, 200);
530
+ // 5. Track the timeout for cleanup
531
+ eventTimeouts.set(sender, timeout);
532
+ }
533
+ } catch (error) {
534
+ console.error(
535
+ LogSource.Events,
536
+ 'CUSTOM_EVENTS',
537
+ 'error while emiting event:',
538
+ {error},
539
+ );
540
+ }
541
+ };
542
+
543
+ // Listeners
544
+ const handleMainChannelStorageEvent = (storage: StorageEvent) => {
545
+ // when remote user sets/updates metadata - 3
546
+ if (
547
+ storage.eventType === nativeStorageEventTypeMapping.SET ||
548
+ storage.eventType === nativeStorageEventTypeMapping.UPDATE
549
+ ) {
550
+ const storageTypeStr = storage.storageType === 1 ? 'user' : 'channel';
551
+ const eventTypeStr = storage.eventType === 2 ? 'SET' : 'UPDATE';
552
+ logger.log(
553
+ LogSource.AgoraSDK,
554
+ 'Event',
555
+ `RTM storage event of type: [${eventTypeStr} ${storageTypeStr} metadata]`,
556
+ storage,
557
+ );
558
+ try {
559
+ if (storage.data?.items && Array.isArray(storage.data.items)) {
560
+ storage.data.items.forEach(item => {
561
+ try {
562
+ if (!item || !item.key) {
563
+ logger.warn(
564
+ LogSource.Events,
565
+ 'CUSTOM_EVENTS',
566
+ 'Invalid storage item:',
567
+ item,
568
+ );
569
+ return;
570
+ }
571
+
572
+ const {key, value, authorUserId, updateTs} = item;
573
+ const timestamp = getMessageTime(updateTs);
574
+ const sender = Platform.OS
575
+ ? get32BitUid(authorUserId)
576
+ : parseInt(authorUserId, 10);
577
+ eventDispatcher(
578
+ {
579
+ evt: key,
580
+ value,
581
+ },
582
+ `${sender}`,
583
+ timestamp,
584
+ );
585
+ } catch (error) {
586
+ logger.error(
587
+ LogSource.Events,
588
+ 'CUSTOM_EVENTS',
589
+ `Failed to process storage item: ${JSON.stringify(item)}`,
590
+ {error},
591
+ );
592
+ }
593
+ });
594
+ }
595
+ } catch (error) {
596
+ logger.error(
597
+ LogSource.Events,
598
+ 'CUSTOM_EVENTS',
599
+ 'error while dispatching through eventDispatcher',
600
+ {error},
601
+ );
602
+ }
603
+ }
604
+ };
605
+
606
+ const handleMainChannelMessageEvent = (message: MessageEvent) => {
607
+ console.log('supriya current message channel: ', currentChannel);
608
+ console.log('supriya message event is', message);
609
+ // message - 1 (channel)
610
+ if (message.channelType === nativeChannelTypeMapping.MESSAGE) {
611
+ // here the channel name will be the channel name
612
+ logger.debug(
613
+ LogSource.Events,
614
+ 'CUSTOM_EVENTS',
615
+ 'messageEvent of type [1 - CHANNEL] (channelMessageReceived)',
616
+ message,
617
+ );
618
+ const {publisher: uid, message: text, timestamp: ts} = message;
619
+ //whiteboard upload
620
+ if (parseInt(uid, 10) === 1010101) {
621
+ const [err, res] = safeJsonParse(text);
622
+ if (err) {
623
+ logger.error(
624
+ LogSource.Events,
625
+ 'CUSTOM_EVENTS',
626
+ 'JSON payload incorrect, Error while parsing the payload',
627
+ {error: err},
628
+ );
629
+ }
630
+ if (res?.data?.data?.images) {
631
+ LocalEventEmitter.emit(
632
+ LocalEventsEnum.WHITEBOARD_FILE_UPLOAD,
633
+ res?.data?.data?.images,
634
+ );
635
+ }
636
+ } else {
637
+ const [err, msg] = safeJsonParse(text);
638
+ if (err) {
639
+ logger.error(
640
+ LogSource.Events,
641
+ 'CUSTOM_EVENTS',
642
+ 'JSON payload incorrect, Error while parsing the payload',
643
+ {error: err},
644
+ );
645
+ }
646
+
647
+ const timestamp = getMessageTime(ts);
648
+ const sender = Platform.OS ? get32BitUid(uid) : parseInt(uid, 10);
649
+ try {
650
+ eventDispatcher(msg, `${sender}`, timestamp);
651
+ } catch (error) {
652
+ logger.error(
653
+ LogSource.Events,
654
+ 'CUSTOM_EVENTS',
655
+ 'error while dispatching through eventDispatcher',
656
+ {error},
657
+ );
658
+ }
659
+ }
660
+ }
661
+
662
+ // message - 3 (user)
663
+ if (message.channelType === nativeChannelTypeMapping.USER) {
664
+ logger.debug(
665
+ LogSource.Events,
666
+ 'CUSTOM_EVENTS',
667
+ 'messageEvent of type [3- USER] (messageReceived)',
668
+ message,
669
+ );
670
+ // here the (message.channelname) channel name will be the to UID
671
+ const {publisher: peerId, timestamp: ts, message: text} = message;
672
+ const [err, msg] = safeJsonParse(text);
673
+ if (err) {
674
+ logger.error(
675
+ LogSource.Events,
676
+ 'CUSTOM_EVENTS',
677
+ 'JSON payload incorrect, Error while parsing the payload',
678
+ {error: err},
679
+ );
680
+ }
681
+
682
+ const timestamp = getMessageTime(ts);
683
+
684
+ const sender = isAndroid() ? get32BitUid(peerId) : parseInt(peerId, 10);
685
+
686
+ try {
687
+ eventDispatcher(msg, `${sender}`, timestamp);
688
+ } catch (error) {
689
+ logger.error(
690
+ LogSource.Events,
691
+ 'CUSTOM_EVENTS',
692
+ 'error while dispatching through eventDispatcher',
693
+ {error},
694
+ );
695
+ }
696
+ }
697
+ };
698
+
699
+ useAsyncEffect(async () => {
700
+ try {
701
+ if (client && isLoggedIn && currentChannel) {
702
+ // RTM initialization logic:
703
+ // - Waiting room attendees: Connect immediately
704
+ // - Waiting room hosts: Wait for callActive
705
+ // - Non-waiting room: Everyone waits for callActive
706
+ const shouldInit =
707
+ callActive || ($config.ENABLE_WAITING_ROOM && !isHost);
708
+ if (shouldInit) {
709
+ registerMainChannelMessageHandler(handleMainChannelMessageEvent);
710
+ registerMainChannelStorageHandler(handleMainChannelStorageEvent);
711
+ await init();
712
+ }
713
+ }
714
+ } catch (error) {
715
+ logger.error(LogSource.AgoraSDK, 'Log', 'RTM init failed', {error});
716
+ }
717
+ return async () => {
718
+ isRTMMounted.current = false;
719
+ logger.log(LogSource.AgoraSDK, 'API', 'RTM destroy done');
720
+ unregisterMainChannelMessageHandler();
721
+ unregisterMainChannelStorageHandler();
722
+ if (isIOS() || isAndroid()) {
723
+ EventUtils.clear();
724
+ }
725
+ // Clear all pending timeouts on unmount
726
+ for (const timeout of eventTimeouts.values()) {
727
+ clearTimeout(timeout);
728
+ }
729
+ eventTimeouts.clear();
730
+ // Clear timer-based retry timeouts
731
+ if (channelAttributesTimeoutRef.current) {
732
+ clearTimeout(channelAttributesTimeoutRef.current);
733
+ channelAttributesTimeoutRef.current = null;
734
+ }
735
+ setHasUserJoinedRTM(false);
736
+ setIsInitialQueueCompleted(false);
737
+ logger.debug(LogSource.AgoraSDK, 'Log', 'RTM cleanup done');
738
+ };
739
+ }, [client, isLoggedIn, callActive, currentChannel, isHost]);
740
+
741
+ // Provide context data to children
742
+ const contextValue: RTMMainRoomData = {
743
+ hasUserJoinedRTM,
744
+ isInitialQueueCompleted,
745
+ onlineUsersCount,
746
+ rtmInitTimstamp,
747
+ syncUserState,
748
+ };
749
+
750
+ return (
751
+ <RTMMainRoomContext.Provider value={contextValue}>
752
+ {children}
753
+ </RTMMainRoomContext.Provider>
754
+ );
755
+ };
756
+
757
+ export default RTMConfigureMainRoomProvider;