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,419 @@
1
+ import React, {
2
+ useRef,
3
+ useState,
4
+ useEffect,
5
+ createContext,
6
+ useContext,
7
+ useCallback,
8
+ useMemo,
9
+ } from 'react';
10
+ import type {
11
+ RTMClient,
12
+ LinkStateEvent,
13
+ MessageEvent,
14
+ PresenceEvent,
15
+ StorageEvent,
16
+ Metadata,
17
+ SetOrUpdateUserMetadataOptions,
18
+ RtmLinkState,
19
+ } from 'agora-react-native-rtm';
20
+ import {UidType} from '../../agora-rn-uikit';
21
+ import RTMEngine from '../rtm/RTMEngine';
22
+ import {isWeb, isWebInternal} from '../utils/common';
23
+ import isSDK from '../utils/isSDK';
24
+ import {useAsyncEffect} from '../utils/useAsyncEffect';
25
+ import {nativeLinkStateMapping} from '../../bridge/rtm/web/Types';
26
+ import {RTMStatusBanner} from './RTMStatusBanner';
27
+
28
+ // ---- Helpers ---- //
29
+ const delay = (ms: number) => new Promise(r => setTimeout(r, ms));
30
+
31
+ async function loginWithBackoff(
32
+ rtmClient: RTMClient,
33
+ token: string,
34
+ onAttempt?: (n: number) => void,
35
+ maxAttempts = 5,
36
+ ) {
37
+ let attempt = 0;
38
+ while (attempt <= maxAttempts) {
39
+ try {
40
+ try {
41
+ await rtmClient.logout();
42
+ } catch {}
43
+ await delay(300);
44
+ await rtmClient.login({token});
45
+ return; // success
46
+ } catch (e: any) {
47
+ attempt += 1;
48
+ onAttempt?.(attempt);
49
+
50
+ if (attempt > maxAttempts) {
51
+ const errorMsg = `RTM login failed: ${e?.message ?? e}`;
52
+ throw new Error(errorMsg);
53
+ }
54
+ const backoff =
55
+ Math.min(5000 * 2 ** (attempt - 1), 60_000) +
56
+ Math.floor(Math.random() * 300);
57
+ await delay(backoff);
58
+ }
59
+ }
60
+ }
61
+
62
+ // ---- Context ---- //
63
+ type MessageCallback = (message: MessageEvent) => void;
64
+ type PresenceCallback = (presence: PresenceEvent) => void;
65
+ type StorageCallback = (storage: StorageEvent) => void;
66
+
67
+ interface EventCallbacks {
68
+ message?: MessageCallback;
69
+ presence?: PresenceCallback;
70
+ storage?: StorageCallback;
71
+ }
72
+
73
+ interface RTMContextType {
74
+ client: RTMClient | null;
75
+ connectionState: RtmLinkState;
76
+ error: Error | null;
77
+ isLoggedIn: boolean;
78
+ registerCallbacks: (channelName: string, callbacks: EventCallbacks) => void;
79
+ unregisterCallbacks: (channelName: string) => void;
80
+ }
81
+
82
+ const RTMContext = createContext<RTMContextType>({
83
+ client: null,
84
+ connectionState: nativeLinkStateMapping.IDLE,
85
+ error: null,
86
+ isLoggedIn: false,
87
+ registerCallbacks: () => {},
88
+ unregisterCallbacks: () => {},
89
+ });
90
+
91
+ interface RTMCoreProviderProps {
92
+ children: React.ReactNode;
93
+ userInfo: {
94
+ localUid: UidType;
95
+ screenShareUid: UidType;
96
+ isHost: boolean;
97
+ rtmToken?: string;
98
+ };
99
+ }
100
+
101
+ export const RTMCoreProvider: React.FC<RTMCoreProviderProps> = ({
102
+ userInfo,
103
+ children,
104
+ }) => {
105
+ const [client, setClient] = useState<RTMClient | null>(null);
106
+ const [isLoggedIn, setIsLoggedIn] = useState(false);
107
+ const [connectionState, setConnectionState] = useState(0);
108
+ console.log('supriya-rtm connectionState: ', connectionState);
109
+ const [error, setError] = useState<Error | null>(null);
110
+
111
+ const mountedRef = useRef(true);
112
+ const cleaningRef = useRef(false);
113
+ const callbackRegistry = useRef<Map<string, EventCallbacks>>(new Map());
114
+ const errorRef = useRef<Error | null>(null);
115
+
116
+ useEffect(() => {
117
+ mountedRef.current = true;
118
+ return () => {
119
+ mountedRef.current = false;
120
+ };
121
+ }, []);
122
+
123
+ // Sync error ref with state to prevent state clearing
124
+ useEffect(() => {
125
+ errorRef.current = error;
126
+ }, [error]);
127
+
128
+ // Keep error persistent if we have a failed state
129
+ useEffect(() => {
130
+ if (
131
+ connectionState === nativeLinkStateMapping.FAILED &&
132
+ !error &&
133
+ errorRef.current
134
+ ) {
135
+ setError(errorRef.current);
136
+ }
137
+ }, [connectionState, error]);
138
+
139
+ // Memoize userInfo
140
+ const stableUserInfo = useMemo(
141
+ () => ({
142
+ localUid: userInfo.localUid,
143
+ screenShareUid: userInfo.screenShareUid,
144
+ isHost: userInfo.isHost,
145
+ rtmToken: userInfo.rtmToken,
146
+ }),
147
+ [
148
+ userInfo.localUid,
149
+ userInfo.screenShareUid,
150
+ userInfo.isHost,
151
+ userInfo.rtmToken,
152
+ ],
153
+ );
154
+
155
+ const setAttribute = useCallback(async (rtmClient: RTMClient, userInfo) => {
156
+ const rtmAttributes = [
157
+ {key: 'screenUid', value: String(userInfo.screenShareUid)},
158
+ {key: 'isHost', value: String(userInfo.isHost)},
159
+ ];
160
+ try {
161
+ const data: Metadata = {items: rtmAttributes};
162
+ const options: SetOrUpdateUserMetadataOptions = {
163
+ userId: `${userInfo.localUid}`,
164
+ };
165
+ // await rtmClient.storage.removeUserMetadata();
166
+ await rtmClient.storage.setUserMetadata(data, options);
167
+ } catch (setAttributeError) {
168
+ console.log('setAttributeError: ', setAttributeError);
169
+ }
170
+ }, []);
171
+
172
+ const registerCallbacks = useCallback(
173
+ (channelName: string, callbacks: EventCallbacks) => {
174
+ callbackRegistry.current.set(channelName, callbacks);
175
+ },
176
+ [],
177
+ );
178
+
179
+ const unregisterCallbacks = useCallback((channelName: string) => {
180
+ callbackRegistry.current.delete(channelName);
181
+ }, []);
182
+
183
+ // Global event listeners
184
+ useEffect(() => {
185
+ if (!client || !userInfo?.localUid) {
186
+ return;
187
+ }
188
+ const handleGlobalStorageEvent = (storage: StorageEvent) => {
189
+ console.log(
190
+ 'rudra-core-client ********************** ---StorageEvent event: ',
191
+ storage,
192
+ callbackRegistry,
193
+ );
194
+ // Distribute to all registered callbacks
195
+ callbackRegistry.current.forEach((callbacks, channelName) => {
196
+ try {
197
+ callbacks.storage?.(storage);
198
+ } catch (globalStorageCbError) {
199
+ console.log('globalStorageCbError: ', globalStorageCbError);
200
+ }
201
+ });
202
+ };
203
+
204
+ const handleGlobalPresenceEvent = (presence: PresenceEvent) => {
205
+ console.log(
206
+ 'rudra-core-client @@@@@@@@@@@@@@@@@@@@@@@ ---PresenceEvent: ',
207
+ presence,
208
+ callbackRegistry,
209
+ );
210
+ // Distribute to all registered callbacks
211
+ callbackRegistry.current.forEach((callbacks, channelName) => {
212
+ try {
213
+ callbacks.presence?.(presence);
214
+ } catch (globalPresenceCbError) {
215
+ console.log('globalPresenceCbError: ', globalPresenceCbError);
216
+ }
217
+ });
218
+ };
219
+
220
+ const handleGlobalMessageEvent = (message: MessageEvent) => {
221
+ console.log(
222
+ 'rudra-core-client ######################## ---MessageEvent event: ',
223
+ message,
224
+ );
225
+ if (String(userInfo.localUid) === message.publisher) {
226
+ console.log(
227
+ 'rudra-core-client ######################## SKIPPING this message event as it is local',
228
+ message,
229
+ callbackRegistry,
230
+ );
231
+ return;
232
+ }
233
+ // Distribute to all registered callbacks
234
+ callbackRegistry.current.forEach((callbacks, channelName) => {
235
+ try {
236
+ callbacks.message?.(message);
237
+ } catch (globalMessageCbError) {
238
+ console.log('globalMessageCbError: ', globalMessageCbError);
239
+ }
240
+ });
241
+ };
242
+
243
+ client.addEventListener('storage', handleGlobalStorageEvent);
244
+ client.addEventListener('presence', handleGlobalPresenceEvent);
245
+ client.addEventListener('message', handleGlobalMessageEvent);
246
+
247
+ return () => {
248
+ // Remove global event listeners
249
+ client.removeEventListener('storage', handleGlobalStorageEvent);
250
+ client.removeEventListener('presence', handleGlobalPresenceEvent);
251
+ client.removeEventListener('message', handleGlobalMessageEvent);
252
+ };
253
+ }, [client, userInfo?.localUid]);
254
+
255
+ // Link state listener for reconnects
256
+ useEffect(() => {
257
+ if (!client) {
258
+ return;
259
+ }
260
+
261
+ const onLink = async (evt: LinkStateEvent) => {
262
+ setConnectionState(evt.currentState);
263
+
264
+ if (evt.currentState === nativeLinkStateMapping.FAILED) {
265
+ setIsLoggedIn(false);
266
+ // Set error if we're in FAILED state and don't have one
267
+ if (!errorRef.current) {
268
+ const failedError = new Error('RTM connection failed');
269
+ errorRef.current = failedError;
270
+ setError(failedError);
271
+ }
272
+ } else if (evt.currentState === nativeLinkStateMapping.DISCONNECTED) {
273
+ setIsLoggedIn(false);
274
+ if (stableUserInfo.rtmToken) {
275
+ try {
276
+ await loginWithBackoff(client, stableUserInfo.rtmToken);
277
+ if (!mountedRef.current) {
278
+ return;
279
+ }
280
+ setIsLoggedIn(true);
281
+ // Clear error only after successful login
282
+ errorRef.current = null;
283
+ setError(null);
284
+ } catch (err: any) {
285
+ if (!mountedRef.current) {
286
+ return;
287
+ }
288
+ errorRef.current = err;
289
+ setError(err);
290
+ }
291
+ }
292
+ } else if (evt.currentState === nativeLinkStateMapping.CONNECTED) {
293
+ setIsLoggedIn(true);
294
+ // Clear error on successful connection
295
+ errorRef.current = null;
296
+ setError(null);
297
+ }
298
+ };
299
+
300
+ client.addEventListener('linkState', onLink);
301
+ return () => {
302
+ client.removeEventListener('linkState', onLink);
303
+ };
304
+ }, [client, stableUserInfo, setAttribute]);
305
+
306
+ // Initialize RTM
307
+ useEffect(() => {
308
+ if (client) {
309
+ return;
310
+ }
311
+
312
+ (async () => {
313
+ // 1, Check if engine is already connected
314
+ // 2. Initialize RTM Engine
315
+ if (!RTMEngine.getInstance()?.isEngineReady) {
316
+ RTMEngine.getInstance().setLocalUID(stableUserInfo.localUid);
317
+ }
318
+ const rtmClient = RTMEngine.getInstance().engine;
319
+ if (!rtmClient) {
320
+ throw new Error('Failed to create RTM client');
321
+ }
322
+ // 3. Set client after successful setup
323
+ setClient(rtmClient);
324
+
325
+ try {
326
+ if (stableUserInfo.rtmToken) {
327
+ await loginWithBackoff(rtmClient, stableUserInfo.rtmToken);
328
+ await setAttribute(rtmClient, stableUserInfo);
329
+ if (!mountedRef.current) {
330
+ return;
331
+ }
332
+ setIsLoggedIn(true);
333
+ }
334
+ } catch (err: any) {
335
+ if (!mountedRef.current) {
336
+ return;
337
+ }
338
+ errorRef.current = err;
339
+ setError(err);
340
+ }
341
+ })();
342
+ }, [client, stableUserInfo, setAttribute]);
343
+
344
+ // Refresh attributes if userInfo changes while logged in
345
+ useEffect(() => {
346
+ if (client && isLoggedIn && stableUserInfo.rtmToken) {
347
+ setAttribute(client, stableUserInfo).catch(console.warn);
348
+ }
349
+ }, [client, isLoggedIn, stableUserInfo, setAttribute]);
350
+
351
+ const cleanupRTM = useCallback(async () => {
352
+ if (cleaningRef.current) {
353
+ return;
354
+ }
355
+ cleaningRef.current = true;
356
+ try {
357
+ const engine = RTMEngine.getInstance();
358
+ if (engine?.engine) {
359
+ console.log('RTM cleanup: destroying engine...');
360
+ await engine.destroy();
361
+ console.log('RTM cleanup: engine destroyed.');
362
+ }
363
+ setClient(null);
364
+ } catch (err) {
365
+ console.error('RTM cleanup failed:', err);
366
+ } finally {
367
+ cleaningRef.current = false;
368
+ }
369
+ }, []);
370
+
371
+ useAsyncEffect(() => {
372
+ return async () => {
373
+ // Cleanup
374
+ console.log('supriya-rtm-lifecycle cleanup');
375
+ await cleanupRTM();
376
+ };
377
+ }, []);
378
+
379
+ // Browser unload cleanup
380
+ useEffect(() => {
381
+ if (
382
+ !$config.ENABLE_CONVERSATIONAL_AI &&
383
+ isWebInternal() &&
384
+ isWeb() &&
385
+ !isSDK()
386
+ ) {
387
+ const handleBrowserClose = (ev: BeforeUnloadEvent) => {
388
+ ev.preventDefault();
389
+ ev.returnValue = 'Are you sure you want to exit?';
390
+ };
391
+ const handleRTMCleanup = () => {
392
+ cleanupRTM();
393
+ };
394
+ window.addEventListener('beforeunload', handleBrowserClose);
395
+ window.addEventListener('pagehide', handleRTMCleanup);
396
+ return () => {
397
+ window.removeEventListener('beforeunload', handleBrowserClose);
398
+ window.removeEventListener('pagehide', handleRTMCleanup);
399
+ };
400
+ }
401
+ }, [cleanupRTM]);
402
+
403
+ return (
404
+ <RTMContext.Provider
405
+ value={{
406
+ client,
407
+ isLoggedIn,
408
+ connectionState,
409
+ error,
410
+ registerCallbacks,
411
+ unregisterCallbacks,
412
+ }}>
413
+ {/* <RTMStatusBanner /> */}
414
+ {children}
415
+ </RTMContext.Provider>
416
+ );
417
+ };
418
+
419
+ export const useRTMCore = () => useContext(RTMContext);