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.
- package/package.json +2 -2
- package/template/agora-rn-uikit/src/Contexts/PropsContext.tsx +1 -3
- package/template/agora-rn-uikit/src/Contexts/RtcContext.tsx +1 -2
- package/template/agora-rn-uikit/src/Reducer/index.ts +0 -2
- package/template/agora-rn-uikit/src/Rtc/Join.tsx +11 -25
- package/template/agora-rn-uikit/src/RtcConfigure.tsx +1 -14
- package/template/bridge/rtc/webNg/RtcEngine.ts +2 -2
- package/template/bridge/rtm/web/index.ts +30 -0
- package/template/customization-api/typeDefinition.ts +1 -0
- package/template/defaultConfig.js +3 -2
- package/template/global.d.ts +1 -0
- package/template/src/AppRoutes.tsx +3 -3
- package/template/src/ai-agent/components/ControlButtons.tsx +1 -1
- package/template/src/assets/font-styles.css +36 -0
- package/template/src/assets/fonts/icomoon.ttf +0 -0
- package/template/src/assets/selection.json +1 -1
- package/template/src/atoms/CustomIcon.tsx +8 -0
- package/template/src/atoms/Dropdown.tsx +5 -0
- package/template/src/atoms/TertiaryButton.tsx +1 -1
- package/template/src/atoms/UserAvatar.tsx +1 -1
- package/template/src/components/ChatContext.ts +5 -3
- package/template/src/components/Controls.tsx +68 -22
- package/template/src/components/DeviceConfigure.tsx +1 -1
- package/template/src/components/EventsConfigure.tsx +22 -17
- package/template/src/components/Navbar.tsx +14 -11
- package/template/src/components/RTMConfigure.tsx +31 -1036
- package/template/src/components/UserGlobalPreferenceProvider.tsx +227 -0
- package/template/src/components/beauty-effect/useBeautyEffects.tsx +50 -13
- package/template/src/components/breakout-room/BreakoutRoomPanel.tsx +58 -0
- package/template/src/components/breakout-room/context/BreakoutRoomContext.tsx +2508 -0
- package/template/src/components/breakout-room/events/BreakoutRoomEventsConfigure.tsx +272 -0
- package/template/src/components/breakout-room/events/constants.ts +17 -0
- package/template/src/components/breakout-room/hoc/BreakoutRoomNameRenderer.tsx +68 -0
- package/template/src/components/breakout-room/hooks/useBreakoutRoomExit.ts +49 -0
- package/template/src/components/breakout-room/state/reducer.ts +522 -0
- package/template/src/components/breakout-room/state/types.ts +54 -0
- package/template/src/components/breakout-room/ui/BreakoutMeetingTitle.tsx +60 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomActionMenu.tsx +136 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomAnnouncementModal.tsx +135 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomGroupSettings.tsx +588 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomMainRoomUsers.tsx +142 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomMemberActionMenu.tsx +122 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomParticipants.tsx +124 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomRaiseHand.tsx +65 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomRenameModal.tsx +227 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomSettings.tsx +140 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomTransition.tsx +52 -0
- package/template/src/components/breakout-room/ui/BreakoutRoomView.tsx +193 -0
- package/template/src/components/breakout-room/ui/ExitBreakoutRoomIconButton.tsx +79 -0
- package/template/src/components/breakout-room/ui/ParticipantManualAssignmentModal.tsx +638 -0
- package/template/src/components/breakout-room/ui/SelectParticipantAssignmentStrategy.tsx +57 -0
- package/template/src/components/chat/chatConfigure.tsx +7 -1
- package/template/src/components/chat-messages/useChatMessages.tsx +43 -11
- package/template/src/components/common/Dividers.tsx +53 -0
- package/template/src/components/controls/toolbar-items/ExitBreakoutRoomToolbarItem.tsx +13 -0
- package/template/src/components/controls/useControlPermissionMatrix.tsx +32 -4
- package/template/src/components/participants/AllHostParticipants.tsx +10 -2
- package/template/src/components/participants/Participant.tsx +7 -1
- package/template/src/components/participants/UserActionMenuOptions.tsx +12 -2
- package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +12 -8
- package/template/src/components/precall/joinWaitingRoomBtn.tsx +14 -10
- package/template/src/components/raise-hand/RaiseHandButton.tsx +50 -0
- package/template/src/components/raise-hand/RaiseHandProvider.tsx +308 -0
- package/template/src/components/raise-hand/index.ts +14 -0
- package/template/src/components/recordings/RecordingsDateTable.tsx +3 -2
- package/template/src/components/room-info/useCurrentRoomInfo.tsx +42 -0
- package/template/src/components/room-info/useSetBreakoutRoomInfo.tsx +64 -0
- package/template/src/components/useUserPreference.tsx +39 -12
- package/template/src/components/virtual-background/useVB.tsx +18 -0
- package/template/src/components/whiteboard/WhiteboardConfigure.tsx +27 -0
- package/template/src/language/default-labels/videoCallScreenLabels.ts +7 -0
- package/template/src/logger/AppBuilderLogger.tsx +11 -3
- package/template/src/pages/VideoCall.tsx +171 -518
- package/template/src/pages/video-call/BreakoutVideoCall.tsx +213 -0
- package/template/src/pages/video-call/SidePanelHeader.tsx +17 -0
- package/template/src/pages/video-call/VideoCallContent.tsx +211 -0
- package/template/src/pages/video-call/VideoCallScreen.tsx +18 -0
- package/template/src/pages/video-call/VideoCallScreenWrapper.tsx +0 -1
- package/template/src/pages/video-call/VideoCallStateWrapper.tsx +495 -0
- package/template/src/rtm/RTMConfigureBreakoutRoomProvider.tsx +882 -0
- package/template/src/rtm/RTMConfigureMainRoomProvider.tsx +757 -0
- package/template/src/rtm/RTMCoreProvider.tsx +419 -0
- package/template/src/rtm/RTMEngine.ts +188 -60
- package/template/src/rtm/RTMGlobalStateProvider.tsx +706 -0
- package/template/src/rtm/RTMStatusBanner.tsx +99 -0
- package/template/src/rtm/constants.ts +12 -0
- package/template/src/rtm/hooks/useMainRoomUserDisplayName.ts +45 -0
- package/template/src/rtm/rtm-presence-utils.ts +344 -0
- package/template/src/rtm/utils.ts +68 -1
- package/template/src/rtm-events/constants.ts +40 -1
- package/template/src/rtm-events-api/Events.ts +62 -19
- package/template/src/subComponents/ChatBubble.tsx +3 -3
- package/template/src/subComponents/ChatContainer.tsx +19 -9
- package/template/src/subComponents/LocalAudioMute.tsx +2 -2
- package/template/src/subComponents/LocalVideoMute.tsx +2 -2
- package/template/src/subComponents/SidePanelEnum.tsx +1 -0
- package/template/src/subComponents/caption/useCaption.tsx +1 -1
- package/template/src/subComponents/chat/ChatAnnouncementView.tsx +65 -0
- package/template/src/subComponents/chat/ChatSendButton.tsx +1 -0
- package/template/src/subComponents/screenshare/ScreenshareButton.tsx +16 -0
- package/template/src/subComponents/screenshare/ScreenshareConfigure.native.tsx +1 -1
- package/template/src/subComponents/waiting-rooms/WaitingRoomControls.tsx +7 -4
- package/template/src/utils/useDebouncedCallback.tsx +20 -0
- package/template/src/utils/useEndCall.ts +0 -2
- package/template/src/utils/useMuteToggleLocal.ts +14 -10
- package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +0 -11
- 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;
|