agora-appbuilder-core 4.1.9 → 4.1.11-beta.2

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 (58) 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/agora-rn-uikit/src/Utils/isBotUser.ts +1 -1
  8. package/template/android/app/build.gradle +0 -7
  9. package/template/bridge/rtm/web/Types.ts +0 -183
  10. package/template/bridge/rtm/web/index.ts +491 -423
  11. package/template/defaultConfig.js +3 -3
  12. package/template/ios/Podfile +0 -41
  13. package/template/package.json +5 -5
  14. package/template/src/assets/font-styles.css +4 -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/ActionMenu.tsx +93 -13
  18. package/template/src/atoms/CustomIcon.tsx +1 -0
  19. package/template/src/atoms/DropDownMulti.tsx +80 -29
  20. package/template/src/atoms/Input.tsx +2 -1
  21. package/template/src/components/Controls.tsx +148 -143
  22. package/template/src/components/EventsConfigure.tsx +152 -97
  23. package/template/src/components/RTMConfigure.tsx +426 -644
  24. package/template/src/components/precall/joinCallBtn.native.tsx +7 -2
  25. package/template/src/components/precall/joinCallBtn.tsx +7 -2
  26. package/template/src/components/precall/joinWaitingRoomBtn.native.tsx +8 -3
  27. package/template/src/components/precall/joinWaitingRoomBtn.tsx +22 -4
  28. package/template/src/components/precall/textInput.tsx +45 -22
  29. package/template/src/components/precall/usePreCall.tsx +7 -0
  30. package/template/src/components/room-info/useRoomInfo.tsx +5 -0
  31. package/template/src/language/default-labels/videoCallScreenLabels.ts +27 -4
  32. package/template/src/pages/video-call/ActionSheetContent.tsx +77 -77
  33. package/template/src/pages/video-call/SidePanelHeader.tsx +81 -36
  34. package/template/src/rtm/RTMEngine.ts +33 -130
  35. package/template/src/rtm-events/constants.ts +6 -0
  36. package/template/src/rtm-events-api/Events.ts +30 -106
  37. package/template/src/subComponents/caption/Caption.tsx +48 -7
  38. package/template/src/subComponents/caption/CaptionContainer.tsx +324 -51
  39. package/template/src/subComponents/caption/CaptionIcon.tsx +35 -34
  40. package/template/src/subComponents/caption/CaptionText.tsx +103 -2
  41. package/template/src/subComponents/caption/LanguageSelectorPopup.tsx +179 -69
  42. package/template/src/subComponents/caption/Transcript.tsx +46 -11
  43. package/template/src/subComponents/caption/TranscriptIcon.tsx +27 -35
  44. package/template/src/subComponents/caption/TranscriptText.tsx +78 -3
  45. package/template/src/subComponents/caption/proto/ptoto.js +38 -4
  46. package/template/src/subComponents/caption/proto/test.proto +34 -19
  47. package/template/src/subComponents/caption/useCaption.tsx +753 -10
  48. package/template/src/subComponents/caption/useSTTAPI.tsx +118 -205
  49. package/template/src/subComponents/caption/useStreamMessageUtils.native.ts +152 -33
  50. package/template/src/subComponents/caption/useStreamMessageUtils.ts +165 -34
  51. package/template/src/subComponents/caption/utils.ts +171 -3
  52. package/template/src/utils/SdkEvents.ts +3 -0
  53. package/template/src/utils/useEndCall.ts +3 -5
  54. package/template/src/utils/useSpeechToText.ts +31 -20
  55. package/template/agora-rn-uikit/src/Reducer/Spotlight.ts +0 -11
  56. package/template/agora-rn-uikit/src/Reducer/UserBanned.ts +0 -11
  57. package/template/bridge/rtm/web/index-legacy.ts +0 -540
  58. package/template/src/components/RTMConfigure-legacy.tsx +0 -848
