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.
- package/package.json +1 -1
- package/template/android/app/build.gradle +7 -0
- package/template/bridge/rtm/web/Types.ts +183 -0
- package/template/bridge/rtm/web/index-legacy.ts +540 -0
- package/template/bridge/rtm/web/index.ts +423 -491
- package/template/defaultConfig.js +1 -1
- package/template/ios/Podfile +63 -0
- package/template/ios/podspec-patches/boost.podspec +23 -0
- package/template/package.json +4 -4
- package/template/src/components/RTMConfigure-legacy.tsx +848 -0
- package/template/src/components/RTMConfigure.tsx +672 -436
- package/template/src/rtm/RTMEngine.ts +130 -33
- package/template/src/rtm-events-api/Events.ts +106 -30
- package/template/src/utils/useEndCall.ts +1 -1
|
@@ -10,57 +10,71 @@
|
|
|
10
10
|
*********************************************
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import
|
|
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
|
-
|
|
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
|
-
|
|
38
|
+
RTMEngine._instance = new RTMEngine();
|
|
26
39
|
}
|
|
27
40
|
return RTMEngine._instance;
|
|
28
41
|
}
|
|
29
42
|
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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
|
-
|
|
42
|
-
if (
|
|
43
|
-
|
|
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
|
-
|
|
52
|
-
}
|
|
63
|
+
this.localUID = newUID;
|
|
53
64
|
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
if (!this._engine) {
|
|
66
|
+
this.createClientInstance();
|
|
67
|
+
}
|
|
56
68
|
}
|
|
57
69
|
|
|
58
70
|
setChannelId(channelID: string) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
78
|
-
|
|
79
|
-
RTMEngine._instance = null;
|
|
169
|
+
if (!this._engine) {
|
|
170
|
+
return;
|
|
80
171
|
}
|
|
81
|
-
|
|
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.
|
|
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
|
|
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:
|
|
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.
|
|
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(
|
|
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
|
|
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
|
-
|
|
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.
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
299
|
-
|
|
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
|
-
'
|
|
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.
|
|
70
|
+
RTMEngine.getInstance().engine.unsubscribe(rtcProps.channel);
|
|
71
71
|
if (!ENABLE_AUTH) {
|
|
72
72
|
// await authLogout();
|
|
73
73
|
await authLogin();
|