agora-appbuilder-core 4.1.11 → 4.1.12

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.
@@ -10,57 +10,71 @@
10
10
  *********************************************
11
11
  */
12
12
 
13
- import RtmEngine from 'agora-react-native-rtm';
13
+ import {
14
+ createAgoraRtmClient,
15
+ RtmConfig,
16
+ type RTMClient,
17
+ } from 'agora-react-native-rtm';
14
18
  import {isAndroid, isIOS} from '../utils/common';
15
19
 
16
20
  class RTMEngine {
17
- engine!: RtmEngine;
21
+ private _engine?: RTMClient;
18
22
  private localUID: string = '';
19
23
  private channelId: string = '';
20
24
 
21
25
  private static _instance: RTMEngine | null = null;
22
26
 
27
+ private constructor() {
28
+ if (RTMEngine._instance) {
29
+ return RTMEngine._instance;
30
+ }
31
+ RTMEngine._instance = this;
32
+ return RTMEngine._instance;
33
+ }
34
+
23
35
  public static getInstance() {
36
+ // We are only creating the instance but not creating the rtm client yet
24
37
  if (!RTMEngine._instance) {
25
- return new RTMEngine();
38
+ RTMEngine._instance = new RTMEngine();
26
39
  }
27
40
  return RTMEngine._instance;
28
41
  }
29
42
 
30
- private async createClientInstance() {
31
- await this.engine.createClient($config.APP_ID);
32
- }
43
+ setLocalUID(localUID: string | number) {
44
+ if (localUID === null || localUID === undefined) {
45
+ throw new Error('setLocalUID: localUID cannot be null or undefined');
46
+ }
33
47
 
34
- private async destroyClientInstance() {
35
- await this.engine.logout();
36
- if (isIOS() || isAndroid()) {
37
- await this.engine.destroyClient();
48
+ const newUID = String(localUID);
49
+ if (newUID.trim() === '') {
50
+ throw new Error(
51
+ 'setLocalUID: localUID cannot be empty after string conversion',
52
+ );
38
53
  }
39
- }
40
54
 
41
- private constructor() {
42
- if (RTMEngine._instance) {
43
- return RTMEngine._instance;
55
+ // If UID is changing and we have an existing engine, throw error
56
+ if (this._engine && this.localUID !== newUID) {
57
+ throw new Error(
58
+ `RTMEngine: Cannot change UID from '${this.localUID}' to '${newUID}' while engine is active. ` +
59
+ `Please call destroy() first, then setLocalUID() with the new UID.`,
60
+ );
44
61
  }
45
- RTMEngine._instance = this;
46
- this.engine = new RtmEngine();
47
- this.localUID = '';
48
- this.channelId = '';
49
- this.createClientInstance();
50
62
 
51
- return RTMEngine._instance;
52
- }
63
+ this.localUID = newUID;
53
64
 
54
- setLocalUID(localUID: string) {
55
- this.localUID = localUID;
65
+ if (!this._engine) {
66
+ this.createClientInstance();
67
+ }
56
68
  }
57
69
 
58
70
  setChannelId(channelID: string) {
59
- this.channelId = channelID;
60
- }
61
-
62
- setLoginInfo(localUID: string, channelID: string) {
63
- this.localUID = localUID;
71
+ if (
72
+ !channelID ||
73
+ typeof channelID !== 'string' ||
74
+ channelID.trim() === ''
75
+ ) {
76
+ throw new Error('setChannelId: channelID must be a non-empty string');
77
+ }
64
78
  this.channelId = channelID;
65
79
  }
66
80
 
@@ -72,16 +86,99 @@ class RTMEngine {
72
86
  return this.channelId;
73
87
  }
74
88
 
89
+ get isEngineReady() {
90
+ return !!this._engine && !!this.localUID;
91
+ }
92
+
93
+ get engine(): RTMClient {
94
+ this.ensureEngineReady();
95
+ return this._engine!;
96
+ }
97
+
98
+ private ensureEngineReady() {
99
+ if (!this.isEngineReady) {
100
+ throw new Error(
101
+ 'RTM Engine not ready. Please call setLocalUID() with a valid UID first.',
102
+ );
103
+ }
104
+ }
105
+
106
+ private createClientInstance() {
107
+ try {
108
+ if (!this.localUID || this.localUID.trim() === '') {
109
+ throw new Error('Cannot create RTM client: localUID is not set');
110
+ }
111
+ if (!$config.APP_ID) {
112
+ throw new Error('Cannot create RTM client: APP_ID is not configured');
113
+ }
114
+ const rtmConfig = new RtmConfig({
115
+ appId: $config.APP_ID,
116
+ userId: this.localUID,
117
+ });
118
+ this._engine = createAgoraRtmClient(rtmConfig);
119
+ } catch (error) {
120
+ const contextError = new Error(
121
+ `Failed to create RTM client instance for userId: ${
122
+ this.localUID
123
+ }, appId: ${$config.APP_ID}. Error: ${error.message || error}`,
124
+ );
125
+ console.error('RTMEngine createClientInstance error:', contextError);
126
+ throw contextError;
127
+ }
128
+ }
129
+
130
+ private async destroyClientInstance() {
131
+ try {
132
+ if (this._engine) {
133
+ // 1. Unsubscribe from channel if we have one
134
+ if (this.channelId) {
135
+ try {
136
+ await this._engine.unsubscribe(this.channelId);
137
+ } catch (error) {
138
+ console.warn(
139
+ `Failed to unsubscribe from channel '${this.channelId}':`,
140
+ error,
141
+ );
142
+ // Continue with cleanup even if unsubscribe fails
143
+ }
144
+ }
145
+ // 2. Remove all listeners
146
+ try {
147
+ this._engine.removeAllListeners?.();
148
+ } catch (error) {
149
+ console.warn('Failed to remove listeners:', error);
150
+ }
151
+ // 3. Logout
152
+ try {
153
+ await this._engine.logout();
154
+ if (isAndroid() || isIOS()) {
155
+ this._engine.release();
156
+ }
157
+ } catch (error) {
158
+ console.warn('Failed to logout:', error);
159
+ }
160
+ }
161
+ } catch (error) {
162
+ console.error('Error during client instance destruction:', error);
163
+ // Don't re-throw - we want cleanup to complete
164
+ }
165
+ }
166
+
75
167
  async destroy() {
76
168
  try {
77
- await this.destroyClientInstance();
78
- if (isIOS() || isAndroid()) {
79
- RTMEngine._instance = null;
169
+ if (!this._engine) {
170
+ return;
80
171
  }
81
- this.localUID = '';
172
+
173
+ await this.destroyClientInstance();
82
174
  this.channelId = '';
175
+ this.localUID = '';
176
+ this._engine = undefined;
177
+ RTMEngine._instance = null;
83
178
  } catch (error) {
84
- console.log('Error destroying instance error: ', error);
179
+ console.error('Error destroying RTM instance:', error);
180
+ // Don't re-throw - destruction should be a best-effort cleanup
181
+ // Re-throwing could prevent proper cleanup in calling code
85
182
  }
86
183
  }
87
184
  }
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  ('use strict');
14
- import RtmEngine from 'agora-react-native-rtm';
14
+ import {type RTMClient} from 'agora-react-native-rtm';
15
15
  import RTMEngine from '../rtm/RTMEngine';
16
16
  import {EventUtils} from '../rtm-events';
17
17
  import {
@@ -23,6 +23,7 @@ import {
23
23
  } from './types';
24
24
  import {adjustUID} from '../rtm/utils';
25
25
  import {LogSource, logger} from '../logger/AppBuilderLogger';
26
+ import {nativeChannelTypeMapping} from '../../bridge/rtm/web/Types';
26
27
 
27
28
  class Events {
28
29
  private source: EventSource = EventSource.core;
@@ -41,11 +42,17 @@ class Events {
41
42
  * @api private
42
43
  */
43
44
  private _persist = async (evt: string, payload: string) => {
44
- const rtmEngine: RtmEngine = RTMEngine.getInstance().engine;
45
+ const rtmEngine: RTMClient = RTMEngine.getInstance().engine;
46
+ const userId = RTMEngine.getInstance().localUid;
45
47
  try {
46
48
  const rtmAttribute = {key: evt, value: payload};
47
49
  // Step 1: Call RTM API to update local attributes
48
- await rtmEngine.addOrUpdateLocalUserAttributes([rtmAttribute]);
50
+ await rtmEngine.storage.setUserMetadata(
51
+ {items: [rtmAttribute]},
52
+ {
53
+ userId,
54
+ },
55
+ );
49
56
  } catch (error) {
50
57
  logger.error(
51
58
  LogSource.Events,
@@ -68,8 +75,8 @@ class Events {
68
75
  `CUSTOM_EVENT_API Event name cannot be of type ${typeof evt}`,
69
76
  );
70
77
  }
71
- if (evt.trim() == '') {
72
- throw Error(`CUSTOM_EVENT_API Name or function cannot be empty`);
78
+ if (evt.trim() === '') {
79
+ throw Error('CUSTOM_EVENT_API Name or function cannot be empty');
73
80
  }
74
81
  return true;
75
82
  };
@@ -103,10 +110,15 @@ class Events {
103
110
  rtmPayload: RTMAttributePayload,
104
111
  toUid?: ReceiverUid,
105
112
  ) => {
106
- const to = typeof toUid == 'string' ? parseInt(toUid) : toUid;
107
- const rtmEngine: RtmEngine = RTMEngine.getInstance().engine;
113
+ const to = typeof toUid === 'string' ? parseInt(toUid, 10) : toUid;
108
114
 
109
115
  const text = JSON.stringify(rtmPayload);
116
+
117
+ if (!RTMEngine.getInstance().isEngineReady) {
118
+ throw new Error('RTM Engine is not ready. Call setLocalUID() first.');
119
+ }
120
+ const rtmEngine: RTMClient = RTMEngine.getInstance().engine;
121
+
110
122
  // Case 1: send to channel
111
123
  if (
112
124
  typeof to === 'undefined' ||
@@ -120,7 +132,16 @@ class Events {
120
132
  );
121
133
  try {
122
134
  const channelId = RTMEngine.getInstance().channelUid;
123
- await rtmEngine.sendMessageByChannelId(channelId, text);
135
+ if (!channelId || channelId.trim() === '') {
136
+ throw new Error(
137
+ 'Channel ID is not set. Cannot send channel attributes.',
138
+ );
139
+ }
140
+ await rtmEngine.publish(channelId, text, {
141
+ channelType: nativeChannelTypeMapping.MESSAGE, // 1 is message
142
+ customType: 'PlainText',
143
+ messageType: 1,
144
+ });
124
145
  } catch (error) {
125
146
  logger.error(
126
147
  LogSource.Events,
@@ -140,10 +161,10 @@ class Events {
140
161
  );
141
162
  const adjustedUID = adjustUID(to);
142
163
  try {
143
- await rtmEngine.sendMessageToPeer({
144
- peerId: `${adjustedUID}`,
145
- offline: false,
146
- text,
164
+ await rtmEngine.publish(`${adjustedUID}`, text, {
165
+ channelType: nativeChannelTypeMapping.USER, // user
166
+ customType: 'PlainText',
167
+ messageType: 1,
147
168
  });
148
169
  } catch (error) {
149
170
  logger.error(
@@ -164,14 +185,32 @@ class Events {
164
185
  to,
165
186
  );
166
187
  try {
167
- for (const uid of to) {
168
- const adjustedUID = adjustUID(uid);
169
- await rtmEngine.sendMessageToPeer({
170
- peerId: `${adjustedUID}`,
171
- offline: false,
172
- text,
173
- });
174
- }
188
+ const response = await Promise.allSettled(
189
+ to.map(uid =>
190
+ rtmEngine.publish(`${adjustUID(uid)}`, text, {
191
+ channelType: nativeChannelTypeMapping.USER,
192
+ customType: 'PlainText',
193
+ messageType: 1,
194
+ }),
195
+ ),
196
+ );
197
+ response.forEach((result, index) => {
198
+ const uid = to[index];
199
+ if (result.status === 'rejected') {
200
+ logger.error(
201
+ LogSource.Events,
202
+ 'CUSTOM_EVENTS',
203
+ `Failed to publish to user ${uid}:`,
204
+ result.reason,
205
+ );
206
+ }
207
+ });
208
+ // for (const uid of to) {
209
+ // const adjustedUID = adjustUID(uid);
210
+ // await rtmEngine.publish(`${adjustedUID}`, text, {
211
+ // channelType: 3, // user
212
+ // });
213
+ // }
175
214
  } catch (error) {
176
215
  logger.error(
177
216
  LogSource.Events,
@@ -192,13 +231,31 @@ class Events {
192
231
  'updating channel attributes',
193
232
  );
194
233
  try {
195
- const rtmEngine: RtmEngine = RTMEngine.getInstance().engine;
234
+ // Validate if rtmengine is ready
235
+ if (!RTMEngine.getInstance().isEngineReady) {
236
+ throw new Error('RTM Engine is not ready. Call setLocalUID() first.');
237
+ }
238
+ const rtmEngine: RTMClient = RTMEngine.getInstance().engine;
239
+
196
240
  const channelId = RTMEngine.getInstance().channelUid;
241
+ if (!channelId || channelId.trim() === '') {
242
+ throw new Error(
243
+ 'Channel ID is not set. Cannot send channel attributes.',
244
+ );
245
+ }
246
+
197
247
  const rtmAttribute = [{key: rtmPayload.evt, value: rtmPayload.value}];
198
- // Step 1: Call RTM API to update local attributes
199
- await rtmEngine.addOrUpdateChannelAttributes(channelId, rtmAttribute, {
200
- enableNotificationToChannelMembers: true,
201
- });
248
+ await rtmEngine.storage.setChannelMetadata(
249
+ channelId,
250
+ nativeChannelTypeMapping.MESSAGE,
251
+ {
252
+ items: rtmAttribute,
253
+ },
254
+ {
255
+ addUserId: true,
256
+ addTimeStamp: true,
257
+ },
258
+ );
202
259
  } catch (error) {
203
260
  logger.error(
204
261
  LogSource.Events,
@@ -223,7 +280,8 @@ class Events {
223
280
  on = (eventName: string, listener: EventCallback): Function => {
224
281
  try {
225
282
  if (!this._validateEvt(eventName) || !this._validateListener(listener)) {
226
- return;
283
+ // Return no-op function instead of undefined to prevent errors
284
+ return () => {};
227
285
  }
228
286
  EventUtils.addListener(eventName, listener, this.source);
229
287
  console.log('CUSTOM_EVENT_API event listener registered', eventName);
@@ -238,6 +296,8 @@ class Events {
238
296
  'Error: events.on',
239
297
  error,
240
298
  );
299
+ // Return no-op function on error to prevent undefined issues
300
+ return () => {};
241
301
  }
242
302
  };
243
303
 
@@ -253,7 +313,11 @@ class Events {
253
313
  off = (eventName?: string, listener?: EventCallback) => {
254
314
  try {
255
315
  if (listener) {
256
- if (this._validateListener(listener) && this._validateEvt(eventName)) {
316
+ if (
317
+ eventName &&
318
+ this._validateListener(listener) &&
319
+ this._validateEvt(eventName)
320
+ ) {
257
321
  // listen off an event by eventName and listener
258
322
  //@ts-ignore
259
323
  EventUtils.removeListener(eventName, listener, this.source);
@@ -295,8 +359,18 @@ class Events {
295
359
  persistLevel: PersistanceLevel = PersistanceLevel.None,
296
360
  receiver: ReceiverUid = -1,
297
361
  ) => {
298
- if (!this._validateEvt(eventName)) {
299
- return;
362
+ try {
363
+ if (!this._validateEvt(eventName)) {
364
+ return;
365
+ }
366
+ } catch (error) {
367
+ logger.error(
368
+ LogSource.Events,
369
+ 'CUSTOM_EVENTS',
370
+ 'Event validation failed',
371
+ error,
372
+ );
373
+ return; // Don't throw - just log and return
300
374
  }
301
375
 
302
376
  const persistValue = JSON.stringify({
@@ -318,6 +392,7 @@ class Events {
318
392
  await this._persist(eventName, persistValue);
319
393
  } catch (error) {
320
394
  logger.error(LogSource.Events, 'CUSTOM_EVENTS', 'persist error', error);
395
+ // don't throw - just log the error, application should continue running
321
396
  }
322
397
  }
323
398
  try {
@@ -336,9 +411,10 @@ class Events {
336
411
  logger.error(
337
412
  LogSource.Events,
338
413
  'CUSTOM_EVENTS',
339
- 'sending event failed',
414
+ `Failed to send event '${eventName}' - event lost`,
340
415
  error,
341
416
  );
417
+ // don't throw - just log the error, application should continue running
342
418
  }
343
419
  };
344
420
  }
@@ -67,7 +67,7 @@ const useEndCall = () => {
67
67
  if ($config.CHAT) {
68
68
  deleteChatUser();
69
69
  }
70
- RTMEngine.getInstance().engine.leaveChannel(rtcProps.channel);
70
+ RTMEngine.getInstance().engine.unsubscribe(rtcProps.channel);
71
71
  if (!ENABLE_AUTH) {
72
72
  // await authLogout();
73
73
  await authLogin();