@@ -1,472 +1,540 @@
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
+ // @ts-nocheck
1
13
  import {
2
- type Metadata as NativeMetadata,
3
- type MetadataItem as NativeMetadataItem,
4
- type GetUserMetadataOptions as NativeGetUserMetadataOptions,
5
- type RtmChannelType as NativeRtmChannelType,
6
- type SetUserMetadataResponse,
7
- type LoginOptions as NativeLoginOptions,
8
- type RTMClientEventMap as NativeRTMClientEventMap,
9
- type GetUserMetadataResponse as NativeGetUserMetadataResponse,
10
- type GetChannelMetadataResponse as NativeGetChannelMetadataResponse,
11
- type SetOrUpdateUserMetadataOptions as NativeSetOrUpdateUserMetadataOptions,
12
- type IMetadataOptions as NativeIMetadataOptions,
13
- type StorageEvent as NativeStorageEvent,
14
- type PresenceEvent as NativePresenceEvent,
15
- type MessageEvent as NativeMessageEvent,
16
- type SubscribeOptions as NativeSubscribeOptions,
17
- type PublishOptions as NativePublishOptions,
18
- } from 'agora-react-native-rtm';
19
- import AgoraRTM, {
20
- RTMClient,
21
- GetUserMetadataResponse,
22
- GetChannelMetadataResponse,
23
- PublishOptions,
24
- ChannelType,
25
- MetaDataDetail,
26
- } from 'agora-rtm-sdk';
27
- import {
28
- linkStatusReasonCodeMapping,
29
- nativeChannelTypeMapping,
30
- nativeLinkStateMapping,
31
- nativeMessageEventTypeMapping,
32
- nativePresenceEventTypeMapping,
33
- nativeStorageEventTypeMapping,
34
- nativeStorageTypeMapping,
35
- webChannelTypeMapping,
36
- } from './Types';
37
-
38
- type CallbackType = (args?: any) => void;
39
-
40
- // Conversion function
41
- const convertWebToNativeMetadata = (webMetadata: any): NativeMetadata => {
42
- // Convert object entries to MetadataItem array
43
- const items: NativeMetadataItem[] =
44
- Object.entries(webMetadata.metadata).map(
45
- ([key, metadataItem]: [string, MetaDataDetail]) => {
46
- return {
47
- key: key,
48
- value: metadataItem.value,
49
- revision: metadataItem.revision,
50
- authorUserId: metadataItem.authorUid,
51
- updateTs: metadataItem.updated,
52
- };
53
- },
54
- ) || [];
55
-
56
- // Create native Metadata object
57
- const nativeMetadata: NativeMetadata = {
58
- majorRevision: webMetadata?.revision || -1, // Use first item's revision as major revision
59
- items: items,
60
- itemCount: webMetadata?.totalCount || 0,
61
- };
62
-
63
- return nativeMetadata;
64
- };
65
-
66
- export class RTMWebClient {
67
- private client: RTMClient;
68
- private appId: string;
69
- private userId: string;
70
- private eventsMap = new Map<keyof NativeRTMClientEventMap, CallbackType>([
71
- ['linkState', () => null],
72
- ['storage', () => null],
73
- ['presence', () => null],
74
- ['message', () => null],
14
+ ChannelAttributeOptions,
15
+ RtmAttribute,
16
+ RtmChannelAttribute,
17
+ Subscription,
18
+ } from 'agora-react-native-rtm/lib/typescript/src';
19
+ import {RtmClientEvents} from 'agora-react-native-rtm/lib/typescript/src/RtmEngine';
20
+ import AgoraRTM, {VERSION} from 'agora-rtm-sdk';
21
+ import RtmClient from 'agora-react-native-rtm';
22
+ import {LogSource, logger} from '../../../src/logger/AppBuilderLogger';
23
+ // export {RtmAttribute}
24
+ //
25
+ interface RtmAttributePlaceholder {}
26
+ export {RtmAttributePlaceholder as RtmAttribute};
27
+
28
+ type callbackType = (args?: any) => void;
29
+
30
+ export default class RtmEngine {
31
+ public appId: string;
32
+ public client: RtmClient;
33
+ public channelMap = new Map<string, any>([]);
34
+ public remoteInvititations = new Map<string, any>([]);
35
+ public localInvititations = new Map<string, any>([]);
36
+ public channelEventsMap = new Map<string, any>([
37
+ ['channelMessageReceived', () => null],
38
+ ['channelMemberJoined', () => null],
39
+ ['channelMemberLeft', () => null],
75
40
  ]);
41
+ public clientEventsMap = new Map<string, any>([
42
+ ['connectionStateChanged', () => null],
43
+ ['messageReceived', () => null],
44
+ ['remoteInvitationReceived', () => null],
45
+ ['tokenExpired', () => null],
46
+ ]);
47
+ public localInvitationEventsMap = new Map<string, any>([
48
+ ['localInvitationAccepted', () => null],
49
+ ['localInvitationCanceled', () => null],
50
+ ['localInvitationFailure', () => null],
51
+ ['localInvitationReceivedByPeer', () => null],
52
+ ['localInvitationRefused', () => null],
53
+ ]);
54
+ public remoteInvitationEventsMap = new Map<string, any>([
55
+ ['remoteInvitationAccepted', () => null],
56
+ ['remoteInvitationCanceled', () => null],
57
+ ['remoteInvitationFailure', () => null],
58
+ ['remoteInvitationRefused', () => null],
59
+ ]);
60
+ constructor() {
61
+ this.appId = '';
62
+ logger.debug(LogSource.AgoraSDK, 'Log', 'Using RTM Bridge');
63
+ }
64
+
65
+ on(event: any, listener: any) {
66
+ if (
67
+ event === 'channelMessageReceived' ||
68
+ event === 'channelMemberJoined' ||
69
+ event === 'channelMemberLeft'
70
+ ) {
71
+ this.channelEventsMap.set(event, listener);
72
+ } else if (
73
+ event === 'connectionStateChanged' ||
74
+ event === 'messageReceived' ||
75
+ event === 'remoteInvitationReceived' ||
76
+ event === 'tokenExpired'
77
+ ) {
78
+ this.clientEventsMap.set(event, listener);
79
+ } else if (
80
+ event === 'localInvitationAccepted' ||
81
+ event === 'localInvitationCanceled' ||
82
+ event === 'localInvitationFailure' ||
83
+ event === 'localInvitationReceivedByPeer' ||
84
+ event === 'localInvitationRefused'
85
+ ) {
86
+ this.localInvitationEventsMap.set(event, listener);
87
+ } else if (
88
+ event === 'remoteInvitationAccepted' ||
89
+ event === 'remoteInvitationCanceled' ||
90
+ event === 'remoteInvitationFailure' ||
91
+ event === 'remoteInvitationRefused'
92
+ ) {
93
+ this.remoteInvitationEventsMap.set(event, listener);
94
+ }
95
+ }
76
96
 
77
- constructor(appId: string, userId: string) {
78
- this.appId = appId;
79
- this.userId = `${userId}`;
80
- try {
81
- // Create the actual web RTM client
82
- this.client = new AgoraRTM.RTM(this.appId, this.userId);
83
-
84
- this.client.addEventListener('linkState', data => {
85
- const nativeState = {
86
- ...data,
87
- currentState:
88
- nativeLinkStateMapping[data.currentState] ||
89
- nativeLinkStateMapping.IDLE,
90
- previousState:
91
- nativeLinkStateMapping[data.previousState] ||
92
- nativeLinkStateMapping.IDLE,
93
- reasonCode: linkStatusReasonCodeMapping[data.reasonCode] || 0,
94
- };
95
- (this.eventsMap.get('linkState') ?? (() => {}))(nativeState);
97
+ createClient(APP_ID: string) {
98
+ this.appId = APP_ID;
99
+ this.client = AgoraRTM.createInstance(this.appId);
100
+
101
+ if ($config.GEO_FENCING) {
102
+ try {
103
+ //include area is comma seperated value
104
+ let includeArea = $config.GEO_FENCING_INCLUDE_AREA
105
+ ? $config.GEO_FENCING_INCLUDE_AREA
106
+ : AREAS.GLOBAL;
107
+
108
+ //exclude area is single value
109
+ let excludeArea = $config.GEO_FENCING_EXCLUDE_AREA
110
+ ? $config.GEO_FENCING_EXCLUDE_AREA
111
+ : '';
112
+
113
+ includeArea = includeArea?.split(',');
114
+
115
+ //pass excludedArea if only its provided
116
+ if (excludeArea) {
117
+ AgoraRTM.setArea({
118
+ areaCodes: includeArea,
119
+ excludedArea: excludeArea,
120
+ });
121
+ }
122
+ //otherwise we can pass area directly
123
+ else {
124
+ AgoraRTM.setArea({areaCodes: includeArea});
125
+ }
126
+ } catch (setAeraError) {
127
+ console.log('error on RTM setArea', setAeraError);
128
+ }
129
+ }
130
+
131
+ window.rtmClient = this.client;
132
+
133
+ this.client.on('ConnectionStateChanged', (state, reason) => {
134
+ this.clientEventsMap.get('connectionStateChanged')({state, reason});
135
+ });
136
+
137
+ this.client.on('MessageFromPeer', (msg, uid, msgProps) => {
138
+ this.clientEventsMap.get('messageReceived')({
139
+ text: msg.text,
140
+ ts: msgProps.serverReceivedTs,
141
+ offline: msgProps.isOfflineMessage,
142
+ peerId: uid,
96
143
  });
144
+ });
97
145
 
98
- this.client.addEventListener('storage', data => {
99
- const nativeStorageEvent: NativeStorageEvent = {
100
- channelType: nativeChannelTypeMapping[data.channelType],
101
- storageType: nativeStorageTypeMapping[data.storageType],
102
- eventType: nativeStorageEventTypeMapping[data.eventType],
103
- data: convertWebToNativeMetadata(data.data),
104
- timestamp: data.timestamp,
105
- };
106
- (this.eventsMap.get('storage') ?? (() => {}))(nativeStorageEvent);
146
+ this.client.on('RemoteInvitationReceived', (remoteInvitation: any) => {
147
+ this.remoteInvititations.set(remoteInvitation.callerId, remoteInvitation);
148
+ this.clientEventsMap.get('remoteInvitationReceived')({
149
+ callerId: remoteInvitation.callerId,
150
+ content: remoteInvitation.content,
151
+ state: remoteInvitation.state,
152
+ channelId: remoteInvitation.channelId,
153
+ response: remoteInvitation.response,
107
154
  });
108
155
 
109
- this.client.addEventListener('presence', data => {
110
- const nativePresenceEvent: NativePresenceEvent = {
111
- channelName: data.channelName,
112
- channelType: nativeChannelTypeMapping[data.channelType],
113
- type: nativePresenceEventTypeMapping[data.eventType],
114
- publisher: data.publisher,
115
- timestamp: data.timestamp,
116
- };
117
- (this.eventsMap.get('presence') ?? (() => {}))(nativePresenceEvent);
156
+ remoteInvitation.on('RemoteInvitationAccepted', () => {
157
+ this.remoteInvitationEventsMap.get('RemoteInvitationAccepted')({
158
+ callerId: remoteInvitation.callerId,
159
+ content: remoteInvitation.content,
160
+ state: remoteInvitation.state,
161
+ channelId: remoteInvitation.channelId,
162
+ response: remoteInvitation.response,
163
+ });
118
164
  });
119
165
 
120
- this.client.addEventListener('message', data => {
121
- const nativeMessageEvent: NativeMessageEvent = {
122
- ...data,
123
- channelType: nativeChannelTypeMapping[data.channelType],
124
- messageType: nativeMessageEventTypeMapping[data.messageType],
125
- message: `${data.message}`,
126
- };
127
- (this.eventsMap.get('message') ?? (() => {}))(nativeMessageEvent);
166
+ remoteInvitation.on('RemoteInvitationCanceled', (content: string) => {
167
+ this.remoteInvitationEventsMap.get('remoteInvitationCanceled')({
168
+ callerId: remoteInvitation.callerId,
169
+ content: content,
170
+ state: remoteInvitation.state,
171
+ channelId: remoteInvitation.channelId,
172
+ response: remoteInvitation.response,
173
+ });
128
174
  });
129
- } catch (error) {
130
- const contextError = new Error(
131
- `Failed to create RTMWebClient for appId: ${this.appId}, userId: ${
132
- this.userId
133
- }. Error: ${error.message || error}`,
134
- );
135
- console.error('RTMWebClient constructor error:', contextError);
136
- throw contextError;
137
- }
138
- }
139
175
 
140
- // Storage methods
141
- get storage() {
142
- return {
143
- setUserMetadata: (
144
- data: NativeMetadata,
145
- options?: NativeSetOrUpdateUserMetadataOptions,
146
- ): Promise<SetUserMetadataResponse> => {
147
- // 1. Validate input parameters
148
- if (!data) {
149
- throw new Error('setUserMetadata: data parameter is required');
150
- }
151
- if (!data.items || !Array.isArray(data.items)) {
152
- throw new Error(
153
- 'setUserMetadata: data.items must be a non-empty array',
154
- );
155
- }
156
- if (data.items.length === 0) {
157
- throw new Error('setUserMetadata: data.items cannot be empty');
158
- }
159
- // 2. Make sure key is present as this is mandatory
160
- // https://docs.agora.io/en/signaling/reference/api?platform=web#storagesetuserpropsag_platform
161
- const validatedItems = data.items.map((item, index) => {
162
- if (!item.key || typeof item.key !== 'string') {
163
- throw new Error(
164
- `setUserMetadata: item at index ${index} missing required 'key' property`,
165
- );
166
- }
167
- return {
168
- key: item.key,
169
- value: item.value || '', // Default to empty string if not provided
170
- revision: item.revision || -1, // Default to -1 if not provided
171
- };
176
+ remoteInvitation.on('RemoteInvitationFailure', (reason: string) => {
177
+ this.remoteInvitationEventsMap.get('remoteInvitationFailure')({
178
+ callerId: remoteInvitation.callerId,
179
+ content: remoteInvitation.content,
180
+ state: remoteInvitation.state,
181
+ channelId: remoteInvitation.channelId,
182
+ response: remoteInvitation.response,
183
+ code: -1, //Web sends string, RN expect number but can't find enum
172
184
  });
173
- // Map native signature to web signature
174
- return this.client.storage.setUserMetadata(validatedItems, {
175
- addTimeStamp: options?.addTimeStamp || true,
176
- addUserId: options?.addUserId || true,
185
+ });
186
+
187
+ remoteInvitation.on('RemoteInvitationRefused', () => {
188
+ this.remoteInvitationEventsMap.get('remoteInvitationRefused')({
189
+ callerId: remoteInvitation.callerId,
190
+ content: remoteInvitation.content,
191
+ state: remoteInvitation.state,
192
+ channelId: remoteInvitation.channelId,
193
+ response: remoteInvitation.response,
177
194
  });
178
- },
195
+ });
196
+ });
179
197
 
180
- getUserMetadata: async (options: NativeGetUserMetadataOptions) => {
181
- // Validate input parameters
182
- if (!options) {
183
- throw new Error('getUserMetadata: options parameter is required');
184
- }
185
- if (
186
- !options.userId ||
187
- typeof options.userId !== 'string' ||
188
- options.userId.trim() === ''
189
- ) {
190
- throw new Error(
191
- 'getUserMetadata: options.userId must be a non-empty string',
192
- );
193
- }
194
- const webResponse: GetUserMetadataResponse =
195
- await this.client.storage.getUserMetadata({
196
- userId: options.userId,
197
- });
198
+ this.client.on('TokenExpired', () => {
199
+ this.clientEventsMap.get('tokenExpired')({}); //RN expect evt: any
200
+ });
201
+ }
202
+
203
+ async login(loginParam: {uid: string; token?: string}): Promise<any> {
204
+ return this.client.login(loginParam);
205
+ }
206
+
207
+ async logout(): Promise<any> {
208
+ return await this.client.logout();
209
+ }
210
+
211
+ async joinChannel(channelId: string): Promise<any> {
212
+ this.channelMap.set(channelId, this.client.createChannel(channelId));
213
+ this.channelMap
214
+ .get(channelId)
215
+ .on('ChannelMessage', (msg: {text: string}, uid: string, messagePros) => {
216
+ let text = msg.text;
217
+ let ts = messagePros.serverReceivedTs;
218
+ this.channelEventsMap.get('channelMessageReceived')({
219
+ uid,
220
+ channelId,
221
+ text,
222
+ ts,
223
+ });
224
+ });
225
+ this.channelMap.get(channelId).on('MemberJoined', (uid: string) => {
226
+ this.channelEventsMap.get('channelMemberJoined')({uid, channelId});
227
+ });
228
+ this.channelMap.get(channelId).on('MemberLeft', (uid: string) => {
229
+ console.log('Member Left', this.channelEventsMap);
230
+ this.channelEventsMap.get('channelMemberLeft')({uid});
231
+ });
232
+ this.channelMap
233
+ .get(channelId)
234
+ .on('AttributesUpdated', (attributes: RtmChannelAttribute) => {
235
+ /**
236
+ * a) Kindly note the below event listener 'channelAttributesUpdated' expects type
237
+ * RtmChannelAttribute[] (array of objects [{key: 'valueOfKey', value: 'valueOfValue}])
238
+ * whereas the above listener 'AttributesUpdated' receives attributes in object form
239
+ * {[valueOfKey]: valueOfValue} of type RtmChannelAttribute
240
+ * b) Hence in this bridge the data should be modified to keep in sync with both the
241
+ * listeners for web and listener for native
242
+ */
198
243
  /**
199
- * majorRevision : 13483783553
200
- * metadata :
201
- * {
202
- * isHost: {authorUid: "", revision: 13483783553, updated: 0, value : "true"},
203
- * screenUid: {…}}
204
- * }
205
- * timestamp: 0
206
- * totalCount: 2
207
- * userId: "xxx"
244
+ * 1. Loop through object
245
+ * 2. Create a object {key: "", value: ""} and push into array
246
+ * 3. Return the Array
208
247
  */
209
- const items = Object.entries(webResponse.metadata).map(
210
- ([key, metadataItem]) => ({
211
- key: key,
212
- value: metadataItem.value,
213
- }),
248
+ const channelAttributes = Object.keys(attributes).reduce((acc, key) => {
249
+ const {value, lastUpdateTs, lastUpdateUserId} = attributes[key];
250
+ acc.push({key, value, lastUpdateTs, lastUpdateUserId});
251
+ return acc;
252
+ }, []);
253
+
254
+ this.channelEventsMap.get('ChannelAttributesUpdated')(
255
+ channelAttributes,
214
256
  );
215
- const nativeResponse: NativeGetUserMetadataResponse = {
216
- items: [...items],
217
- itemCount: webResponse.totalCount,
218
- userId: webResponse.userId,
219
- timestamp: webResponse.timestamp,
220
- };
221
- return nativeResponse;
222
- },
257
+ });
223
258
 
224
- setChannelMetadata: async (
225
- channelName: string,
226
- channelType: NativeRtmChannelType,
227
- data: NativeMetadata,
228
- options?: NativeIMetadataOptions,
229
- ) => {
230
- // Validate input parameters
231
- if (
232
- !channelName ||
233
- typeof channelName !== 'string' ||
234
- channelName.trim() === ''
235
- ) {
236
- throw new Error(
237
- 'setChannelMetadata: channelName must be a non-empty string',
238
- );
239
- }
240
- if (typeof channelType !== 'number') {
241
- throw new Error('setChannelMetadata: channelType must be a number');
242
- }
243
- if (!data) {
244
- throw new Error('setChannelMetadata: data parameter is required');
245
- }
246
- if (!data.items || !Array.isArray(data.items)) {
247
- throw new Error('setChannelMetadata: data.items must be an array');
248
- }
249
- if (data.items.length === 0) {
250
- throw new Error('setChannelMetadata: data.items cannot be empty');
251
- }
252
- // 2. Make sure key is present as this is mandatory
253
- // https://docs.agora.io/en/signaling/reference/api?platform=web#storagesetuserpropsag_platform
254
- const validatedItems = data.items.map((item, index) => {
255
- if (!item.key || typeof item.key !== 'string') {
256
- throw new Error(
257
- `setChannelMetadata: item at index ${index} missing required 'key' property`,
258
- );
259
- }
260
- return {
261
- key: item.key,
262
- value: item.value || '', // Default to empty string if not provided
263
- revision: item.revision || -1, // Default to -1 if not provided
264
- };
259
+ return this.channelMap.get(channelId).join();
260
+ }
261
+
262
+ async leaveChannel(channelId: string): Promise<any> {
263
+ if (this.channelMap.get(channelId)) {
264
+ return this.channelMap.get(channelId).leave();
265
+ } else {
266
+ Promise.reject('Wrong channel');
267
+ }
268
+ }
269
+
270
+ async sendMessageByChannelId(channel: string, message: string): Promise<any> {
271
+ if (this.channelMap.get(channel)) {
272
+ return this.channelMap.get(channel).sendMessage({text: message});
273
+ } else {
274
+ console.log(this.channelMap, channel);
275
+ Promise.reject('Wrong channel');
276
+ }
277
+ }
278
+
279
+ destroyClient() {
280
+ console.log('Destroy called');
281
+ this.channelEventsMap.forEach((callback, event) => {
282
+ this.client.off(event, callback);
283
+ });
284
+ this.channelEventsMap.clear();
285
+ this.channelMap.clear();
286
+ this.clientEventsMap.clear();
287
+ this.remoteInvitationEventsMap.clear();
288
+ this.localInvitationEventsMap.clear();
289
+ }
290
+
291
+ async getChannelMembersBychannelId(channel: string) {
292
+ if (this.channelMap.get(channel)) {
293
+ let memberArray: Array<any> = [];
294
+ let currentChannel = this.channelMap.get(channel);
295
+ await currentChannel.getMembers().then((arr: Array<number>) => {
296
+ arr.map((elem: number) => {
297
+ memberArray.push({
298
+ channelId: channel,
299
+ uid: elem,
300
+ });
265
301
  });
266
- return this.client.storage.setChannelMetadata(
267
- channelName,
268
- (webChannelTypeMapping[channelType] as ChannelType) || 'MESSAGE',
269
- validatedItems,
270
- {
271
- addUserId: options?.addUserId || true,
272
- addTimeStamp: options?.addTimeStamp || true,
273
- },
274
- );
275
- },
302
+ });
303
+ return {members: memberArray};
304
+ } else {
305
+ Promise.reject('Wrong channel');
306
+ }
307
+ }
276
308
 
277
- getChannelMetadata: async (
278
- channelName: string,
279
- channelType: NativeRtmChannelType,
280
- ) => {
281
- try {
282
- const webResponse: GetChannelMetadataResponse =
283
- await this.client.storage.getChannelMetadata(
284
- channelName,
285
- (webChannelTypeMapping[channelType] as ChannelType) || 'MESSAGE',
286
- );
287
-
288
- const items = Object.entries(webResponse.metadata).map(
289
- ([key, metadataItem]) => ({
290
- key: key,
291
- value: metadataItem.value,
292
- }),
293
- );
294
- const nativeResponse: NativeGetChannelMetadataResponse = {
295
- items: [...items],
296
- itemCount: webResponse.totalCount,
297
- timestamp: webResponse.timestamp,
298
- channelName: webResponse.channelName,
299
- channelType: nativeChannelTypeMapping.MESSAGE,
300
- };
301
- return nativeResponse;
302
- } catch (error) {
303
- const contextError = new Error(
304
- `Failed to get channel metadata for channel '${channelName}' with type ${channelType}: ${
305
- error.message || error
306
- }`,
307
- );
308
- console.error('BRIDGE getChannelMetadata error:', contextError);
309
- throw contextError;
310
- }
311
- },
312
- };
309
+ async queryPeersOnlineStatus(uid: Array<String>) {
310
+ let peerArray: Array<any> = [];
311
+ await this.client.queryPeersOnlineStatus(uid).then(list => {
312
+ Object.entries(list).forEach(value => {
313
+ peerArray.push({
314
+ online: value[1],
315
+ uid: value[0],
316
+ });
317
+ });
318
+ });
319
+ return {items: peerArray};
313
320
  }
314
321
 
315
- get presence() {
316
- return {
317
- getOnlineUsers: async (
318
- channelName: string,
319
- channelType: NativeRtmChannelType,
320
- ) => {
321
- // Validate input parameters
322
- if (
323
- !channelName ||
324
- typeof channelName !== 'string' ||
325
- channelName.trim() === ''
326
- ) {
327
- throw new Error(
328
- 'getOnlineUsers: channelName must be a non-empty string',
329
- );
330
- }
331
- if (typeof channelType !== 'number') {
332
- throw new Error('getOnlineUsers: channelType must be a number');
333
- }
322
+ async renewToken(token: string) {
323
+ return this.client.renewToken(token);
324
+ }
334
325
 
335
- try {
336
- // Call web SDK's presence method
337
- const result = await this.client.presence.getOnlineUsers(
338
- channelName,
339
- (webChannelTypeMapping[channelType] as ChannelType) || 'MESSAGE',
340
- );
341
- return result;
342
- } catch (error) {
343
- const contextError = new Error(
344
- `Failed to get online users for channel '${channelName}' with type ${channelType}: ${
345
- error.message || error
346
- }`,
347
- );
348
- console.error('BRIDGE presence error:', contextError);
349
- throw contextError;
350
- }
351
- },
326
+ async getUserAttributesByUid(uid: string) {
327
+ let response = {};
328
+ await this.client
329
+ .getUserAttributes(uid)
330
+ .then((attributes: string) => {
331
+ response = {attributes, uid};
332
+ })
333
+ .catch((e: any) => {
334
+ Promise.reject(e);
335
+ });
336
+ return response;
337
+ }
352
338
 
353
- whoNow: async (
354
- channelName: string,
355
- channelType?: NativeRtmChannelType,
356
- ) => {
357
- const webChannelType = channelType
358
- ? (webChannelTypeMapping[channelType] as ChannelType)
359
- : 'MESSAGE';
360
- return this.client.presence.whoNow(channelName, webChannelType);
361
- },
339
+ async getChannelAttributes(channelId: string) {
340
+ let response = {};
341
+ await this.client
342
+ .getChannelAttributes(channelId)
343
+ .then((attributes: RtmChannelAttribute) => {
344
+ /**
345
+ * Here the attributes received are in the format {[valueOfKey]: valueOfValue} of type RtmChannelAttribute
346
+ * We need to convert it into (array of objects [{key: 'valueOfKey', value: 'valueOfValue}])
347
+ /**
348
+ * 1. Loop through object
349
+ * 2. Create a object {key: "", value: ""} and push into array
350
+ * 3. Return the Array
351
+ */
352
+ const channelAttributes = Object.keys(attributes).reduce((acc, key) => {
353
+ const {value, lastUpdateTs, lastUpdateUserId} = attributes[key];
354
+ acc.push({key, value, lastUpdateTs, lastUpdateUserId});
355
+ return acc;
356
+ }, []);
357
+ response = channelAttributes;
358
+ })
359
+ .catch((e: any) => {
360
+ Promise.reject(e);
361
+ });
362
+ return response;
363
+ }
362
364
 
363
- whereNow: async (userId: string) => {
364
- return this.client.presence.whereNow(userId);
365
- },
366
- };
365
+ async removeAllLocalUserAttributes() {
366
+ return this.client.clearLocalUserAttributes();
367
367
  }
368
368
 
369
- addEventListener(
370
- event: keyof NativeRTMClientEventMap,
371
- listener: (event: any) => void,
372
- ) {
373
- if (this.client) {
374
- // Simply replace the handler in our map - web client listeners are fixed in constructor
375
- this.eventsMap.set(event, listener as CallbackType);
376
- }
369
+ async removeLocalUserAttributesByKeys(keys: string[]) {
370
+ return this.client.deleteLocalUserAttributesByKeys(keys);
377
371
  }
378
372
 
379
- removeEventListener(
380
- event: keyof NativeRTMClientEventMap,
381
- _listener: (event: any) => void,
382
- ) {
383
- if (this.client && this.eventsMap.has(event)) {
384
- const prevListener = this.eventsMap.get(event);
385
- if (prevListener) {
386
- this.client.removeEventListener(event, prevListener);
387
- }
388
- this.eventsMap.set(event, () => null); // reset to no-op
389
- }
373
+ async replaceLocalUserAttributes(attributes: string[]) {
374
+ let formattedAttributes: any = {};
375
+ attributes.map(attribute => {
376
+ let key = Object.values(attribute)[0];
377
+ let value = Object.values(attribute)[1];
378
+ formattedAttributes[key] = value;
379
+ });
380
+ return this.client.setLocalUserAttributes({...formattedAttributes});
390
381
  }
391
382
 
392
- // Core RTM methods - direct delegation to web SDK
393
- async login(options?: NativeLoginOptions) {
394
- if (!options?.token) {
395
- throw new Error('login: token is required in options');
396
- }
397
- return this.client.login({token: options.token});
383
+ async setLocalUserAttributes(attributes: string[]) {
384
+ let formattedAttributes: any = {};
385
+ attributes.map(attribute => {
386
+ let key = Object.values(attribute)[0];
387
+ let value = Object.values(attribute)[1];
388
+ formattedAttributes[key] = value;
389
+ // console.log('!!!!formattedAttributes', formattedAttributes, key, value);
390
+ });
391
+ return this.client.setLocalUserAttributes({...formattedAttributes});
398
392
  }
399
393
 
400
- async logout() {
401
- return this.client.logout();
394
+ async addOrUpdateLocalUserAttributes(attributes: RtmAttribute[]) {
395
+ let formattedAttributes: any = {};
396
+ attributes.map(attribute => {
397
+ let key = Object.values(attribute)[0];
398
+ let value = Object.values(attribute)[1];
399
+ formattedAttributes[key] = value;
400
+ });
401
+ return this.client.addOrUpdateLocalUserAttributes({...formattedAttributes});
402
402
  }
403
403
 
404
- async subscribe(channelName: string, options?: NativeSubscribeOptions) {
405
- if (
406
- !channelName ||
407
- typeof channelName !== 'string' ||
408
- channelName.trim() === ''
409
- ) {
410
- throw new Error('subscribe: channelName must be a non-empty string');
411
- }
412
- return this.client.subscribe(channelName, options);
404
+ async addOrUpdateChannelAttributes(
405
+ channelId: string,
406
+ attributes: RtmChannelAttribute[],
407
+ option: ChannelAttributeOptions,
408
+ ): Promise<void> {
409
+ let formattedAttributes: any = {};
410
+ attributes.map(attribute => {
411
+ let key = Object.values(attribute)[0];
412
+ let value = Object.values(attribute)[1];
413
+ formattedAttributes[key] = value;
414
+ });
415
+ return this.client.addOrUpdateChannelAttributes(
416
+ channelId,
417
+ {...formattedAttributes},
418
+ option,
419
+ );
413
420
  }
414
421
 
415
- async unsubscribe(channelName: string) {
416
- return this.client.unsubscribe(channelName);
422
+ async sendLocalInvitation(invitationProps: any) {
423
+ let invite = this.client.createLocalInvitation(invitationProps.uid);
424
+ this.localInvititations.set(invitationProps.uid, invite);
425
+ invite.content = invitationProps.content;
426
+
427
+ invite.on('LocalInvitationAccepted', (response: string) => {
428
+ this.localInvitationEventsMap.get('localInvitationAccepted')({
429
+ calleeId: invite.calleeId,
430
+ content: invite.content,
431
+ state: invite.state,
432
+ channelId: invite.channelId,
433
+ response,
434
+ });
435
+ });
436
+
437
+ invite.on('LocalInvitationCanceled', () => {
438
+ this.localInvitationEventsMap.get('localInvitationCanceled')({
439
+ calleeId: invite.calleeId,
440
+ content: invite.content,
441
+ state: invite.state,
442
+ channelId: invite.channelId,
443
+ response: invite.response,
444
+ });
445
+ });
446
+
447
+ invite.on('LocalInvitationFailure', (reason: string) => {
448
+ this.localInvitationEventsMap.get('localInvitationFailure')({
449
+ calleeId: invite.calleeId,
450
+ content: invite.content,
451
+ state: invite.state,
452
+ channelId: invite.channelId,
453
+ response: invite.response,
454
+ code: -1, //Web sends string, RN expect number but can't find enum
455
+ });
456
+ });
457
+
458
+ invite.on('LocalInvitationReceivedByPeer', () => {
459
+ this.localInvitationEventsMap.get('localInvitationReceivedByPeer')({
460
+ calleeId: invite.calleeId,
461
+ content: invite.content,
462
+ state: invite.state,
463
+ channelId: invite.channelId,
464
+ response: invite.response,
465
+ });
466
+ });
467
+
468
+ invite.on('LocalInvitationRefused', (response: string) => {
469
+ this.localInvitationEventsMap.get('localInvitationRefused')({
470
+ calleeId: invite.calleeId,
471
+ content: invite.content,
472
+ state: invite.state,
473
+ channelId: invite.channelId,
474
+ response: response,
475
+ });
476
+ });
477
+ return invite.send();
417
478
  }
418
479
 
419
- async publish(
420
- channelName: string,
421
- message: string,
422
- options?: NativePublishOptions,
423
- ) {
424
- // Validate input parameters
425
- if (
426
- !channelName ||
427
- typeof channelName !== 'string' ||
428
- channelName.trim() === ''
429
- ) {
430
- throw new Error('publish: channelName must be a non-empty string');
431
- }
432
- if (typeof message !== 'string') {
433
- throw new Error('publish: message must be a string');
434
- }
480
+ async sendMessageToPeer(AgoraPeerMessage: {
481
+ peerId: string;
482
+ offline: boolean;
483
+ text: string;
484
+ }) {
485
+ return this.client.sendMessageToPeer(
486
+ {text: AgoraPeerMessage.text},
487
+ AgoraPeerMessage.peerId,
488
+ );
489
+ //check promise result
490
+ }
435
491
 
436
- const webOptions: PublishOptions = {
437
- ...options,
438
- channelType:
439
- (webChannelTypeMapping[options?.channelType] as ChannelType) ||
440
- 'MESSAGE',
441
- };
442
- return this.client.publish(channelName, message, webOptions);
492
+ async acceptRemoteInvitation(remoteInvitationProps: {
493
+ uid: string;
494
+ response?: string;
495
+ channelId: string;
496
+ }) {
497
+ let invite = this.remoteInvititations.get(remoteInvitationProps.uid);
498
+ // console.log(invite);
499
+ // console.log(this.remoteInvititations);
500
+ // console.log(remoteInvitationProps.uid);
501
+ return invite.accept();
443
502
  }
444
503
 
445
- async renewToken(token: string) {
446
- return this.client.renewToken(token);
504
+ async refuseRemoteInvitation(remoteInvitationProps: {
505
+ uid: string;
506
+ response?: string;
507
+ channelId: string;
508
+ }) {
509
+ return this.remoteInvititations.get(remoteInvitationProps.uid).refuse();
447
510
  }
448
511
 
449
- removeAllListeners() {
450
- this.eventsMap = new Map([
451
- ['linkState', () => null],
452
- ['storage', () => null],
453
- ['presence', () => null],
454
- ['message', () => null],
455
- ]);
456
- return this.client.removeAllListeners();
512
+ async cancelLocalInvitation(LocalInvitationProps: {
513
+ uid: string;
514
+ content?: string;
515
+ channelId?: string;
516
+ }) {
517
+ console.log(this.localInvititations.get(LocalInvitationProps.uid));
518
+ return this.localInvititations.get(LocalInvitationProps.uid).cancel();
457
519
  }
458
- }
459
520
 
460
- export class RtmConfig {
461
- public appId: string;
462
- public userId: string;
521
+ getSdkVersion(callback: (version: string) => void) {
522
+ callback(VERSION);
523
+ }
463
524
 
464
- constructor(config: {appId: string; userId: string}) {
465
- this.appId = config.appId;
466
- this.userId = config.userId;
525
+ addListener<EventType extends keyof RtmClientEvents>(
526
+ event: EventType,
527
+ listener: RtmClientEvents[EventType],
528
+ ): Subscription {
529
+ if (event === 'ChannelAttributesUpdated') {
530
+ this.channelEventsMap.set(event, listener as callbackType);
531
+ }
532
+ return {
533
+ remove: () => {
534
+ console.log(
535
+ 'Use destroy method to remove all the event listeners from the RtcEngine instead.',
536
+ );
537
+ },
538
+ };
467
539
  }
468
540
  }
469
- // Factory function to create RTM client
470
- export function createAgoraRtmClient(config: RtmConfig): RTMWebClient {
471
- return new RTMWebClient(config.appId, config.userId);
472
- }