@typexim/web-sdk 0.0.6 → 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.
Files changed (45) hide show
  1. package/README.md +10 -3
  2. package/dist/types/enums/sdk.d.ts +0 -1
  3. package/dist/types/interfaces/business.d.ts +45 -1
  4. package/dist/types/interfaces/sdk/api.d.ts +41 -1
  5. package/dist/types/modules/ChatSDK.d.ts +24 -3
  6. package/dist/types/modules/TabSessionCoordinator.d.ts +5 -5
  7. package/dist/types/utils/misc.d.ts +10 -0
  8. package/dist/web-sdk.esm.js +1 -1
  9. package/dist/web-sdk.js +1 -1
  10. package/dist-test/enums/business.js +139 -0
  11. package/dist-test/enums/index.js +2 -0
  12. package/dist-test/enums/sdk.js +24 -0
  13. package/dist-test/events.js +50 -0
  14. package/dist-test/index.js +26 -0
  15. package/dist-test/interfaces/business.js +1 -0
  16. package/dist-test/interfaces/index.js +3 -0
  17. package/dist-test/interfaces/pushMessage.js +1 -0
  18. package/dist-test/interfaces/sdk/api.js +1 -0
  19. package/dist-test/interfaces/sdk/common.js +1 -0
  20. package/dist-test/interfaces/sdk/index.js +2 -0
  21. package/dist-test/modules/Base.js +70 -0
  22. package/dist-test/modules/BusinessWebsocket.js +68 -0
  23. package/dist-test/modules/ChatSDK.js +698 -0
  24. package/dist-test/modules/FeedCache.js +211 -0
  25. package/dist-test/modules/HttpClient.js +104 -0
  26. package/dist-test/modules/Logger.js +73 -0
  27. package/dist-test/modules/PushMessageProcessor.js +437 -0
  28. package/dist-test/modules/ReConnectWebsocket.js +223 -0
  29. package/dist-test/modules/Storage.js +27 -0
  30. package/dist-test/modules/TabSessionCoordinator.js +289 -0
  31. package/dist-test/modules/Version.js +20 -0
  32. package/dist-test/utils/date.js +4 -0
  33. package/dist-test/utils/http.js +13 -0
  34. package/dist-test/utils/makeSign.js +79 -0
  35. package/dist-test/utils/message.js +146 -0
  36. package/dist-test/utils/misc.js +13 -0
  37. package/dist-test/utils/promise.js +10 -0
  38. package/dist-test/utils/sleep.js +3 -0
  39. package/dist-test/utils/websocket.js +24 -0
  40. package/package.json +3 -2
  41. package/test/chat-sdk-independent-login.test.mjs +218 -0
  42. package/test/{chat-sdk-restore-login.test.mjs → chat-sdk-session-restore.test.mjs} +69 -29
  43. package/test/tab-session-coordinator.test.mjs +30 -64
  44. package/dist/types/modules/SingletonLock.d.ts +0 -12
  45. 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
+ }