agora-appbuilder-core 4.1.8 → 4.1.9-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.
- 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 +3 -3
- package/template/ios/Podfile +41 -0
- package/template/package.json +4 -4
- package/template/src/atoms/TextInput.tsx +3 -0
- package/template/src/components/RTMConfigure-legacy.tsx +848 -0
- package/template/src/components/RTMConfigure.tsx +644 -426
- package/template/src/rtm/RTMEngine.ts +130 -33
- package/template/src/rtm-events-api/Events.ts +106 -30
- package/template/src/subComponents/ChatInput.tsx +72 -1
- package/template/src/subComponents/caption/useSTTAPI.tsx +2 -2
- package/template/src/subComponents/caption/utils.ts +50 -14
- package/template/src/utils/useEndCall.ts +1 -1
- package/template/src/utils/useJoinRoom.ts +0 -1
|
@@ -1,540 +1,472 @@
|
|
|
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
|
|
13
1
|
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
type
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
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],
|
|
59
75
|
]);
|
|
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
|
-
}
|
|
96
76
|
|
|
97
|
-
|
|
98
|
-
this.appId =
|
|
99
|
-
this.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
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,
|
|
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);
|
|
143
96
|
});
|
|
144
|
-
});
|
|
145
97
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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);
|
|
154
107
|
});
|
|
155
108
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
}
|
|
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);
|
|
164
118
|
});
|
|
165
119
|
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
});
|
|
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);
|
|
174
128
|
});
|
|
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
|
+
}
|
|
175
139
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
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
|
+
};
|
|
184
172
|
});
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
callerId: remoteInvitation.callerId,
|
|
190
|
-
content: remoteInvitation.content,
|
|
191
|
-
state: remoteInvitation.state,
|
|
192
|
-
channelId: remoteInvitation.channelId,
|
|
193
|
-
response: remoteInvitation.response,
|
|
173
|
+
// Map native signature to web signature
|
|
174
|
+
return this.client.storage.setUserMetadata(validatedItems, {
|
|
175
|
+
addTimeStamp: options?.addTimeStamp || true,
|
|
176
|
+
addUserId: options?.addUserId || true,
|
|
194
177
|
});
|
|
195
|
-
}
|
|
196
|
-
});
|
|
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
|
-
}
|
|
178
|
+
},
|
|
210
179
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
*/
|
|
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
|
+
});
|
|
243
198
|
/**
|
|
244
|
-
*
|
|
245
|
-
*
|
|
246
|
-
*
|
|
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"
|
|
247
208
|
*/
|
|
248
|
-
const
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
this.channelEventsMap.get('ChannelAttributesUpdated')(
|
|
255
|
-
channelAttributes,
|
|
209
|
+
const items = Object.entries(webResponse.metadata).map(
|
|
210
|
+
([key, metadataItem]) => ({
|
|
211
|
+
key: key,
|
|
212
|
+
value: metadataItem.value,
|
|
213
|
+
}),
|
|
256
214
|
);
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
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
|
-
}
|
|
215
|
+
const nativeResponse: NativeGetUserMetadataResponse = {
|
|
216
|
+
items: [...items],
|
|
217
|
+
itemCount: webResponse.totalCount,
|
|
218
|
+
userId: webResponse.userId,
|
|
219
|
+
timestamp: webResponse.timestamp,
|
|
220
|
+
};
|
|
221
|
+
return nativeResponse;
|
|
222
|
+
},
|
|
290
223
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
};
|
|
301
265
|
});
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
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
|
+
},
|
|
308
276
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
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
|
+
};
|
|
320
313
|
}
|
|
321
314
|
|
|
322
|
-
|
|
323
|
-
return
|
|
324
|
-
|
|
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
|
+
}
|
|
325
334
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
+
},
|
|
338
352
|
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
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
|
-
}
|
|
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
|
+
},
|
|
364
362
|
|
|
365
|
-
|
|
366
|
-
|
|
363
|
+
whereNow: async (userId: string) => {
|
|
364
|
+
return this.client.presence.whereNow(userId);
|
|
365
|
+
},
|
|
366
|
+
};
|
|
367
367
|
}
|
|
368
368
|
|
|
369
|
-
|
|
370
|
-
|
|
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
|
+
}
|
|
371
377
|
}
|
|
372
378
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
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
|
+
}
|
|
381
390
|
}
|
|
382
391
|
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
// console.log('!!!!formattedAttributes', formattedAttributes, key, value);
|
|
390
|
-
});
|
|
391
|
-
return this.client.setLocalUserAttributes({...formattedAttributes});
|
|
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});
|
|
392
398
|
}
|
|
393
399
|
|
|
394
|
-
async
|
|
395
|
-
|
|
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});
|
|
400
|
+
async logout() {
|
|
401
|
+
return this.client.logout();
|
|
402
402
|
}
|
|
403
403
|
|
|
404
|
-
async
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
formattedAttributes[key] = value;
|
|
414
|
-
});
|
|
415
|
-
return this.client.addOrUpdateChannelAttributes(
|
|
416
|
-
channelId,
|
|
417
|
-
{...formattedAttributes},
|
|
418
|
-
option,
|
|
419
|
-
);
|
|
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);
|
|
420
413
|
}
|
|
421
414
|
|
|
422
|
-
async
|
|
423
|
-
|
|
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();
|
|
415
|
+
async unsubscribe(channelName: string) {
|
|
416
|
+
return this.client.unsubscribe(channelName);
|
|
478
417
|
}
|
|
479
418
|
|
|
480
|
-
async
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
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
|
+
}
|
|
491
435
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
// console.log(this.remoteInvititations);
|
|
500
|
-
// console.log(remoteInvitationProps.uid);
|
|
501
|
-
return invite.accept();
|
|
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);
|
|
502
443
|
}
|
|
503
444
|
|
|
504
|
-
async
|
|
505
|
-
|
|
506
|
-
response?: string;
|
|
507
|
-
channelId: string;
|
|
508
|
-
}) {
|
|
509
|
-
return this.remoteInvititations.get(remoteInvitationProps.uid).refuse();
|
|
445
|
+
async renewToken(token: string) {
|
|
446
|
+
return this.client.renewToken(token);
|
|
510
447
|
}
|
|
511
448
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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();
|
|
519
457
|
}
|
|
458
|
+
}
|
|
520
459
|
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
460
|
+
export class RtmConfig {
|
|
461
|
+
public appId: string;
|
|
462
|
+
public userId: string;
|
|
524
463
|
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
};
|
|
464
|
+
constructor(config: {appId: string; userId: string}) {
|
|
465
|
+
this.appId = config.appId;
|
|
466
|
+
this.userId = config.userId;
|
|
539
467
|
}
|
|
540
468
|
}
|
|
469
|
+
// Factory function to create RTM client
|
|
470
|
+
export function createAgoraRtmClient(config: RtmConfig): RTMWebClient {
|
|
471
|
+
return new RTMWebClient(config.appId, config.userId);
|
|
472
|
+
}
|