@typexim/web-sdk 0.0.7 → 0.0.8-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -4
- package/dist/types/enums/sdk.d.ts +0 -1
- package/dist/types/interfaces/business.d.ts +45 -1
- package/dist/types/interfaces/sdk/api.d.ts +41 -1
- package/dist/types/modules/ChatSDK.d.ts +19 -3
- package/dist/types/modules/TabSessionCoordinator.d.ts +5 -5
- package/dist/types/utils/misc.d.ts +10 -0
- package/dist/web-sdk.esm.js +1 -1
- package/dist/web-sdk.js +1 -1
- package/dist-test/enums/business.js +139 -0
- package/dist-test/enums/index.js +2 -0
- package/dist-test/enums/sdk.js +24 -0
- package/dist-test/events.js +50 -0
- package/dist-test/index.js +26 -0
- package/dist-test/interfaces/business.js +1 -0
- package/dist-test/interfaces/index.js +3 -0
- package/dist-test/interfaces/pushMessage.js +1 -0
- package/dist-test/interfaces/sdk/api.js +1 -0
- package/dist-test/interfaces/sdk/common.js +1 -0
- package/dist-test/interfaces/sdk/index.js +2 -0
- package/dist-test/modules/Base.js +70 -0
- package/dist-test/modules/BusinessWebsocket.js +68 -0
- package/dist-test/modules/ChatSDK.js +698 -0
- package/dist-test/modules/FeedCache.js +211 -0
- package/dist-test/modules/HttpClient.js +104 -0
- package/dist-test/modules/Logger.js +73 -0
- package/dist-test/modules/PushMessageProcessor.js +437 -0
- package/dist-test/modules/ReConnectWebsocket.js +223 -0
- package/dist-test/modules/Storage.js +27 -0
- package/dist-test/modules/TabSessionCoordinator.js +289 -0
- package/dist-test/modules/Version.js +20 -0
- package/dist-test/utils/date.js +4 -0
- package/dist-test/utils/http.js +13 -0
- package/dist-test/utils/makeSign.js +79 -0
- package/dist-test/utils/message.js +146 -0
- package/dist-test/utils/misc.js +13 -0
- package/dist-test/utils/promise.js +10 -0
- package/dist-test/utils/sleep.js +3 -0
- package/dist-test/utils/websocket.js +24 -0
- package/package.json +3 -2
- package/test/chat-sdk-independent-login.test.mjs +218 -0
- package/test/{chat-sdk-restore-login.test.mjs → chat-sdk-session-restore.test.mjs} +69 -29
- package/test/tab-session-coordinator.test.mjs +30 -64
- package/dist/types/modules/SingletonLock.d.ts +0 -12
- package/test/singleton-lock.test.mjs +0 -75
|
@@ -0,0 +1,698 @@
|
|
|
1
|
+
import { Bool, HttpClientResponseCode, MessageKind, MessageOperation, MessageType, RequestDirection, RESOURCE_AUTH_DOMAINS, } from '../enums/index.js';
|
|
2
|
+
import { CHAT_SDK_EVENT } from '../events.js';
|
|
3
|
+
import { getCurrentISOString } from '../utils/date.js';
|
|
4
|
+
import { createErrorResponse, createOkResponse } from '../utils/http.js';
|
|
5
|
+
import { appendQueryParam, createDomainUrlRegex, makePositions, parseCustomMessageContent, replaceDomainUrlsInContent, stringifyCustomMessageContent, } from '../utils/message.js';
|
|
6
|
+
import Base from './Base.js';
|
|
7
|
+
import BusinessWebsocket from './BusinessWebsocket.js';
|
|
8
|
+
import FeedCache from './FeedCache.js';
|
|
9
|
+
import HttpClient from './HttpClient.js';
|
|
10
|
+
import Logger from './Logger.js';
|
|
11
|
+
import PushMessageProcessor from './PushMessageProcessor.js';
|
|
12
|
+
import Storage from './Storage.js';
|
|
13
|
+
import TabSessionCoordinator from './TabSessionCoordinator.js';
|
|
14
|
+
import Version from './Version.js';
|
|
15
|
+
const LOGIN_SESSION_TRANSFER_TIMEOUT_MS = 500;
|
|
16
|
+
export default class ChatSDK extends Base {
|
|
17
|
+
constructor(config) {
|
|
18
|
+
super();
|
|
19
|
+
Object.defineProperty(this, "context", {
|
|
20
|
+
enumerable: true,
|
|
21
|
+
configurable: true,
|
|
22
|
+
writable: true,
|
|
23
|
+
value: void 0
|
|
24
|
+
});
|
|
25
|
+
Object.defineProperty(this, "version", {
|
|
26
|
+
enumerable: true,
|
|
27
|
+
configurable: true,
|
|
28
|
+
writable: true,
|
|
29
|
+
value: void 0
|
|
30
|
+
});
|
|
31
|
+
Object.defineProperty(this, "tabSessionCoordinator", {
|
|
32
|
+
enumerable: true,
|
|
33
|
+
configurable: true,
|
|
34
|
+
writable: true,
|
|
35
|
+
value: void 0
|
|
36
|
+
});
|
|
37
|
+
Object.defineProperty(this, "restoreLoginPromise", {
|
|
38
|
+
enumerable: true,
|
|
39
|
+
configurable: true,
|
|
40
|
+
writable: true,
|
|
41
|
+
value: void 0
|
|
42
|
+
});
|
|
43
|
+
Object.defineProperty(this, "logger", {
|
|
44
|
+
enumerable: true,
|
|
45
|
+
configurable: true,
|
|
46
|
+
writable: true,
|
|
47
|
+
value: void 0
|
|
48
|
+
});
|
|
49
|
+
Object.defineProperty(this, "httpClient", {
|
|
50
|
+
enumerable: true,
|
|
51
|
+
configurable: true,
|
|
52
|
+
writable: true,
|
|
53
|
+
value: void 0
|
|
54
|
+
});
|
|
55
|
+
Object.defineProperty(this, "feedCache", {
|
|
56
|
+
enumerable: true,
|
|
57
|
+
configurable: true,
|
|
58
|
+
writable: true,
|
|
59
|
+
value: void 0
|
|
60
|
+
});
|
|
61
|
+
Object.defineProperty(this, "ws", {
|
|
62
|
+
enumerable: true,
|
|
63
|
+
configurable: true,
|
|
64
|
+
writable: true,
|
|
65
|
+
value: void 0
|
|
66
|
+
});
|
|
67
|
+
Object.defineProperty(this, "pushMessageProcessor", {
|
|
68
|
+
enumerable: true,
|
|
69
|
+
configurable: true,
|
|
70
|
+
writable: true,
|
|
71
|
+
value: void 0
|
|
72
|
+
});
|
|
73
|
+
Object.defineProperty(this, "handlePageHide", {
|
|
74
|
+
enumerable: true,
|
|
75
|
+
configurable: true,
|
|
76
|
+
writable: true,
|
|
77
|
+
value: () => {
|
|
78
|
+
this.ws.disconnect();
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
Object.defineProperty(this, "handlePageShow", {
|
|
82
|
+
enumerable: true,
|
|
83
|
+
configurable: true,
|
|
84
|
+
writable: true,
|
|
85
|
+
value: (event) => {
|
|
86
|
+
// bfcache restore: open a fresh socket if still logged in.
|
|
87
|
+
if (event.persisted && this.context.isLogin) {
|
|
88
|
+
this.ws.connect();
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
this.context = {
|
|
93
|
+
isLogin: false,
|
|
94
|
+
loginUser: null,
|
|
95
|
+
isReady: false,
|
|
96
|
+
deviceId: null,
|
|
97
|
+
};
|
|
98
|
+
this.version = new Version();
|
|
99
|
+
this.logger = new Logger();
|
|
100
|
+
this.restoreLoginPromise = null;
|
|
101
|
+
this.tabSessionCoordinator = new TabSessionCoordinator({
|
|
102
|
+
logger: this.logger,
|
|
103
|
+
getCurrentSession: () => this.getTransferableLoginSession(),
|
|
104
|
+
onLogoutBroadcast: () => {
|
|
105
|
+
this.afterLogout();
|
|
106
|
+
},
|
|
107
|
+
onLoginBroadcast: (session) => {
|
|
108
|
+
void this.applySharedLoginSession(session);
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
this.httpClient = new HttpClient({
|
|
112
|
+
host: config.httpHost,
|
|
113
|
+
context: this.context,
|
|
114
|
+
logger: this.logger,
|
|
115
|
+
developer: config.developer,
|
|
116
|
+
});
|
|
117
|
+
this.feedCache = new FeedCache();
|
|
118
|
+
this.ws = new BusinessWebsocket({
|
|
119
|
+
host: config.websocketHost,
|
|
120
|
+
context: this.context,
|
|
121
|
+
logger: this.logger,
|
|
122
|
+
});
|
|
123
|
+
this.pushMessageProcessor = new PushMessageProcessor({
|
|
124
|
+
context: this.context,
|
|
125
|
+
logger: this.logger,
|
|
126
|
+
emitter: this,
|
|
127
|
+
feedCache: this.feedCache,
|
|
128
|
+
onKickoff: () => {
|
|
129
|
+
this.afterLogout();
|
|
130
|
+
},
|
|
131
|
+
});
|
|
132
|
+
this.init();
|
|
133
|
+
}
|
|
134
|
+
init() {
|
|
135
|
+
this.loadContext();
|
|
136
|
+
this.ws.on('message', (message) => {
|
|
137
|
+
this.pushMessageProcessor.process(message);
|
|
138
|
+
});
|
|
139
|
+
this.ws.on('kickoff', () => {
|
|
140
|
+
this.afterLogout();
|
|
141
|
+
});
|
|
142
|
+
if (typeof window !== 'undefined') {
|
|
143
|
+
// Close the push socket on unload/refresh so the tab never leaves a stale connection.
|
|
144
|
+
window.addEventListener('pagehide', this.handlePageHide);
|
|
145
|
+
window.addEventListener('pageshow', this.handlePageShow);
|
|
146
|
+
}
|
|
147
|
+
setTimeout(() => {
|
|
148
|
+
void this.restoreLoginFromActiveTab();
|
|
149
|
+
}, 0);
|
|
150
|
+
}
|
|
151
|
+
getTransferableLoginSession() {
|
|
152
|
+
if (!this.context.isLogin || this.context.loginUser === null) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
deviceId: this.context.deviceId,
|
|
157
|
+
loginUser: {
|
|
158
|
+
...this.context.loginUser,
|
|
159
|
+
},
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async restoreLoginFromActiveTab() {
|
|
163
|
+
if (this.restoreLoginPromise) {
|
|
164
|
+
return this.restoreLoginPromise;
|
|
165
|
+
}
|
|
166
|
+
this.restoreLoginPromise = this.doRestoreLoginFromActiveTab();
|
|
167
|
+
try {
|
|
168
|
+
return await this.restoreLoginPromise;
|
|
169
|
+
}
|
|
170
|
+
finally {
|
|
171
|
+
this.restoreLoginPromise = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async doRestoreLoginFromActiveTab() {
|
|
175
|
+
if (this.context.isLogin) {
|
|
176
|
+
return true;
|
|
177
|
+
}
|
|
178
|
+
const session = await this.tabSessionCoordinator.requestCurrentSession(LOGIN_SESSION_TRANSFER_TIMEOUT_MS);
|
|
179
|
+
if (!session || this.context.isLogin) {
|
|
180
|
+
return this.context.isLogin;
|
|
181
|
+
}
|
|
182
|
+
return this.applySharedLoginSession(session);
|
|
183
|
+
}
|
|
184
|
+
async applySharedLoginSession(session) {
|
|
185
|
+
if (this.context.isLogin) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
this.context.isLogin = true;
|
|
189
|
+
this.context.loginUser = session.loginUser;
|
|
190
|
+
if (session.deviceId) {
|
|
191
|
+
this.context.deviceId = session.deviceId;
|
|
192
|
+
}
|
|
193
|
+
// ws_route is derived from deviceId; ensure it exists before connecting.
|
|
194
|
+
await this.requestDeviceId();
|
|
195
|
+
this.applyLoginStatus();
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
async requestDeviceId() {
|
|
199
|
+
if (this.context.deviceId) {
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
const resp = await this.httpClient.post('/device_api/device_id', {
|
|
203
|
+
platform: 'web',
|
|
204
|
+
hash_str: '',
|
|
205
|
+
}, {
|
|
206
|
+
needSign: false,
|
|
207
|
+
});
|
|
208
|
+
if (!resp.ok) {
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
this.context.deviceId = resp.data;
|
|
212
|
+
this.saveContext();
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
saveContext() {
|
|
216
|
+
const { context } = this;
|
|
217
|
+
Storage.set({
|
|
218
|
+
deviceId: context.deviceId,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
loadContext() {
|
|
222
|
+
const localData = Storage.get();
|
|
223
|
+
if (!localData || !localData.deviceId) {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
this.context.deviceId = localData.deviceId;
|
|
227
|
+
}
|
|
228
|
+
applyLoginStatus() {
|
|
229
|
+
this.version.increment();
|
|
230
|
+
this.saveContext();
|
|
231
|
+
if (this.context.isLogin) {
|
|
232
|
+
this.ws.connect();
|
|
233
|
+
this.loadFeedList();
|
|
234
|
+
this.context.isReady = true;
|
|
235
|
+
this.emit(CHAT_SDK_EVENT.SDK_READY);
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
this.ws.disconnect();
|
|
239
|
+
this.feedCache.reset();
|
|
240
|
+
this.context.isReady = false;
|
|
241
|
+
this.emit(CHAT_SDK_EVENT.SDK_NOT_READY);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
async loadFeedList() {
|
|
245
|
+
const version = this.version.get();
|
|
246
|
+
const last = {
|
|
247
|
+
refresh_at: null,
|
|
248
|
+
feed_id: null,
|
|
249
|
+
};
|
|
250
|
+
while (true) {
|
|
251
|
+
const res = await this.httpClient.get('/feed', {
|
|
252
|
+
limit: 500,
|
|
253
|
+
last_refresh_at: last.refresh_at ?? undefined,
|
|
254
|
+
last_feed_id: last.feed_id ?? undefined,
|
|
255
|
+
});
|
|
256
|
+
if (!this.version.check(version)) {
|
|
257
|
+
break;
|
|
258
|
+
}
|
|
259
|
+
// 当未查出数据时,res.data === null
|
|
260
|
+
if (res.ok && res.data !== null) {
|
|
261
|
+
last.refresh_at = res.data.refresh_at;
|
|
262
|
+
last.feed_id = res.data.last_feed_id;
|
|
263
|
+
(res.data.feed_list ?? []).forEach((feed) => {
|
|
264
|
+
this.feedCache.push(feed);
|
|
265
|
+
});
|
|
266
|
+
if (!res.data.has_more) {
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (this.version.check(version)) {
|
|
275
|
+
this.feedCache.markSyncComplete();
|
|
276
|
+
this.emit(CHAT_SDK_EVENT.FEED_LIST_UPDATED, {
|
|
277
|
+
list: this.feedCache.getAll(),
|
|
278
|
+
is_sync_completed: true,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* SDK 是否已就绪(登录成功且 WebSocket 已连接)。
|
|
284
|
+
* @returns {boolean} true 表示可用,false 表示未就绪
|
|
285
|
+
*/
|
|
286
|
+
isReady() {
|
|
287
|
+
return this.context.isReady;
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* 设置 SDK 的日志级别。
|
|
291
|
+
* @param {LogLevel} level 要设置的日志级别
|
|
292
|
+
*/
|
|
293
|
+
setLogLevel(level) {
|
|
294
|
+
this.logger.setLogLevel(level);
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* 使用user_code登录,建立会话并连接WebSocket,成功后会触发事件:SDK_READY
|
|
298
|
+
* @param req
|
|
299
|
+
* @returns
|
|
300
|
+
*/
|
|
301
|
+
async login(req) {
|
|
302
|
+
if (this.context.isLogin) {
|
|
303
|
+
return createOkResponse(null);
|
|
304
|
+
}
|
|
305
|
+
const restored = await this.restoreLoginFromActiveTab();
|
|
306
|
+
if (restored || this.context.isLogin) {
|
|
307
|
+
return createOkResponse(null);
|
|
308
|
+
}
|
|
309
|
+
await this.requestDeviceId();
|
|
310
|
+
if (!this.context.deviceId) {
|
|
311
|
+
this.logger.error('Get deviceId failed');
|
|
312
|
+
return createErrorResponse(HttpClientResponseCode.SDK_ERROR, 'Get deviceId failed');
|
|
313
|
+
}
|
|
314
|
+
const resp = await this.httpClient.post('/user/session/login', {
|
|
315
|
+
userCode: req.user_code,
|
|
316
|
+
device_id: this.context.deviceId,
|
|
317
|
+
}, {
|
|
318
|
+
needSign: false,
|
|
319
|
+
});
|
|
320
|
+
if (!resp.ok) {
|
|
321
|
+
return resp;
|
|
322
|
+
}
|
|
323
|
+
this.context.isLogin = true;
|
|
324
|
+
this.context.loginUser = {
|
|
325
|
+
userId: resp.data.id,
|
|
326
|
+
secretKey: resp.data.key,
|
|
327
|
+
sessionId: req.user_code,
|
|
328
|
+
};
|
|
329
|
+
this.applyLoginStatus();
|
|
330
|
+
const transferableSession = this.getTransferableLoginSession();
|
|
331
|
+
if (transferableSession) {
|
|
332
|
+
// Notify already-open sibling tabs that were waiting without a session.
|
|
333
|
+
this.tabSessionCoordinator.publishLogin(transferableSession);
|
|
334
|
+
}
|
|
335
|
+
return createOkResponse(null);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* 退出当前登录用户并断开WebSocket,成功后会触发事件:SDK_NOT_READY
|
|
339
|
+
* 注意,登出后user_code便会失效
|
|
340
|
+
* @returns
|
|
341
|
+
*/
|
|
342
|
+
async logout() {
|
|
343
|
+
if (!this.context.isLogin) {
|
|
344
|
+
return createOkResponse(null);
|
|
345
|
+
}
|
|
346
|
+
const resp = await this.httpClient.get('/user/logout');
|
|
347
|
+
if (!resp.ok) {
|
|
348
|
+
return resp;
|
|
349
|
+
}
|
|
350
|
+
// Notify sibling tabs to clear local login state without calling logout again.
|
|
351
|
+
this.tabSessionCoordinator.publishLogout();
|
|
352
|
+
this.afterLogout();
|
|
353
|
+
return createOkResponse(null);
|
|
354
|
+
}
|
|
355
|
+
afterLogout() {
|
|
356
|
+
if (!this.context.isLogin && !this.context.isReady) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
this.context.isLogin = false;
|
|
360
|
+
this.context.loginUser = null;
|
|
361
|
+
this.applyLoginStatus();
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* 获取当前登录用户的user_id
|
|
365
|
+
* @returns
|
|
366
|
+
*/
|
|
367
|
+
getLoginUser() {
|
|
368
|
+
if (this.context.loginUser === null) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
return this.context.loginUser.userId;
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* 创建单聊feed,如果已创建就返回已创建的feed
|
|
375
|
+
* @param user_id
|
|
376
|
+
* @returns
|
|
377
|
+
*/
|
|
378
|
+
async createSingleChatFeed(user_id) {
|
|
379
|
+
return await this.httpClient.post('/chat/single', {
|
|
380
|
+
receiver_id: user_id,
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* 删除指定feed
|
|
385
|
+
* @param feed_id
|
|
386
|
+
* @returns
|
|
387
|
+
*/
|
|
388
|
+
async deleteFeed(feed_id) {
|
|
389
|
+
return await this.httpClient.post('/feed/delete', {
|
|
390
|
+
feed_id_list: [feed_id],
|
|
391
|
+
delete_peer: true,
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* 获取当前用户的 Feed 列表
|
|
396
|
+
* @returns
|
|
397
|
+
*/
|
|
398
|
+
async getFeedList() {
|
|
399
|
+
return createOkResponse({
|
|
400
|
+
list: this.feedCache.getAll(),
|
|
401
|
+
is_sync_completed: this.feedCache.isSyncCompleted(),
|
|
402
|
+
});
|
|
403
|
+
}
|
|
404
|
+
/**
|
|
405
|
+
* 根据 Feed ID 获取详情
|
|
406
|
+
* @param feed_id
|
|
407
|
+
* @returns
|
|
408
|
+
*/
|
|
409
|
+
async getFeedDetail(feed_id) {
|
|
410
|
+
const feed = await this.feedCache.waitFor(feed_id);
|
|
411
|
+
return createOkResponse(feed);
|
|
412
|
+
}
|
|
413
|
+
/**
|
|
414
|
+
* 根据 Feed ID 获取Feed的成员列表
|
|
415
|
+
* @param feed_id
|
|
416
|
+
* @returns
|
|
417
|
+
*/
|
|
418
|
+
async getFeedMemberList(feed_id) {
|
|
419
|
+
const feed = await this.feedCache.get(feed_id);
|
|
420
|
+
if (feed !== null && feed.chat_member.length > 0) {
|
|
421
|
+
return await this.httpClient.post('/chat/chatter_info', {
|
|
422
|
+
chatters: {
|
|
423
|
+
[feed.chat_id]: feed.chat_member,
|
|
424
|
+
},
|
|
425
|
+
});
|
|
426
|
+
}
|
|
427
|
+
return createOkResponse([]);
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* 根据 user_ids 或 uids 批量获取用户信息
|
|
431
|
+
* @param params
|
|
432
|
+
* @returns
|
|
433
|
+
*/
|
|
434
|
+
async getUserByUids(params) {
|
|
435
|
+
const resp = await this.httpClient.post('/user/users_info', params);
|
|
436
|
+
if (!resp.ok) {
|
|
437
|
+
return resp;
|
|
438
|
+
}
|
|
439
|
+
return createOkResponse(resp.data.map((user) => ({ id: user.id })));
|
|
440
|
+
}
|
|
441
|
+
getDomain(messages) {
|
|
442
|
+
const [domain, testDomain] = RESOURCE_AUTH_DOMAINS;
|
|
443
|
+
const regex = createDomainUrlRegex(domain);
|
|
444
|
+
const testRegex = createDomainUrlRegex(testDomain);
|
|
445
|
+
for (const item of messages) {
|
|
446
|
+
if (regex.test(item.content)) {
|
|
447
|
+
return domain;
|
|
448
|
+
}
|
|
449
|
+
if (testRegex.test(item.content)) {
|
|
450
|
+
return testDomain;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* 检测链接域名并为需要鉴权的域名添加x-sid参数
|
|
456
|
+
* @param messages
|
|
457
|
+
*/
|
|
458
|
+
appendXsid(messages) {
|
|
459
|
+
if (!this.context.loginUser?.sessionId) {
|
|
460
|
+
return messages;
|
|
461
|
+
}
|
|
462
|
+
const sessionId = this.context.loginUser.sessionId;
|
|
463
|
+
const domain = this.getDomain(messages);
|
|
464
|
+
if (!domain) {
|
|
465
|
+
return messages;
|
|
466
|
+
}
|
|
467
|
+
return messages.map((item) => {
|
|
468
|
+
const content = replaceDomainUrlsInContent(item.content, domain, (url) => appendQueryParam(url, 'xsid', sessionId));
|
|
469
|
+
return {
|
|
470
|
+
...item,
|
|
471
|
+
content,
|
|
472
|
+
};
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* 标记Feed的所有消息为已读
|
|
477
|
+
* @param feedId
|
|
478
|
+
* @returns
|
|
479
|
+
*/
|
|
480
|
+
async markFeedMessageAsRead(feedId) {
|
|
481
|
+
const feed = this.feedCache.get(feedId);
|
|
482
|
+
if (!feed) {
|
|
483
|
+
return createOkResponse(false);
|
|
484
|
+
}
|
|
485
|
+
const resp = await this.httpClient.post('/feed/update', {
|
|
486
|
+
id: feed.id,
|
|
487
|
+
mark_unread: Bool.yes,
|
|
488
|
+
});
|
|
489
|
+
if (!resp.ok) {
|
|
490
|
+
return createOkResponse(false);
|
|
491
|
+
}
|
|
492
|
+
const res = await this.httpClient.post('/message/read', {
|
|
493
|
+
chat_id: feed.chat_id,
|
|
494
|
+
thread_id: feed.thread_id,
|
|
495
|
+
});
|
|
496
|
+
if (!res.ok) {
|
|
497
|
+
return createOkResponse(false);
|
|
498
|
+
}
|
|
499
|
+
return createOkResponse(true);
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* 获取Feed的消息
|
|
503
|
+
* @param feedId
|
|
504
|
+
* @param messageIds
|
|
505
|
+
* @returns
|
|
506
|
+
*/
|
|
507
|
+
async getMessagesByIds(feedId, messageIds) {
|
|
508
|
+
const feed = this.feedCache.get(feedId);
|
|
509
|
+
if (!feed || !Array.isArray(messageIds) || messageIds.length === 0) {
|
|
510
|
+
return createOkResponse([]);
|
|
511
|
+
}
|
|
512
|
+
const res = await this.httpClient.post('/message/get_by_msgid', {
|
|
513
|
+
chat_msg_id_map: { [feed.chat_id]: messageIds },
|
|
514
|
+
});
|
|
515
|
+
if (res.ok) {
|
|
516
|
+
const list = this.appendXsid(res.data[feed.chat_id]);
|
|
517
|
+
return createOkResponse(list);
|
|
518
|
+
}
|
|
519
|
+
return createOkResponse([]);
|
|
520
|
+
}
|
|
521
|
+
/**
|
|
522
|
+
* 获取某个 Feed 的消息列表
|
|
523
|
+
* @param req
|
|
524
|
+
* @returns
|
|
525
|
+
*/
|
|
526
|
+
async getFeedMessageList(req) {
|
|
527
|
+
const feedResp = await this.getFeedDetail(req.feed_id);
|
|
528
|
+
if (feedResp.ok && feedResp.data) {
|
|
529
|
+
const feed = feedResp.data;
|
|
530
|
+
const { chat_id, first_position, last_message_position } = feed;
|
|
531
|
+
const { positions, nextPosition, hasMore } = makePositions({
|
|
532
|
+
maxPosition: last_message_position,
|
|
533
|
+
minPosition: first_position,
|
|
534
|
+
startPosition: req.message_position,
|
|
535
|
+
limit: req.limit,
|
|
536
|
+
direction: req.direction ?? RequestDirection.up,
|
|
537
|
+
});
|
|
538
|
+
if (positions.length > 0) {
|
|
539
|
+
const res = await this.httpClient.post('/message/get_by_position', {
|
|
540
|
+
chat_position: {
|
|
541
|
+
[chat_id]: positions,
|
|
542
|
+
},
|
|
543
|
+
}, {
|
|
544
|
+
needSign: false,
|
|
545
|
+
});
|
|
546
|
+
if (res.ok && res.data[chat_id]) {
|
|
547
|
+
// 因为结果是position倒序,所以要reverse
|
|
548
|
+
const list = this.appendXsid(res.data[chat_id].reverse());
|
|
549
|
+
// TODO将所有消息里的域名图片都拼接上x-sid
|
|
550
|
+
return createOkResponse({
|
|
551
|
+
has_more: hasMore,
|
|
552
|
+
list: list,
|
|
553
|
+
next_message_position: nextPosition,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
return createOkResponse({
|
|
559
|
+
has_more: false,
|
|
560
|
+
list: [],
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* 本地创建一条 Feed 自定义消息
|
|
565
|
+
* @param req
|
|
566
|
+
* @returns
|
|
567
|
+
*/
|
|
568
|
+
createCustomMessage(req) {
|
|
569
|
+
if (this.context.loginUser === null) {
|
|
570
|
+
return createErrorResponse(HttpClientResponseCode.SDK_ERROR, 'not login');
|
|
571
|
+
}
|
|
572
|
+
const feed = this.feedCache.get(req.feed_id);
|
|
573
|
+
if (feed === null) {
|
|
574
|
+
return createErrorResponse(HttpClientResponseCode.SDK_ERROR, 'not found feed');
|
|
575
|
+
}
|
|
576
|
+
const currentIsoString = getCurrentISOString();
|
|
577
|
+
const message = {
|
|
578
|
+
id: '',
|
|
579
|
+
chat_id: feed.chat_id,
|
|
580
|
+
thread_id: feed.thread_id,
|
|
581
|
+
position: feed.last_message_position,
|
|
582
|
+
sender_id: this.context.loginUser.userId,
|
|
583
|
+
message_kind_id: MessageKind.text,
|
|
584
|
+
message_type_id: MessageType.custom,
|
|
585
|
+
send_at: currentIsoString,
|
|
586
|
+
edited_at: currentIsoString,
|
|
587
|
+
reader_ids: [],
|
|
588
|
+
unreader_ids: [],
|
|
589
|
+
hide_ids: [],
|
|
590
|
+
operation: MessageOperation.normal,
|
|
591
|
+
operation_user_id: '0',
|
|
592
|
+
content: stringifyCustomMessageContent({
|
|
593
|
+
client_msg_id: req.client_msg_id,
|
|
594
|
+
content: req.content,
|
|
595
|
+
}),
|
|
596
|
+
reactions: [],
|
|
597
|
+
};
|
|
598
|
+
return createOkResponse(message);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* 发送一条消息到指定 Feed。
|
|
602
|
+
*/
|
|
603
|
+
async sendMessageToFeed(message) {
|
|
604
|
+
if (message.message_type_id === MessageType.custom) {
|
|
605
|
+
const content = parseCustomMessageContent(message.content);
|
|
606
|
+
return this.httpClient.post('/message/send_custom', {
|
|
607
|
+
chat_id: message.chat_id,
|
|
608
|
+
thread_id: message.thread_id,
|
|
609
|
+
client_id: content.client_msg_id,
|
|
610
|
+
content: content.content == null ? undefined : JSON.stringify(content.content),
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
return createErrorResponse(HttpClientResponseCode.SDK_ERROR, 'not support');
|
|
614
|
+
}
|
|
615
|
+
/**
|
|
616
|
+
* 发送消息已读回执
|
|
617
|
+
* @param req
|
|
618
|
+
* @returns
|
|
619
|
+
*/
|
|
620
|
+
async sendMessageReadReceipt(req) {
|
|
621
|
+
const feed = this.feedCache.get(req.feed_id);
|
|
622
|
+
if (feed === null) {
|
|
623
|
+
return createErrorResponse(HttpClientResponseCode.SDK_ERROR, 'not found feed');
|
|
624
|
+
}
|
|
625
|
+
return await this.httpClient.post('/message/read', {
|
|
626
|
+
chat_id: feed.chat_id,
|
|
627
|
+
message_ids: req.message_ids,
|
|
628
|
+
});
|
|
629
|
+
}
|
|
630
|
+
/**
|
|
631
|
+
* 加入直播间
|
|
632
|
+
* @param {string} room_id 直播间id
|
|
633
|
+
* @returns {Promise<HttpClientResponse>} 请求结果
|
|
634
|
+
*/
|
|
635
|
+
async joinRoom(room_id) {
|
|
636
|
+
return await this.httpClient.post('/liveshow/join', {
|
|
637
|
+
room_id,
|
|
638
|
+
});
|
|
639
|
+
}
|
|
640
|
+
/**
|
|
641
|
+
* 设置 Feed 是否静言
|
|
642
|
+
* @param feedId Feed id
|
|
643
|
+
* @param isMute 是否静言
|
|
644
|
+
* @returns
|
|
645
|
+
*/
|
|
646
|
+
async setMute(params) {
|
|
647
|
+
const res = await this.httpClient.post('/feed/update', {
|
|
648
|
+
id: params.feedId,
|
|
649
|
+
is_mute: params.isMute ? Bool.yes : Bool.no,
|
|
650
|
+
});
|
|
651
|
+
if (res.ok) {
|
|
652
|
+
return res.data;
|
|
653
|
+
}
|
|
654
|
+
return null;
|
|
655
|
+
}
|
|
656
|
+
/**
|
|
657
|
+
* 离开直播间
|
|
658
|
+
* @param {string} room_id 直播间id
|
|
659
|
+
* @returns {Promise<HttpClientResponse>} 请求结果
|
|
660
|
+
*/
|
|
661
|
+
async leaveRoom(room_id) {
|
|
662
|
+
return await this.httpClient.post('/liveshow/leave', {
|
|
663
|
+
room_id,
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
/**
|
|
667
|
+
* 获取已加入的直播间
|
|
668
|
+
* @returns {Promise<HttpClientResponse>} 请求结果
|
|
669
|
+
*/
|
|
670
|
+
async getJoinedRooms() {
|
|
671
|
+
return await this.httpClient.get('/liveshow/joined_rooms');
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* 获取直播间的最近50条消息
|
|
675
|
+
* @param room_id 直播间id
|
|
676
|
+
* @returns
|
|
677
|
+
*/
|
|
678
|
+
async getRoomRecentMessageList(room_id) {
|
|
679
|
+
const resp = await this.httpClient.get('/liveshow/get_recent_message', {
|
|
680
|
+
room_id,
|
|
681
|
+
});
|
|
682
|
+
if (!resp.ok) {
|
|
683
|
+
return resp;
|
|
684
|
+
}
|
|
685
|
+
return createOkResponse(resp.data);
|
|
686
|
+
}
|
|
687
|
+
/**
|
|
688
|
+
* 向直播间发送一条消息。
|
|
689
|
+
* @param req
|
|
690
|
+
* @returns
|
|
691
|
+
*/
|
|
692
|
+
async sendMessageToRoom(req) {
|
|
693
|
+
return await this.httpClient.post('/liveshow/send_message', {
|
|
694
|
+
room_id: req.room_id,
|
|
695
|
+
content: req.content,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}
|