@typexim/web-sdk 0.0.3 → 0.0.4-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 +60 -0
- package/dist/web-sdk.esm.js +1 -1525
- package/dist/web-sdk.js +1 -1532
- package/package.json +1 -1
- package/rollup.config.js +1 -1
package/dist/web-sdk.esm.js
CHANGED
|
@@ -1,1525 +1 @@
|
|
|
1
|
-
import Cookies from 'js-cookie';
|
|
2
|
-
import axios from 'axios';
|
|
3
|
-
import CryptoJS from 'crypto-js';
|
|
4
|
-
|
|
5
|
-
var Boolean;
|
|
6
|
-
(function (Boolean) {
|
|
7
|
-
Boolean[Boolean["yes"] = 1] = "yes";
|
|
8
|
-
Boolean[Boolean["no"] = 0] = "no";
|
|
9
|
-
})(Boolean || (Boolean = {}));
|
|
10
|
-
var ApiResponseCode;
|
|
11
|
-
(function (ApiResponseCode) {
|
|
12
|
-
ApiResponseCode[ApiResponseCode["SDK_ERROR"] = -1e4] = "SDK_ERROR";
|
|
13
|
-
ApiResponseCode[ApiResponseCode["NETWORK_ERROR"] = -9999] = "NETWORK_ERROR";
|
|
14
|
-
ApiResponseCode[ApiResponseCode["MAKE_SIGN_FAIL"] = -9998] = "MAKE_SIGN_FAIL";
|
|
15
|
-
ApiResponseCode[ApiResponseCode["REQUEST_SINGLETON_LOCK_FAIL"] = -9997] = "REQUEST_SINGLETON_LOCK_FAIL";
|
|
16
|
-
ApiResponseCode[ApiResponseCode["PARAMS_INVALID"] = -9996] = "PARAMS_INVALID";
|
|
17
|
-
ApiResponseCode[ApiResponseCode["SUCCESS"] = 0] = "SUCCESS";
|
|
18
|
-
})(ApiResponseCode || (ApiResponseCode = {}));
|
|
19
|
-
var PushMessageType;
|
|
20
|
-
(function (PushMessageType) {
|
|
21
|
-
PushMessageType[PushMessageType["CREATE_FEED"] = 0] = "CREATE_FEED";
|
|
22
|
-
PushMessageType[PushMessageType["UPDATE_FEED"] = 1] = "UPDATE_FEED";
|
|
23
|
-
PushMessageType[PushMessageType["ADD_MESSAGE"] = 2] = "ADD_MESSAGE";
|
|
24
|
-
PushMessageType[PushMessageType["UPDATE_MESSAGE"] = 3] = "UPDATE_MESSAGE";
|
|
25
|
-
PushMessageType[PushMessageType["UPDATE_USER_DATA"] = 4] = "UPDATE_USER_DATA";
|
|
26
|
-
PushMessageType[PushMessageType["USER_LOGIN"] = 5] = "USER_LOGIN";
|
|
27
|
-
PushMessageType[PushMessageType["READ_MESSAGE"] = 6] = "READ_MESSAGE";
|
|
28
|
-
PushMessageType[PushMessageType["PONG_MESSAGE"] = 8] = "PONG_MESSAGE";
|
|
29
|
-
PushMessageType[PushMessageType["DELETE_MESSAGE"] = 9] = "DELETE_MESSAGE";
|
|
30
|
-
PushMessageType[PushMessageType["UPDATE_GROUP"] = 12] = "UPDATE_GROUP";
|
|
31
|
-
PushMessageType[PushMessageType["DELETE_GROUP"] = 13] = "DELETE_GROUP";
|
|
32
|
-
PushMessageType[PushMessageType["UPDATE_LOGIN_USER"] = 17] = "UPDATE_LOGIN_USER";
|
|
33
|
-
// ADD_MESSAGE_REACTION = 18,
|
|
34
|
-
PushMessageType[PushMessageType["UPDATE_MESSAGE_REACTION"] = 19] = "UPDATE_MESSAGE_REACTION";
|
|
35
|
-
// DEL_MESSAGE_REACTION = 20,
|
|
36
|
-
PushMessageType[PushMessageType["ADD_GROUP_MEMBER"] = 21] = "ADD_GROUP_MEMBER";
|
|
37
|
-
PushMessageType[PushMessageType["DEL_GROUP_MEMBER"] = 22] = "DEL_GROUP_MEMBER";
|
|
38
|
-
PushMessageType[PushMessageType["ADD_CONTACTS"] = 23] = "ADD_CONTACTS";
|
|
39
|
-
PushMessageType[PushMessageType["UPDATE_CONTACTS"] = 24] = "UPDATE_CONTACTS";
|
|
40
|
-
PushMessageType[PushMessageType["DEL_CONTACTS"] = 25] = "DEL_CONTACTS";
|
|
41
|
-
PushMessageType[PushMessageType["BLOCK_USER"] = 26] = "BLOCK_USER";
|
|
42
|
-
PushMessageType[PushMessageType["DEVICE_CODE"] = 27] = "DEVICE_CODE";
|
|
43
|
-
PushMessageType[PushMessageType["MSG_OPEN_CLOCK"] = 28] = "MSG_OPEN_CLOCK";
|
|
44
|
-
PushMessageType[PushMessageType["DELETE_SECRET_CHAT"] = 29] = "DELETE_SECRET_CHAT";
|
|
45
|
-
PushMessageType[PushMessageType["DELETE_FEED"] = 30] = "DELETE_FEED";
|
|
46
|
-
PushMessageType[PushMessageType["FAVORITE_UPDATE"] = 31] = "FAVORITE_UPDATE";
|
|
47
|
-
PushMessageType[PushMessageType["STICKERS_UPDATE"] = 32] = "STICKERS_UPDATE";
|
|
48
|
-
PushMessageType[PushMessageType["CUSTOM_STICKERS_UPDATE"] = 33] = "CUSTOM_STICKERS_UPDATE";
|
|
49
|
-
PushMessageType[PushMessageType["MANAGE_DEVICE_UPDATE"] = 34] = "MANAGE_DEVICE_UPDATE";
|
|
50
|
-
PushMessageType[PushMessageType["MASS_MESSAGE_UPDATE"] = 35] = "MASS_MESSAGE_UPDATE";
|
|
51
|
-
PushMessageType[PushMessageType["CHAT_WALLPAPER_DELETE"] = 36] = "CHAT_WALLPAPER_DELETE";
|
|
52
|
-
PushMessageType[PushMessageType["CHAT_WALLPAPER_ADD"] = 37] = "CHAT_WALLPAPER_ADD";
|
|
53
|
-
PushMessageType[PushMessageType["CONTACT_REQUEST"] = 40] = "CONTACT_REQUEST";
|
|
54
|
-
PushMessageType[PushMessageType["USER_PREFERENCE_CHANGE"] = 38] = "USER_PREFERENCE_CHANGE";
|
|
55
|
-
// Chat_PREFERENCE_CHANGE = 39,
|
|
56
|
-
PushMessageType[PushMessageType["UPDATE_GROUP_PIN"] = 41] = "UPDATE_GROUP_PIN";
|
|
57
|
-
PushMessageType[PushMessageType["RTC_CANCEL"] = 46] = "RTC_CANCEL";
|
|
58
|
-
PushMessageType[PushMessageType["ADD_FEED_FOLDER"] = 43] = "ADD_FEED_FOLDER";
|
|
59
|
-
PushMessageType[PushMessageType["UPDATE_FEED_FOLDER"] = 44] = "UPDATE_FEED_FOLDER";
|
|
60
|
-
PushMessageType[PushMessageType["UPDATE_FOLDER_FEED_LIST"] = 47] = "UPDATE_FOLDER_FEED_LIST";
|
|
61
|
-
PushMessageType[PushMessageType["DELETE_FEED_FOLDER"] = 45] = "DELETE_FEED_FOLDER";
|
|
62
|
-
PushMessageType[PushMessageType["UPDATE_CHATTER"] = 42] = "UPDATE_CHATTER";
|
|
63
|
-
PushMessageType[PushMessageType["PAPER_KEY"] = 48] = "PAPER_KEY";
|
|
64
|
-
PushMessageType[PushMessageType["PAPER_KEY_LOG"] = 49] = "PAPER_KEY_LOG";
|
|
65
|
-
PushMessageType[PushMessageType["USER_LOGIN_SCAN"] = 50] = "USER_LOGIN_SCAN";
|
|
66
|
-
PushMessageType[PushMessageType["RTC_RINGING"] = 51] = "RTC_RINGING";
|
|
67
|
-
PushMessageType[PushMessageType["ON_UPDATE_PRIVATE_SETTING"] = 52] = "ON_UPDATE_PRIVATE_SETTING";
|
|
68
|
-
PushMessageType[PushMessageType["ON_DELETE_PRIVATE_SETTING"] = 53] = "ON_DELETE_PRIVATE_SETTING";
|
|
69
|
-
PushMessageType[PushMessageType["ON_UPDATE_LOGIN_PRIVATE_SETTING"] = 54] = "ON_UPDATE_LOGIN_PRIVATE_SETTING";
|
|
70
|
-
PushMessageType[PushMessageType["PUSH_LIVE_MESSAGE"] = 56] = "PUSH_LIVE_MESSAGE";
|
|
71
|
-
PushMessageType[PushMessageType["BIG_PACKAGE_PUSH"] = 126] = "BIG_PACKAGE_PUSH";
|
|
72
|
-
PushMessageType[PushMessageType["KICK_OUT_DEVICE"] = 127] = "KICK_OUT_DEVICE";
|
|
73
|
-
PushMessageType[PushMessageType["CLEAR_HISTORY"] = 128] = "CLEAR_HISTORY";
|
|
74
|
-
PushMessageType[PushMessageType["CLEAR_DATA"] = 255] = "CLEAR_DATA";
|
|
75
|
-
})(PushMessageType || (PushMessageType = {}));
|
|
76
|
-
// 聊天类型
|
|
77
|
-
var ChatType;
|
|
78
|
-
(function (ChatType) {
|
|
79
|
-
/**
|
|
80
|
-
* 单聊
|
|
81
|
-
*/
|
|
82
|
-
ChatType[ChatType["singleChat"] = 1] = "singleChat";
|
|
83
|
-
/**
|
|
84
|
-
* 群聊
|
|
85
|
-
*/
|
|
86
|
-
ChatType[ChatType["groupChat"] = 2] = "groupChat";
|
|
87
|
-
})(ChatType || (ChatType = {}));
|
|
88
|
-
// 系统消息、普通消息
|
|
89
|
-
var MessageKind;
|
|
90
|
-
(function (MessageKind) {
|
|
91
|
-
MessageKind[MessageKind["text"] = 0] = "text";
|
|
92
|
-
MessageKind[MessageKind["system"] = 1] = "system";
|
|
93
|
-
})(MessageKind || (MessageKind = {}));
|
|
94
|
-
// 普通消息
|
|
95
|
-
var MessageType;
|
|
96
|
-
(function (MessageType) {
|
|
97
|
-
MessageType[MessageType["text"] = 0] = "text";
|
|
98
|
-
MessageType[MessageType["image"] = 1] = "image";
|
|
99
|
-
MessageType[MessageType["card"] = 2] = "card";
|
|
100
|
-
MessageType[MessageType["audio"] = 3] = "audio";
|
|
101
|
-
MessageType[MessageType["video"] = 4] = "video";
|
|
102
|
-
MessageType[MessageType["file"] = 5] = "file";
|
|
103
|
-
MessageType[MessageType["mergeForward"] = 6] = "mergeForward";
|
|
104
|
-
MessageType[MessageType["sticker"] = 7] = "sticker";
|
|
105
|
-
MessageType[MessageType["richText"] = 8] = "richText";
|
|
106
|
-
MessageType[MessageType["custom"] = 9] = "custom";
|
|
107
|
-
})(MessageType || (MessageType = {}));
|
|
108
|
-
// 消息状态
|
|
109
|
-
var MessageSendStatus;
|
|
110
|
-
(function (MessageSendStatus) {
|
|
111
|
-
MessageSendStatus[MessageSendStatus["success"] = 0] = "success";
|
|
112
|
-
MessageSendStatus[MessageSendStatus["sending"] = 1] = "sending";
|
|
113
|
-
MessageSendStatus[MessageSendStatus["failed"] = 2] = "failed";
|
|
114
|
-
})(MessageSendStatus || (MessageSendStatus = {}));
|
|
115
|
-
/** 群成员角色 */
|
|
116
|
-
var GroupRole;
|
|
117
|
-
(function (GroupRole) {
|
|
118
|
-
GroupRole[GroupRole["Owner"] = 1] = "Owner";
|
|
119
|
-
GroupRole[GroupRole["Admin"] = 2] = "Admin";
|
|
120
|
-
GroupRole[GroupRole["Common"] = 3] = "Common";
|
|
121
|
-
GroupRole[GroupRole["Bot"] = 4] = "Bot";
|
|
122
|
-
})(GroupRole || (GroupRole = {}));
|
|
123
|
-
// 消息操作
|
|
124
|
-
var MessageOperation;
|
|
125
|
-
(function (MessageOperation) {
|
|
126
|
-
MessageOperation[MessageOperation["normal"] = 0] = "normal";
|
|
127
|
-
MessageOperation[MessageOperation["recall"] = 1] = "recall";
|
|
128
|
-
})(MessageOperation || (MessageOperation = {}));
|
|
129
|
-
// 群聊类型
|
|
130
|
-
var GroupType;
|
|
131
|
-
(function (GroupType) {
|
|
132
|
-
/** 加密群聊 */
|
|
133
|
-
GroupType[GroupType["private"] = 1] = "private";
|
|
134
|
-
/** 超级群聊 */
|
|
135
|
-
GroupType[GroupType["public"] = 0] = "public";
|
|
136
|
-
/** 匿名群聊,新增群聊类型 */
|
|
137
|
-
GroupType[GroupType["anonymous"] = 2] = "anonymous";
|
|
138
|
-
})(GroupType || (GroupType = {}));
|
|
139
|
-
|
|
140
|
-
var LogLevel;
|
|
141
|
-
(function (LogLevel) {
|
|
142
|
-
LogLevel["error"] = "ERROR";
|
|
143
|
-
LogLevel["warning"] = "WARNING";
|
|
144
|
-
LogLevel["info"] = "INFO";
|
|
145
|
-
})(LogLevel || (LogLevel = {}));
|
|
146
|
-
|
|
147
|
-
var index$1 = /*#__PURE__*/Object.freeze({
|
|
148
|
-
__proto__: null,
|
|
149
|
-
get ApiResponseCode () { return ApiResponseCode; },
|
|
150
|
-
get Boolean () { return Boolean; },
|
|
151
|
-
get ChatType () { return ChatType; },
|
|
152
|
-
get GroupRole () { return GroupRole; },
|
|
153
|
-
get GroupType () { return GroupType; },
|
|
154
|
-
get LogLevel () { return LogLevel; },
|
|
155
|
-
get MessageKind () { return MessageKind; },
|
|
156
|
-
get MessageOperation () { return MessageOperation; },
|
|
157
|
-
get MessageSendStatus () { return MessageSendStatus; },
|
|
158
|
-
get MessageType () { return MessageType; },
|
|
159
|
-
get PushMessageType () { return PushMessageType; }
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
const CHAT_SDK_EVENT = {
|
|
163
|
-
/**
|
|
164
|
-
* sdk用户已登录,可以调用发送消息等API
|
|
165
|
-
*/
|
|
166
|
-
SDK_READY: 'sdkReady',
|
|
167
|
-
/**
|
|
168
|
-
* sdk用户已登出,无法调用发送消息等API,需要重新登录
|
|
169
|
-
*/
|
|
170
|
-
SDK_NOT_READY: 'sdkNotReady',
|
|
171
|
-
/**
|
|
172
|
-
* feed列表有更新
|
|
173
|
-
*/
|
|
174
|
-
FEED_LIST_UPDATED: 'feedListUpdated',
|
|
175
|
-
/**
|
|
176
|
-
* sdk收到新message
|
|
177
|
-
*/
|
|
178
|
-
MESSAGE_RECEIVED: 'messageReceived',
|
|
179
|
-
/**
|
|
180
|
-
* message有更新
|
|
181
|
-
*/
|
|
182
|
-
MESSAGE_UPDATED: 'messageUpdated',
|
|
183
|
-
/**
|
|
184
|
-
* sdk收到message已读回执
|
|
185
|
-
*/
|
|
186
|
-
MESSAGE_RECEIPT_RECEIVED: 'messageReceiptReceived',
|
|
187
|
-
/**
|
|
188
|
-
* sdk收到直播间message
|
|
189
|
-
*/
|
|
190
|
-
ROOM_MESSAGE_RECEIVED: 'roomMessageReceived',
|
|
191
|
-
};
|
|
192
|
-
|
|
193
|
-
var events = /*#__PURE__*/Object.freeze({
|
|
194
|
-
__proto__: null,
|
|
195
|
-
CHAT_SDK_EVENT: CHAT_SDK_EVENT
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
var index = /*#__PURE__*/Object.freeze({
|
|
199
|
-
__proto__: null
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
function createErrorResponse(code, msg) {
|
|
203
|
-
return {
|
|
204
|
-
ok: false,
|
|
205
|
-
code,
|
|
206
|
-
msg,
|
|
207
|
-
};
|
|
208
|
-
}
|
|
209
|
-
function createOkResponse(data) {
|
|
210
|
-
return {
|
|
211
|
-
ok: true,
|
|
212
|
-
data,
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
const jsonParse = (content) => {
|
|
217
|
-
try {
|
|
218
|
-
if (content == null ||
|
|
219
|
-
typeof content !== 'string' ||
|
|
220
|
-
content.length === 0) {
|
|
221
|
-
return null;
|
|
222
|
-
}
|
|
223
|
-
return JSON.parse(content);
|
|
224
|
-
}
|
|
225
|
-
catch {
|
|
226
|
-
return null;
|
|
227
|
-
}
|
|
228
|
-
};
|
|
229
|
-
|
|
230
|
-
const makePositions = (config) => {
|
|
231
|
-
const positions = [];
|
|
232
|
-
let lastPosition = config.lastPosition ?? config.maxPosition;
|
|
233
|
-
while (positions.length < config.limit &&
|
|
234
|
-
lastPosition >= config.minPosition) {
|
|
235
|
-
positions.push(lastPosition);
|
|
236
|
-
lastPosition--;
|
|
237
|
-
}
|
|
238
|
-
return positions;
|
|
239
|
-
};
|
|
240
|
-
const stringifyCustomMessageContent = (payload) => {
|
|
241
|
-
return JSON.stringify({
|
|
242
|
-
client_msg_id: payload.client_msg_id,
|
|
243
|
-
content: JSON.stringify(payload.content),
|
|
244
|
-
});
|
|
245
|
-
};
|
|
246
|
-
/**
|
|
247
|
-
* 解析message_type_id为MessageType.custom的消息内容
|
|
248
|
-
*/
|
|
249
|
-
const parseCustomMessageContent = (str) => {
|
|
250
|
-
const json = jsonParse(str);
|
|
251
|
-
if (json !== null) {
|
|
252
|
-
let parsedContent = undefined;
|
|
253
|
-
if (json.content != null) {
|
|
254
|
-
parsedContent = jsonParse(json.content) ?? undefined;
|
|
255
|
-
}
|
|
256
|
-
return {
|
|
257
|
-
client_msg_id: json.client_msg_id,
|
|
258
|
-
content: parsedContent,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
|
-
return {};
|
|
262
|
-
};
|
|
263
|
-
/**
|
|
264
|
-
* 解析message_kind_id为MessageKind.system,message_type_id为MessageType.text的消息内容
|
|
265
|
-
*/
|
|
266
|
-
const parseSystemMessageContent = (str) => {
|
|
267
|
-
const json = jsonParse(str);
|
|
268
|
-
if (json !== null) {
|
|
269
|
-
return {
|
|
270
|
-
text: json.text,
|
|
271
|
-
text_i18n: json.text_i18n,
|
|
272
|
-
};
|
|
273
|
-
}
|
|
274
|
-
return {};
|
|
275
|
-
};
|
|
276
|
-
/**
|
|
277
|
-
* 解析message_kind_id为MessageKind.text,message_type_id为MessageType.text的消息内容
|
|
278
|
-
*/
|
|
279
|
-
const parseTextMessageContent = (str) => {
|
|
280
|
-
const json = jsonParse(str);
|
|
281
|
-
if (json !== null) {
|
|
282
|
-
return {
|
|
283
|
-
text: json.text,
|
|
284
|
-
client_msg_id: json.client_msg_id,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
return {};
|
|
288
|
-
};
|
|
289
|
-
|
|
290
|
-
class Base {
|
|
291
|
-
// A Map of event names to registered handler functions.
|
|
292
|
-
all;
|
|
293
|
-
constructor(all) {
|
|
294
|
-
this.all = all || new Map();
|
|
295
|
-
}
|
|
296
|
-
on(type, handler) {
|
|
297
|
-
const handlers = this.all.get(type);
|
|
298
|
-
if (handlers) {
|
|
299
|
-
handlers.push(handler);
|
|
300
|
-
}
|
|
301
|
-
else {
|
|
302
|
-
this.all.set(type, [handler]);
|
|
303
|
-
}
|
|
304
|
-
return () => {
|
|
305
|
-
if (type === '*') {
|
|
306
|
-
this.off(type, handler);
|
|
307
|
-
}
|
|
308
|
-
else {
|
|
309
|
-
this.off(type, handler);
|
|
310
|
-
}
|
|
311
|
-
};
|
|
312
|
-
}
|
|
313
|
-
off(type, handler) {
|
|
314
|
-
const handlers = this.all.get(type);
|
|
315
|
-
if (handlers) {
|
|
316
|
-
if (handler) {
|
|
317
|
-
handlers.splice(handlers.indexOf(handler) >>> 0, 1);
|
|
318
|
-
}
|
|
319
|
-
else {
|
|
320
|
-
this.all.set(type, []);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
emit(type, evt) {
|
|
325
|
-
let handlers = this.all.get(type);
|
|
326
|
-
if (handlers) {
|
|
327
|
-
handlers.slice().map((handler) => {
|
|
328
|
-
handler(evt);
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
|
-
handlers = this.all.get('*');
|
|
332
|
-
if (handlers) {
|
|
333
|
-
handlers.slice().map((handler) => {
|
|
334
|
-
handler(type, evt);
|
|
335
|
-
});
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
once(type, handler) {
|
|
339
|
-
if (type === '*') {
|
|
340
|
-
const onceHandler = ((type, event) => {
|
|
341
|
-
handler(type, event);
|
|
342
|
-
this.off('*', onceHandler);
|
|
343
|
-
});
|
|
344
|
-
this.on(type, onceHandler);
|
|
345
|
-
}
|
|
346
|
-
else {
|
|
347
|
-
const onceHandler = ((event) => {
|
|
348
|
-
handler(event);
|
|
349
|
-
this.off(type, onceHandler);
|
|
350
|
-
});
|
|
351
|
-
this.on(type, onceHandler);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const PING_TIMEOUT_MS = 20000;
|
|
357
|
-
const PONG_TIMEOUT_MS = 10000;
|
|
358
|
-
const RE_CONNECT_TIMEOUT_MS = 5000;
|
|
359
|
-
class ReConnectWebsocket extends Base {
|
|
360
|
-
url;
|
|
361
|
-
logger;
|
|
362
|
-
ws;
|
|
363
|
-
destroyed;
|
|
364
|
-
pingTimeout;
|
|
365
|
-
pongTimeout;
|
|
366
|
-
reConnectTimeout;
|
|
367
|
-
constructor(config) {
|
|
368
|
-
super();
|
|
369
|
-
this.url = config.url;
|
|
370
|
-
this.logger = config.logger;
|
|
371
|
-
this.ws = null;
|
|
372
|
-
this.destroyed = false;
|
|
373
|
-
this.pingTimeout = null;
|
|
374
|
-
this.pongTimeout = null;
|
|
375
|
-
this.reConnectTimeout = null;
|
|
376
|
-
this.reConnect = this.reConnect.bind(this);
|
|
377
|
-
window.addEventListener('online', this.reConnect);
|
|
378
|
-
this.connect();
|
|
379
|
-
}
|
|
380
|
-
disconnect() {
|
|
381
|
-
if (this.ws) {
|
|
382
|
-
this.ws.onopen = null;
|
|
383
|
-
this.ws.onerror = null;
|
|
384
|
-
this.ws.onmessage = null;
|
|
385
|
-
this.ws.onclose = null;
|
|
386
|
-
this.ws.close();
|
|
387
|
-
this.ws = null;
|
|
388
|
-
}
|
|
389
|
-
if (this.pingTimeout !== null) {
|
|
390
|
-
clearTimeout(this.pingTimeout);
|
|
391
|
-
this.pingTimeout = null;
|
|
392
|
-
}
|
|
393
|
-
if (this.pongTimeout !== null) {
|
|
394
|
-
clearTimeout(this.pongTimeout);
|
|
395
|
-
this.pongTimeout = null;
|
|
396
|
-
}
|
|
397
|
-
if (this.reConnectTimeout !== null) {
|
|
398
|
-
clearTimeout(this.reConnectTimeout);
|
|
399
|
-
this.reConnectTimeout = null;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
connect() {
|
|
403
|
-
if (this.ws) {
|
|
404
|
-
this.logger.warn('ReConnectWebsocket try connect but already connected');
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
const ws = new WebSocket(this.url);
|
|
408
|
-
this.ws = ws;
|
|
409
|
-
ws.onopen = () => {
|
|
410
|
-
this.delaySendPing();
|
|
411
|
-
};
|
|
412
|
-
ws.onerror = (e) => {
|
|
413
|
-
this.logger.info('ReConnectWebsocket error', e);
|
|
414
|
-
};
|
|
415
|
-
ws.onmessage = (e) => {
|
|
416
|
-
try {
|
|
417
|
-
const pushMessage = JSON.parse(e.data);
|
|
418
|
-
if (pushMessage.type === PushMessageType.PONG_MESSAGE) {
|
|
419
|
-
if (this.pongTimeout !== null) {
|
|
420
|
-
clearTimeout(this.pongTimeout);
|
|
421
|
-
this.pongTimeout = null;
|
|
422
|
-
this.delaySendPing();
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
else {
|
|
426
|
-
this.sendAck(pushMessage.id);
|
|
427
|
-
}
|
|
428
|
-
if (pushMessage.type === PushMessageType.BIG_PACKAGE_PUSH) {
|
|
429
|
-
for (const item of pushMessage.content.big_push) {
|
|
430
|
-
this.emit('message', item);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
else {
|
|
434
|
-
this.emit('message', pushMessage);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
catch (e) {
|
|
438
|
-
this.logger.error('ReConnectWebsocket message parse failed', e);
|
|
439
|
-
}
|
|
440
|
-
};
|
|
441
|
-
ws.onclose = (e) => {
|
|
442
|
-
if (e.code === 4001) {
|
|
443
|
-
// kick off
|
|
444
|
-
this.kickOff();
|
|
445
|
-
}
|
|
446
|
-
else {
|
|
447
|
-
this.reConnect();
|
|
448
|
-
}
|
|
449
|
-
};
|
|
450
|
-
}
|
|
451
|
-
reConnect() {
|
|
452
|
-
this.disconnect();
|
|
453
|
-
if (!this.destroyed) {
|
|
454
|
-
this.reConnectTimeout = setTimeout(() => {
|
|
455
|
-
this.reConnectTimeout = null;
|
|
456
|
-
this.connect();
|
|
457
|
-
}, RE_CONNECT_TIMEOUT_MS);
|
|
458
|
-
}
|
|
459
|
-
}
|
|
460
|
-
send(message) {
|
|
461
|
-
if (this.destroyed) {
|
|
462
|
-
return false;
|
|
463
|
-
}
|
|
464
|
-
if (!this.ws) {
|
|
465
|
-
this.logger.warn('ReConnectWebsocket send before connect');
|
|
466
|
-
return false;
|
|
467
|
-
}
|
|
468
|
-
try {
|
|
469
|
-
if (this.ws.readyState !== this.ws.OPEN) {
|
|
470
|
-
this.logger.warn('ReConnectWebsocket send but not open', this.ws.readyState);
|
|
471
|
-
return false;
|
|
472
|
-
}
|
|
473
|
-
this.ws.send(JSON.stringify(message));
|
|
474
|
-
return true;
|
|
475
|
-
}
|
|
476
|
-
catch (e) {
|
|
477
|
-
this.logger.warn('ReConnectWebsocket send error', e);
|
|
478
|
-
return false;
|
|
479
|
-
}
|
|
480
|
-
}
|
|
481
|
-
sendAck(id) {
|
|
482
|
-
this.send({
|
|
483
|
-
type: 1,
|
|
484
|
-
ids: [id],
|
|
485
|
-
});
|
|
486
|
-
}
|
|
487
|
-
destroy() {
|
|
488
|
-
if (this.destroyed) {
|
|
489
|
-
return;
|
|
490
|
-
}
|
|
491
|
-
this.destroyed = true;
|
|
492
|
-
this.disconnect();
|
|
493
|
-
window.removeEventListener('online', this.reConnect);
|
|
494
|
-
}
|
|
495
|
-
delaySendPing() {
|
|
496
|
-
if (this.pingTimeout !== null) {
|
|
497
|
-
clearTimeout(this.pingTimeout);
|
|
498
|
-
this.pingTimeout = null;
|
|
499
|
-
}
|
|
500
|
-
this.pingTimeout = setTimeout(() => {
|
|
501
|
-
this.pingTimeout = null;
|
|
502
|
-
const success = this.send({
|
|
503
|
-
type: 0,
|
|
504
|
-
});
|
|
505
|
-
if (success) {
|
|
506
|
-
this.waitPong();
|
|
507
|
-
}
|
|
508
|
-
else {
|
|
509
|
-
this.reConnect();
|
|
510
|
-
}
|
|
511
|
-
}, PING_TIMEOUT_MS);
|
|
512
|
-
}
|
|
513
|
-
waitPong() {
|
|
514
|
-
this.pongTimeout = setTimeout(() => {
|
|
515
|
-
this.logger.info('ReConnectWebsocket pong timeout');
|
|
516
|
-
this.reConnect();
|
|
517
|
-
}, PONG_TIMEOUT_MS);
|
|
518
|
-
}
|
|
519
|
-
kickOff() {
|
|
520
|
-
this.destroy();
|
|
521
|
-
this.emit('kickoff');
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
class BusinessWebsocket extends Base {
|
|
526
|
-
host;
|
|
527
|
-
context;
|
|
528
|
-
logger;
|
|
529
|
-
ws;
|
|
530
|
-
constructor(config) {
|
|
531
|
-
super();
|
|
532
|
-
this.host = config.host;
|
|
533
|
-
this.context = config.context;
|
|
534
|
-
this.logger = config.logger;
|
|
535
|
-
this.ws = null;
|
|
536
|
-
}
|
|
537
|
-
connect() {
|
|
538
|
-
if (!this.context.isLogin) {
|
|
539
|
-
this.logger.warn('BusinessWebsocket try connect but not login');
|
|
540
|
-
return;
|
|
541
|
-
}
|
|
542
|
-
if (this.ws) {
|
|
543
|
-
this.logger.warn('BusinessWebsocket try connect but already connected');
|
|
544
|
-
return;
|
|
545
|
-
}
|
|
546
|
-
const url = `${this.host}/ws?token=${this.context.loginUser.sessionId}&platform=web`;
|
|
547
|
-
this.ws = new ReConnectWebsocket({
|
|
548
|
-
url,
|
|
549
|
-
logger: this.logger,
|
|
550
|
-
});
|
|
551
|
-
this.ws.on('message', (message) => {
|
|
552
|
-
this.emit('message', message);
|
|
553
|
-
});
|
|
554
|
-
this.ws.on('kickoff', () => {
|
|
555
|
-
this.emit('kickoff');
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
disconnect() {
|
|
559
|
-
if (this.ws) {
|
|
560
|
-
this.ws.destroy();
|
|
561
|
-
this.ws = null;
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
this.logger.info('BusinessWebsocket already disconnected');
|
|
565
|
-
}
|
|
566
|
-
}
|
|
567
|
-
}
|
|
568
|
-
|
|
569
|
-
const withResolvers = () => {
|
|
570
|
-
let resolver = () => { };
|
|
571
|
-
const promise = new Promise((resolve) => {
|
|
572
|
-
resolver = resolve;
|
|
573
|
-
});
|
|
574
|
-
return {
|
|
575
|
-
promise,
|
|
576
|
-
resolve: resolver,
|
|
577
|
-
};
|
|
578
|
-
};
|
|
579
|
-
|
|
580
|
-
class FeedCache {
|
|
581
|
-
_isSyncCompleted;
|
|
582
|
-
map;
|
|
583
|
-
chatMap;
|
|
584
|
-
list;
|
|
585
|
-
waitMap;
|
|
586
|
-
constructor() {
|
|
587
|
-
this._isSyncCompleted = false;
|
|
588
|
-
this.map = new Map();
|
|
589
|
-
this.chatMap = new Map();
|
|
590
|
-
this.waitMap = new Map();
|
|
591
|
-
this.list = [];
|
|
592
|
-
}
|
|
593
|
-
getAll() {
|
|
594
|
-
const result = [];
|
|
595
|
-
for (const id of this.list) {
|
|
596
|
-
const feed = this.map.get(id);
|
|
597
|
-
if (feed) {
|
|
598
|
-
result.push(feed);
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
return result;
|
|
602
|
-
}
|
|
603
|
-
get(feedId) {
|
|
604
|
-
return this.map.get(feedId) ?? null;
|
|
605
|
-
}
|
|
606
|
-
getByChatId(chatId) {
|
|
607
|
-
const feedId = this.chatMap.get(chatId);
|
|
608
|
-
if (feedId !== undefined) {
|
|
609
|
-
return this.get(feedId);
|
|
610
|
-
}
|
|
611
|
-
return null;
|
|
612
|
-
}
|
|
613
|
-
async waitFor(feedId) {
|
|
614
|
-
const feed = this.get(feedId);
|
|
615
|
-
if (feed) {
|
|
616
|
-
return feed;
|
|
617
|
-
}
|
|
618
|
-
if (this._isSyncCompleted) {
|
|
619
|
-
return null;
|
|
620
|
-
}
|
|
621
|
-
let resolver = this.waitMap.get(feedId);
|
|
622
|
-
if (!resolver) {
|
|
623
|
-
resolver = withResolvers();
|
|
624
|
-
this.waitMap.set(feedId, resolver);
|
|
625
|
-
}
|
|
626
|
-
await resolver.promise;
|
|
627
|
-
return this.get(feedId);
|
|
628
|
-
}
|
|
629
|
-
resolveWait(feedId) {
|
|
630
|
-
const resolver = this.waitMap.get(feedId);
|
|
631
|
-
if (resolver) {
|
|
632
|
-
resolver.resolve(true);
|
|
633
|
-
this.waitMap.delete(feedId);
|
|
634
|
-
}
|
|
635
|
-
}
|
|
636
|
-
remove(feedId) {
|
|
637
|
-
const feed = this.map.get(feedId);
|
|
638
|
-
if (feed) {
|
|
639
|
-
this.map.delete(feedId);
|
|
640
|
-
this.chatMap.delete(feed.chat_id);
|
|
641
|
-
this.list.splice(this.list.indexOf(feedId), 1);
|
|
642
|
-
return true;
|
|
643
|
-
}
|
|
644
|
-
return false;
|
|
645
|
-
}
|
|
646
|
-
push(feed) {
|
|
647
|
-
this.remove(feed.id);
|
|
648
|
-
this.map.set(feed.id, feed);
|
|
649
|
-
this.list.push(feed.id);
|
|
650
|
-
this.chatMap.set(feed.chat_id, feed.id);
|
|
651
|
-
this.resolveWait(feed.id);
|
|
652
|
-
}
|
|
653
|
-
unshift(feed) {
|
|
654
|
-
this.remove(feed.id);
|
|
655
|
-
this.map.set(feed.id, feed);
|
|
656
|
-
this.list.unshift(feed.id);
|
|
657
|
-
this.chatMap.set(feed.chat_id, feed.id);
|
|
658
|
-
this.resolveWait(feed.id);
|
|
659
|
-
}
|
|
660
|
-
update(feed) {
|
|
661
|
-
if (!this.map.has(feed.id)) {
|
|
662
|
-
return;
|
|
663
|
-
}
|
|
664
|
-
this.map.set(feed.id, feed);
|
|
665
|
-
}
|
|
666
|
-
markSyncComplete() {
|
|
667
|
-
this._isSyncCompleted = true;
|
|
668
|
-
for (const [_, resolver] of this.waitMap) {
|
|
669
|
-
resolver.resolve(true);
|
|
670
|
-
}
|
|
671
|
-
this.waitMap.clear();
|
|
672
|
-
}
|
|
673
|
-
isSyncCompleted() {
|
|
674
|
-
return this._isSyncCompleted;
|
|
675
|
-
}
|
|
676
|
-
reset() {
|
|
677
|
-
this._isSyncCompleted = false;
|
|
678
|
-
this.map.clear();
|
|
679
|
-
this.chatMap.clear();
|
|
680
|
-
this.waitMap.clear();
|
|
681
|
-
this.list = [];
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
/**
|
|
686
|
-
* 将字符串转换为 ArrayBuffer
|
|
687
|
-
* @param str 输入字符串
|
|
688
|
-
* @returns ArrayBuffer
|
|
689
|
-
*/
|
|
690
|
-
function stringToArrayBuffer(str) {
|
|
691
|
-
return new TextEncoder().encode(str).buffer;
|
|
692
|
-
}
|
|
693
|
-
async function generateSignatureWebCrypto(message, secretKey) {
|
|
694
|
-
const keyBuffer = stringToArrayBuffer(secretKey);
|
|
695
|
-
const messageBuffer = stringToArrayBuffer(message);
|
|
696
|
-
// 导入密钥
|
|
697
|
-
const key = await crypto.subtle.importKey('raw', keyBuffer, { name: 'HMAC', hash: { name: 'SHA-256' } }, false, ['sign']);
|
|
698
|
-
// 生成签名
|
|
699
|
-
const signature = await crypto.subtle.sign('HMAC', key, messageBuffer);
|
|
700
|
-
// 转换签名为十六进制字符串
|
|
701
|
-
return Array.from(new Uint8Array(signature))
|
|
702
|
-
.map((b) => b.toString(16).padStart(2, '0'))
|
|
703
|
-
.join('');
|
|
704
|
-
}
|
|
705
|
-
/**
|
|
706
|
-
* 使用 CryptoJS 生成 HMAC 签名(降级方案)
|
|
707
|
-
* @param message 待签名的消息
|
|
708
|
-
* @param secretKey 密钥
|
|
709
|
-
* @returns 签名字符串(十六进制格式)
|
|
710
|
-
*/
|
|
711
|
-
function generateSignatureCryptoJS(message, secretKey) {
|
|
712
|
-
const hash = CryptoJS.HmacSHA256(message, secretKey);
|
|
713
|
-
return hash.toString(CryptoJS.enc.Hex);
|
|
714
|
-
}
|
|
715
|
-
/**
|
|
716
|
-
* 生成 HMAC 签名(兼容降级)
|
|
717
|
-
* @param message 待签名的消息
|
|
718
|
-
* @returns 签名字符串(十六进制格式)
|
|
719
|
-
*/
|
|
720
|
-
async function generateSignature(message, secretKey) {
|
|
721
|
-
if (window.crypto && window.crypto.subtle) {
|
|
722
|
-
// 优先使用 Web Crypto API
|
|
723
|
-
return await generateSignatureWebCrypto(message, secretKey);
|
|
724
|
-
}
|
|
725
|
-
// 降级到 CryptoJS
|
|
726
|
-
return generateSignatureCryptoJS(message, secretKey);
|
|
727
|
-
}
|
|
728
|
-
const sortedJSON = (obj) => {
|
|
729
|
-
if (obj === null || obj === undefined) {
|
|
730
|
-
return '';
|
|
731
|
-
}
|
|
732
|
-
if (typeof obj === 'object' && !Array.isArray(obj)) {
|
|
733
|
-
// 处理对象 (map)
|
|
734
|
-
const keys = Object.keys(obj).sort(); // 对键进行字典序排序
|
|
735
|
-
let str = '';
|
|
736
|
-
for (const key of keys) {
|
|
737
|
-
str += sortedJSON(obj[key]);
|
|
738
|
-
}
|
|
739
|
-
return str;
|
|
740
|
-
}
|
|
741
|
-
else if (Array.isArray(obj)) {
|
|
742
|
-
// 处理数组
|
|
743
|
-
let str = '';
|
|
744
|
-
for (const item of obj) {
|
|
745
|
-
str += sortedJSON(item);
|
|
746
|
-
}
|
|
747
|
-
return str;
|
|
748
|
-
}
|
|
749
|
-
else if (typeof obj === 'string') {
|
|
750
|
-
// 处理字符串
|
|
751
|
-
return obj;
|
|
752
|
-
}
|
|
753
|
-
else {
|
|
754
|
-
// 处理其他基础类型 (数字、布尔值等)
|
|
755
|
-
return String(obj);
|
|
756
|
-
}
|
|
757
|
-
};
|
|
758
|
-
const makeSign = async (data, secretKey) => {
|
|
759
|
-
const message = await sortedJSON(data);
|
|
760
|
-
return await generateSignature(message, secretKey);
|
|
761
|
-
};
|
|
762
|
-
|
|
763
|
-
function createMakeSignErrorResponse() {
|
|
764
|
-
return createErrorResponse(ApiResponseCode.MAKE_SIGN_FAIL, 'make sign fail');
|
|
765
|
-
}
|
|
766
|
-
class HttpClient {
|
|
767
|
-
api;
|
|
768
|
-
context;
|
|
769
|
-
logger;
|
|
770
|
-
constructor(config) {
|
|
771
|
-
this.api = axios.create({
|
|
772
|
-
baseURL: config.host,
|
|
773
|
-
headers: {
|
|
774
|
-
'x-developer': config.developer,
|
|
775
|
-
},
|
|
776
|
-
});
|
|
777
|
-
this.context = config.context;
|
|
778
|
-
this.logger = config.logger;
|
|
779
|
-
}
|
|
780
|
-
async request(config) {
|
|
781
|
-
try {
|
|
782
|
-
const { needSign = true, ...axiosConfig } = config;
|
|
783
|
-
if (!axiosConfig.headers) {
|
|
784
|
-
axiosConfig.headers = {};
|
|
785
|
-
}
|
|
786
|
-
axiosConfig.headers['platform'] = 'web';
|
|
787
|
-
if (needSign &&
|
|
788
|
-
config.method?.toUpperCase() === 'POST' &&
|
|
789
|
-
axiosConfig.data) {
|
|
790
|
-
if (!this.context.isLogin) {
|
|
791
|
-
this.logger.warn('HttpClient needs make sign but not login');
|
|
792
|
-
return createMakeSignErrorResponse();
|
|
793
|
-
}
|
|
794
|
-
try {
|
|
795
|
-
const sign = await makeSign(axiosConfig.data, this.context.loginUser.secretKey);
|
|
796
|
-
axiosConfig.headers['sign'] = sign;
|
|
797
|
-
}
|
|
798
|
-
catch (e) {
|
|
799
|
-
this.logger.error('HttpClient make sign error', e);
|
|
800
|
-
return createMakeSignErrorResponse();
|
|
801
|
-
}
|
|
802
|
-
}
|
|
803
|
-
const res = await this.api(axiosConfig);
|
|
804
|
-
const body = res.data;
|
|
805
|
-
if (body.code === ApiResponseCode.SUCCESS) {
|
|
806
|
-
return {
|
|
807
|
-
ok: true,
|
|
808
|
-
data: body.data,
|
|
809
|
-
};
|
|
810
|
-
}
|
|
811
|
-
this.logger.info('HttpClient server error', res);
|
|
812
|
-
return {
|
|
813
|
-
ok: false,
|
|
814
|
-
code: body.code,
|
|
815
|
-
msg: body.msg,
|
|
816
|
-
msg_i18n: body.msg_i18n,
|
|
817
|
-
};
|
|
818
|
-
}
|
|
819
|
-
catch {
|
|
820
|
-
return {
|
|
821
|
-
ok: false,
|
|
822
|
-
code: ApiResponseCode.NETWORK_ERROR,
|
|
823
|
-
msg: 'network error',
|
|
824
|
-
};
|
|
825
|
-
}
|
|
826
|
-
}
|
|
827
|
-
get(url, params, config) {
|
|
828
|
-
return this.request({
|
|
829
|
-
url,
|
|
830
|
-
method: 'GET',
|
|
831
|
-
params,
|
|
832
|
-
...config,
|
|
833
|
-
});
|
|
834
|
-
}
|
|
835
|
-
post(url, data, config) {
|
|
836
|
-
return this.request({
|
|
837
|
-
url,
|
|
838
|
-
method: 'POST',
|
|
839
|
-
data,
|
|
840
|
-
...config,
|
|
841
|
-
});
|
|
842
|
-
}
|
|
843
|
-
}
|
|
844
|
-
|
|
845
|
-
class Logger {
|
|
846
|
-
level;
|
|
847
|
-
logHandler;
|
|
848
|
-
constructor() {
|
|
849
|
-
this.level = LogLevel.error;
|
|
850
|
-
this.logHandler = this.createLogHandler();
|
|
851
|
-
}
|
|
852
|
-
getPriority(level) {
|
|
853
|
-
switch (level) {
|
|
854
|
-
case LogLevel.error:
|
|
855
|
-
return 3;
|
|
856
|
-
case LogLevel.warning:
|
|
857
|
-
return 2;
|
|
858
|
-
case LogLevel.info:
|
|
859
|
-
return 1;
|
|
860
|
-
default:
|
|
861
|
-
return 0;
|
|
862
|
-
}
|
|
863
|
-
}
|
|
864
|
-
createLogHandler() {
|
|
865
|
-
return (level, message, ...args) => {
|
|
866
|
-
const timestamp = new Date().toISOString().split('T')[0]; // YYYY-MM-DD format
|
|
867
|
-
const prefix = `[tlog] ${timestamp}`;
|
|
868
|
-
const logMessage = message ? `${prefix} ${message}` : prefix;
|
|
869
|
-
switch (level) {
|
|
870
|
-
case LogLevel.error:
|
|
871
|
-
console.error(logMessage, ...args);
|
|
872
|
-
break;
|
|
873
|
-
case LogLevel.warning:
|
|
874
|
-
console.warn(logMessage, ...args);
|
|
875
|
-
break;
|
|
876
|
-
case LogLevel.info:
|
|
877
|
-
console.info(logMessage, ...args);
|
|
878
|
-
break;
|
|
879
|
-
default:
|
|
880
|
-
console.log(logMessage, ...args);
|
|
881
|
-
}
|
|
882
|
-
};
|
|
883
|
-
}
|
|
884
|
-
log(level, message, ...args) {
|
|
885
|
-
if (this.getPriority(level) >= this.getPriority(this.level)) {
|
|
886
|
-
this.logHandler(level, message, ...args);
|
|
887
|
-
}
|
|
888
|
-
}
|
|
889
|
-
setLogLevel(level) {
|
|
890
|
-
this.level = level;
|
|
891
|
-
// 重新创建logHandler以确保使用新的日志级别
|
|
892
|
-
this.logHandler = this.createLogHandler();
|
|
893
|
-
}
|
|
894
|
-
info(message, ...args) {
|
|
895
|
-
this.log(LogLevel.info, message, ...args);
|
|
896
|
-
}
|
|
897
|
-
warn(message, ...args) {
|
|
898
|
-
this.log(LogLevel.warning, message, ...args);
|
|
899
|
-
}
|
|
900
|
-
error(message, ...args) {
|
|
901
|
-
this.log(LogLevel.error, message, ...args);
|
|
902
|
-
}
|
|
903
|
-
}
|
|
904
|
-
|
|
905
|
-
class PushMessageProcessor {
|
|
906
|
-
context;
|
|
907
|
-
logger;
|
|
908
|
-
emitter;
|
|
909
|
-
feedCache;
|
|
910
|
-
handleKickoff;
|
|
911
|
-
constructor(config) {
|
|
912
|
-
this.context = config.context;
|
|
913
|
-
this.logger = config.logger;
|
|
914
|
-
this.emitter = config.emitter;
|
|
915
|
-
this.feedCache = config.feedCache;
|
|
916
|
-
this.handleKickoff = config.onKickoff;
|
|
917
|
-
}
|
|
918
|
-
emitFeedListUpdated() {
|
|
919
|
-
this.emitter.emit(CHAT_SDK_EVENT.FEED_LIST_UPDATED, {
|
|
920
|
-
list: this.feedCache.getAll(),
|
|
921
|
-
is_sync_completed: this.feedCache.isSyncCompleted(),
|
|
922
|
-
});
|
|
923
|
-
}
|
|
924
|
-
processCreateFeed(pushMessage) {
|
|
925
|
-
this.feedCache.unshift(pushMessage.content);
|
|
926
|
-
this.emitFeedListUpdated();
|
|
927
|
-
}
|
|
928
|
-
processDeleteFeed(pushMessage) {
|
|
929
|
-
const ok = this.feedCache.remove(pushMessage.content.id);
|
|
930
|
-
if (ok) {
|
|
931
|
-
this.emitFeedListUpdated();
|
|
932
|
-
}
|
|
933
|
-
}
|
|
934
|
-
processAddMessage(pushMessage) {
|
|
935
|
-
if (!this.context.isLogin) {
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
const loginUserId = this.context.loginUser.userId;
|
|
939
|
-
const message = pushMessage.content;
|
|
940
|
-
const feed = this.feedCache.getByChatId(message.chat_id);
|
|
941
|
-
if (feed !== null) {
|
|
942
|
-
let needUpdateFeed = false;
|
|
943
|
-
const partialFeed = {};
|
|
944
|
-
// 更新未读消息数
|
|
945
|
-
if (message.message_kind_id !== MessageKind.system &&
|
|
946
|
-
message.sender_id !== loginUserId) {
|
|
947
|
-
needUpdateFeed = true;
|
|
948
|
-
const unreadMessageSet = new Set(feed.unread_message_ids);
|
|
949
|
-
unreadMessageSet.add(message.id);
|
|
950
|
-
partialFeed.unread_message_ids = [...unreadMessageSet];
|
|
951
|
-
partialFeed.badge = unreadMessageSet.size;
|
|
952
|
-
}
|
|
953
|
-
// 更新最后一条消息
|
|
954
|
-
if (feed.last_message_position < message.position) {
|
|
955
|
-
needUpdateFeed = true;
|
|
956
|
-
partialFeed.last_message_position = message.position;
|
|
957
|
-
partialFeed.last_message_send_at = message.send_at;
|
|
958
|
-
partialFeed.last_message = message.content;
|
|
959
|
-
partialFeed.last_message_kind_id = message.message_kind_id;
|
|
960
|
-
partialFeed.last_message_type_id = message.message_type_id;
|
|
961
|
-
}
|
|
962
|
-
if (needUpdateFeed) {
|
|
963
|
-
this.feedCache.unshift({
|
|
964
|
-
...feed,
|
|
965
|
-
...partialFeed,
|
|
966
|
-
});
|
|
967
|
-
this.emitFeedListUpdated();
|
|
968
|
-
}
|
|
969
|
-
this.emitter.emit(CHAT_SDK_EVENT.MESSAGE_RECEIVED, message);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
processUpdateMessage(pushMessage) {
|
|
973
|
-
this.emitter.emit(CHAT_SDK_EVENT.MESSAGE_UPDATED, pushMessage.content);
|
|
974
|
-
}
|
|
975
|
-
processPushLiveMessage(pushMessage) {
|
|
976
|
-
this.emitter.emit(CHAT_SDK_EVENT.ROOM_MESSAGE_RECEIVED, pushMessage.content);
|
|
977
|
-
}
|
|
978
|
-
processReadMessage(pushMessage) {
|
|
979
|
-
if (!this.context.isLogin) {
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
const loginUserId = this.context.loginUser.userId;
|
|
983
|
-
const feed = this.feedCache.getByChatId(pushMessage.content.chat_id);
|
|
984
|
-
if (feed !== null) {
|
|
985
|
-
let needUpdateFeed = false;
|
|
986
|
-
const partialFeed = {};
|
|
987
|
-
// 更新未读数
|
|
988
|
-
if (pushMessage.content.reader_ids.includes(loginUserId) &&
|
|
989
|
-
pushMessage.content.message_ids.length > 0) {
|
|
990
|
-
needUpdateFeed = true;
|
|
991
|
-
const unreadMessageSet = new Set(feed.unread_message_ids);
|
|
992
|
-
pushMessage.content.message_ids.forEach((id) => {
|
|
993
|
-
unreadMessageSet.delete(id);
|
|
994
|
-
});
|
|
995
|
-
partialFeed.unread_message_ids = [...unreadMessageSet];
|
|
996
|
-
partialFeed.badge = unreadMessageSet.size;
|
|
997
|
-
}
|
|
998
|
-
if (needUpdateFeed) {
|
|
999
|
-
this.feedCache.update({
|
|
1000
|
-
...feed,
|
|
1001
|
-
...partialFeed,
|
|
1002
|
-
});
|
|
1003
|
-
this.emitFeedListUpdated();
|
|
1004
|
-
}
|
|
1005
|
-
this.emitter.emit(CHAT_SDK_EVENT.MESSAGE_RECEIPT_RECEIVED, {
|
|
1006
|
-
feed_id: feed.id,
|
|
1007
|
-
message_ids: pushMessage.content.message_ids,
|
|
1008
|
-
reader_user_ids: pushMessage.content.reader_ids,
|
|
1009
|
-
});
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
processKickOutDevice() {
|
|
1013
|
-
this.handleKickoff();
|
|
1014
|
-
}
|
|
1015
|
-
process(pushMessage) {
|
|
1016
|
-
this.logger.info('PushMessageProcessor process', pushMessage);
|
|
1017
|
-
switch (pushMessage.type) {
|
|
1018
|
-
case PushMessageType.CREATE_FEED:
|
|
1019
|
-
this.processCreateFeed(pushMessage);
|
|
1020
|
-
break;
|
|
1021
|
-
case PushMessageType.DELETE_FEED:
|
|
1022
|
-
this.processDeleteFeed(pushMessage);
|
|
1023
|
-
break;
|
|
1024
|
-
case PushMessageType.ADD_MESSAGE:
|
|
1025
|
-
this.processAddMessage(pushMessage);
|
|
1026
|
-
break;
|
|
1027
|
-
case PushMessageType.UPDATE_MESSAGE:
|
|
1028
|
-
this.processUpdateMessage(pushMessage);
|
|
1029
|
-
break;
|
|
1030
|
-
case PushMessageType.PUSH_LIVE_MESSAGE:
|
|
1031
|
-
this.processPushLiveMessage(pushMessage);
|
|
1032
|
-
break;
|
|
1033
|
-
case PushMessageType.READ_MESSAGE:
|
|
1034
|
-
this.processReadMessage(pushMessage);
|
|
1035
|
-
break;
|
|
1036
|
-
case PushMessageType.KICK_OUT_DEVICE:
|
|
1037
|
-
this.processKickOutDevice();
|
|
1038
|
-
break;
|
|
1039
|
-
}
|
|
1040
|
-
}
|
|
1041
|
-
}
|
|
1042
|
-
|
|
1043
|
-
var sleep = (ms) => {
|
|
1044
|
-
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1045
|
-
};
|
|
1046
|
-
|
|
1047
|
-
class SingletonLock {
|
|
1048
|
-
destroyed;
|
|
1049
|
-
promise;
|
|
1050
|
-
constructor() {
|
|
1051
|
-
this.destroyed = false;
|
|
1052
|
-
let resolve = () => { };
|
|
1053
|
-
const promise = new Promise((_resolve) => {
|
|
1054
|
-
resolve = _resolve;
|
|
1055
|
-
});
|
|
1056
|
-
this.promise = promise;
|
|
1057
|
-
navigator.locks.request('typex-sdk', {
|
|
1058
|
-
ifAvailable: true,
|
|
1059
|
-
}, async (lock) => {
|
|
1060
|
-
if (!lock) {
|
|
1061
|
-
resolve(false);
|
|
1062
|
-
return;
|
|
1063
|
-
}
|
|
1064
|
-
resolve(true);
|
|
1065
|
-
while (!this.destroyed) {
|
|
1066
|
-
await sleep(10000);
|
|
1067
|
-
}
|
|
1068
|
-
});
|
|
1069
|
-
}
|
|
1070
|
-
async request() {
|
|
1071
|
-
const obtained = await this.promise;
|
|
1072
|
-
return obtained;
|
|
1073
|
-
}
|
|
1074
|
-
}
|
|
1075
|
-
|
|
1076
|
-
class Storage {
|
|
1077
|
-
static key = '__typex_sdk_data__';
|
|
1078
|
-
static set(value) {
|
|
1079
|
-
localStorage.setItem(this.key, JSON.stringify(value));
|
|
1080
|
-
}
|
|
1081
|
-
static get() {
|
|
1082
|
-
try {
|
|
1083
|
-
const raw = localStorage.getItem(this.key);
|
|
1084
|
-
if (!raw)
|
|
1085
|
-
return null;
|
|
1086
|
-
const data = JSON.parse(raw);
|
|
1087
|
-
return data;
|
|
1088
|
-
}
|
|
1089
|
-
catch {
|
|
1090
|
-
return null;
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
static clear() {
|
|
1094
|
-
localStorage.removeItem(this.key);
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
class Version {
|
|
1099
|
-
version;
|
|
1100
|
-
constructor() {
|
|
1101
|
-
this.version = 0;
|
|
1102
|
-
}
|
|
1103
|
-
increment() {
|
|
1104
|
-
this.version++;
|
|
1105
|
-
}
|
|
1106
|
-
get() {
|
|
1107
|
-
return this.version;
|
|
1108
|
-
}
|
|
1109
|
-
check(curVersion) {
|
|
1110
|
-
return curVersion === this.version;
|
|
1111
|
-
}
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
class ChatSDK extends Base {
|
|
1115
|
-
context;
|
|
1116
|
-
version;
|
|
1117
|
-
singletonLock;
|
|
1118
|
-
logger;
|
|
1119
|
-
httpClient;
|
|
1120
|
-
feedCache;
|
|
1121
|
-
ws;
|
|
1122
|
-
pushMessageProcessor;
|
|
1123
|
-
constructor(config) {
|
|
1124
|
-
super();
|
|
1125
|
-
this.context = {
|
|
1126
|
-
isLogin: false,
|
|
1127
|
-
isReady: false,
|
|
1128
|
-
deviceId: null,
|
|
1129
|
-
};
|
|
1130
|
-
this.version = new Version();
|
|
1131
|
-
this.singletonLock = new SingletonLock();
|
|
1132
|
-
this.logger = new Logger();
|
|
1133
|
-
this.httpClient = new HttpClient({
|
|
1134
|
-
host: config.httpHost,
|
|
1135
|
-
context: this.context,
|
|
1136
|
-
logger: this.logger,
|
|
1137
|
-
developer: config.developer,
|
|
1138
|
-
});
|
|
1139
|
-
this.feedCache = new FeedCache();
|
|
1140
|
-
this.ws = new BusinessWebsocket({
|
|
1141
|
-
host: config.websocketHost,
|
|
1142
|
-
context: this.context,
|
|
1143
|
-
logger: this.logger,
|
|
1144
|
-
});
|
|
1145
|
-
this.pushMessageProcessor = new PushMessageProcessor({
|
|
1146
|
-
context: this.context,
|
|
1147
|
-
logger: this.logger,
|
|
1148
|
-
emitter: this,
|
|
1149
|
-
feedCache: this.feedCache,
|
|
1150
|
-
onKickoff: () => {
|
|
1151
|
-
this.handleKickoff();
|
|
1152
|
-
},
|
|
1153
|
-
});
|
|
1154
|
-
this.init();
|
|
1155
|
-
}
|
|
1156
|
-
init() {
|
|
1157
|
-
this.loadContext();
|
|
1158
|
-
this.ws.on('message', (message) => {
|
|
1159
|
-
this.pushMessageProcessor.process(message);
|
|
1160
|
-
});
|
|
1161
|
-
this.ws.on('kickoff', () => {
|
|
1162
|
-
this.handleKickoff();
|
|
1163
|
-
});
|
|
1164
|
-
}
|
|
1165
|
-
async requestDeviceId() {
|
|
1166
|
-
if (this.context.deviceId) {
|
|
1167
|
-
return true;
|
|
1168
|
-
}
|
|
1169
|
-
const resp = await this.httpClient.post('/device_api/device_id', {
|
|
1170
|
-
platform: 'web',
|
|
1171
|
-
hash_str: '',
|
|
1172
|
-
}, {
|
|
1173
|
-
needSign: false,
|
|
1174
|
-
});
|
|
1175
|
-
if (!resp.ok) {
|
|
1176
|
-
return false;
|
|
1177
|
-
}
|
|
1178
|
-
this.context.deviceId = resp.data;
|
|
1179
|
-
this.saveContext();
|
|
1180
|
-
return true;
|
|
1181
|
-
}
|
|
1182
|
-
saveContext() {
|
|
1183
|
-
const { context } = this;
|
|
1184
|
-
Storage.set({
|
|
1185
|
-
deviceId: context.deviceId,
|
|
1186
|
-
});
|
|
1187
|
-
}
|
|
1188
|
-
loadContext() {
|
|
1189
|
-
const localData = Storage.get();
|
|
1190
|
-
if (!localData || !localData.deviceId) {
|
|
1191
|
-
return;
|
|
1192
|
-
}
|
|
1193
|
-
this.context.deviceId = localData.deviceId;
|
|
1194
|
-
}
|
|
1195
|
-
applyLoginStatus() {
|
|
1196
|
-
this.version.increment();
|
|
1197
|
-
this.saveContext();
|
|
1198
|
-
if (this.context.isLogin) {
|
|
1199
|
-
this.loadFeedList();
|
|
1200
|
-
this.ws.connect();
|
|
1201
|
-
this.context.isReady = true;
|
|
1202
|
-
this.emit(CHAT_SDK_EVENT.SDK_READY);
|
|
1203
|
-
}
|
|
1204
|
-
else {
|
|
1205
|
-
this.ws.disconnect();
|
|
1206
|
-
this.feedCache.reset();
|
|
1207
|
-
this.context.isReady = false;
|
|
1208
|
-
this.emit(CHAT_SDK_EVENT.SDK_NOT_READY);
|
|
1209
|
-
}
|
|
1210
|
-
}
|
|
1211
|
-
async loadFeedList() {
|
|
1212
|
-
const version = this.version.get();
|
|
1213
|
-
const last = {
|
|
1214
|
-
refresh_at: null,
|
|
1215
|
-
feed_id: null,
|
|
1216
|
-
};
|
|
1217
|
-
while (true) {
|
|
1218
|
-
const res = await this.httpClient.get('/feed', {
|
|
1219
|
-
limit: 500,
|
|
1220
|
-
last_refresh_at: last.refresh_at ?? undefined,
|
|
1221
|
-
last_feed_id: last.feed_id ?? undefined,
|
|
1222
|
-
});
|
|
1223
|
-
if (!this.version.check(version)) {
|
|
1224
|
-
break;
|
|
1225
|
-
}
|
|
1226
|
-
if (res.ok) {
|
|
1227
|
-
last.refresh_at = res.data.refresh_at;
|
|
1228
|
-
last.feed_id = res.data.last_feed_id;
|
|
1229
|
-
res.data.feed_list.forEach((feed) => {
|
|
1230
|
-
this.feedCache.push(feed);
|
|
1231
|
-
});
|
|
1232
|
-
if (!res.data.has_more) {
|
|
1233
|
-
break;
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
else {
|
|
1237
|
-
break;
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
if (this.version.check(version)) {
|
|
1241
|
-
this.feedCache.markSyncComplete();
|
|
1242
|
-
this.emit(CHAT_SDK_EVENT.FEED_LIST_UPDATED, {
|
|
1243
|
-
list: this.feedCache.getAll(),
|
|
1244
|
-
is_sync_completed: true,
|
|
1245
|
-
});
|
|
1246
|
-
}
|
|
1247
|
-
}
|
|
1248
|
-
/**
|
|
1249
|
-
* SDK 是否已就绪(登录成功且 WebSocket 已连接)。
|
|
1250
|
-
* @returns {boolean} true 表示可用,false 表示未就绪
|
|
1251
|
-
*/
|
|
1252
|
-
isReady() {
|
|
1253
|
-
return this.context.isReady;
|
|
1254
|
-
}
|
|
1255
|
-
/**
|
|
1256
|
-
* 设置 SDK 的日志级别。
|
|
1257
|
-
* @param {LogLevel} level 要设置的日志级别
|
|
1258
|
-
*/
|
|
1259
|
-
setLogLevel(level) {
|
|
1260
|
-
this.logger.setLogLevel(level);
|
|
1261
|
-
}
|
|
1262
|
-
/**
|
|
1263
|
-
* 使用一次性 userCode 登录,建立会话并连接 WebSocket。
|
|
1264
|
-
* 成功后会触发事件:SDK_READY。
|
|
1265
|
-
* @param {SDKLoginReq} req 登录请求参数(包含 userCode)
|
|
1266
|
-
* @returns {Promise<HttpClientResponse>} 成功返回 ok,失败返回错误信息
|
|
1267
|
-
*/
|
|
1268
|
-
async login(req) {
|
|
1269
|
-
const obtained = await this.singletonLock.request();
|
|
1270
|
-
if (!obtained) {
|
|
1271
|
-
this.logger.error('Another active TypeX SDK instance detected');
|
|
1272
|
-
return createErrorResponse(ApiResponseCode.REQUEST_SINGLETON_LOCK_FAIL, 'Another active TypeX SDK instance detected');
|
|
1273
|
-
}
|
|
1274
|
-
await this.requestDeviceId();
|
|
1275
|
-
if (!this.context.deviceId) {
|
|
1276
|
-
this.logger.error('Get deviceId failed');
|
|
1277
|
-
return createErrorResponse(ApiResponseCode.SDK_ERROR, 'Login params invalid');
|
|
1278
|
-
}
|
|
1279
|
-
const resp = await this.httpClient.post('/user/session/login', {
|
|
1280
|
-
userCode: req.user_code,
|
|
1281
|
-
device_id: this.context.deviceId,
|
|
1282
|
-
}, {
|
|
1283
|
-
needSign: false,
|
|
1284
|
-
});
|
|
1285
|
-
if (!resp.ok) {
|
|
1286
|
-
return resp;
|
|
1287
|
-
}
|
|
1288
|
-
this.context.isLogin = true;
|
|
1289
|
-
if (this.context.isLogin) {
|
|
1290
|
-
this.context.loginUser = {
|
|
1291
|
-
userId: resp.data.id,
|
|
1292
|
-
secretKey: resp.data.key,
|
|
1293
|
-
sessionId: Cookies.get('sessionid') ?? '',
|
|
1294
|
-
};
|
|
1295
|
-
}
|
|
1296
|
-
this.applyLoginStatus();
|
|
1297
|
-
return createOkResponse(null);
|
|
1298
|
-
}
|
|
1299
|
-
/**
|
|
1300
|
-
* 退出当前登录用户并断开 WebSocket。
|
|
1301
|
-
* 成功后会触发事件:SDK_NOT_READY。
|
|
1302
|
-
* @returns {Promise<HttpClientResponse>} 成功返回 ok,失败返回错误信息
|
|
1303
|
-
*/
|
|
1304
|
-
async logout() {
|
|
1305
|
-
const resp = await this.httpClient.get('/user/logout');
|
|
1306
|
-
if (!resp.ok) {
|
|
1307
|
-
return resp;
|
|
1308
|
-
}
|
|
1309
|
-
this.context.isLogin = false;
|
|
1310
|
-
this.applyLoginStatus();
|
|
1311
|
-
return createOkResponse(null);
|
|
1312
|
-
}
|
|
1313
|
-
async handleKickoff() {
|
|
1314
|
-
// websocket强制下线,更新登录状态即可
|
|
1315
|
-
this.context.isLogin = false;
|
|
1316
|
-
this.applyLoginStatus();
|
|
1317
|
-
}
|
|
1318
|
-
/**
|
|
1319
|
-
* 获取当前登录用户的 userId。
|
|
1320
|
-
* @returns {string | null} 已登录返回 userId,未登录返回 null
|
|
1321
|
-
*/
|
|
1322
|
-
getLoginUser() {
|
|
1323
|
-
if (this.context.isLogin) {
|
|
1324
|
-
return this.context.loginUser.userId;
|
|
1325
|
-
}
|
|
1326
|
-
return null;
|
|
1327
|
-
}
|
|
1328
|
-
/**
|
|
1329
|
-
* 创建单聊feed,如果已创建就返回已创建的feed
|
|
1330
|
-
* @param user_id
|
|
1331
|
-
* @returns
|
|
1332
|
-
*/
|
|
1333
|
-
async createSingleChatFeed(user_id) {
|
|
1334
|
-
return await this.httpClient.post('/chat/single', {
|
|
1335
|
-
receiver_id: user_id,
|
|
1336
|
-
});
|
|
1337
|
-
}
|
|
1338
|
-
/**
|
|
1339
|
-
* 删除指定feed
|
|
1340
|
-
* @param feed_id
|
|
1341
|
-
* @returns
|
|
1342
|
-
*/
|
|
1343
|
-
async deleteFeed(feed_id) {
|
|
1344
|
-
return await this.httpClient.post('/feed/delete', {
|
|
1345
|
-
feed_id_list: [feed_id],
|
|
1346
|
-
delete_peer: true,
|
|
1347
|
-
});
|
|
1348
|
-
}
|
|
1349
|
-
/**
|
|
1350
|
-
* 获取当前用户的 Feed 列表
|
|
1351
|
-
* @returns
|
|
1352
|
-
*/
|
|
1353
|
-
async getFeedList() {
|
|
1354
|
-
return createOkResponse({
|
|
1355
|
-
list: this.feedCache.getAll(),
|
|
1356
|
-
is_sync_completed: this.feedCache.isSyncCompleted(),
|
|
1357
|
-
});
|
|
1358
|
-
}
|
|
1359
|
-
/**
|
|
1360
|
-
* 根据 Feed ID 获取详情
|
|
1361
|
-
* @param feed_id
|
|
1362
|
-
* @returns
|
|
1363
|
-
*/
|
|
1364
|
-
async getFeedDetail(feed_id) {
|
|
1365
|
-
const feed = await this.feedCache.waitFor(feed_id);
|
|
1366
|
-
return createOkResponse(feed);
|
|
1367
|
-
}
|
|
1368
|
-
/**
|
|
1369
|
-
* 获取某个 Feed 的消息列表
|
|
1370
|
-
* @param req
|
|
1371
|
-
* @returns
|
|
1372
|
-
*/
|
|
1373
|
-
async getFeedMessageList(req) {
|
|
1374
|
-
const feedResp = await this.getFeedDetail(req.feed_id);
|
|
1375
|
-
if (feedResp.ok && feedResp.data) {
|
|
1376
|
-
const feed = feedResp.data;
|
|
1377
|
-
const { chat_id, first_position, last_message_position } = feed;
|
|
1378
|
-
const positions = makePositions({
|
|
1379
|
-
maxPosition: last_message_position,
|
|
1380
|
-
minPosition: first_position,
|
|
1381
|
-
lastPosition: req.last_message_position,
|
|
1382
|
-
limit: req.limit,
|
|
1383
|
-
});
|
|
1384
|
-
if (positions.length > 0) {
|
|
1385
|
-
const res = await this.httpClient.post('/message/get_by_position', {
|
|
1386
|
-
chat_position: {
|
|
1387
|
-
[chat_id]: positions,
|
|
1388
|
-
},
|
|
1389
|
-
}, {
|
|
1390
|
-
needSign: false,
|
|
1391
|
-
});
|
|
1392
|
-
if (res.ok && res.data[chat_id]) {
|
|
1393
|
-
// 因为结果是position倒序,所以要reverse
|
|
1394
|
-
const list = res.data[chat_id].reverse();
|
|
1395
|
-
return createOkResponse({
|
|
1396
|
-
has_more: positions[0] === first_position,
|
|
1397
|
-
list: list,
|
|
1398
|
-
last_message_position: list[0]?.position,
|
|
1399
|
-
});
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
return createOkResponse({
|
|
1404
|
-
has_more: false,
|
|
1405
|
-
list: [],
|
|
1406
|
-
});
|
|
1407
|
-
}
|
|
1408
|
-
/**
|
|
1409
|
-
* 本地创建一条 Feed 自定义消息
|
|
1410
|
-
* @param req
|
|
1411
|
-
* @returns
|
|
1412
|
-
*/
|
|
1413
|
-
createCustomMessage(req) {
|
|
1414
|
-
if (!this.context.isLogin) {
|
|
1415
|
-
return createErrorResponse(ApiResponseCode.SDK_ERROR, 'not login');
|
|
1416
|
-
}
|
|
1417
|
-
const feed = this.feedCache.get(req.feed_id);
|
|
1418
|
-
if (feed === null) {
|
|
1419
|
-
return createErrorResponse(ApiResponseCode.SDK_ERROR, 'not found feed');
|
|
1420
|
-
}
|
|
1421
|
-
const message = {
|
|
1422
|
-
id: '',
|
|
1423
|
-
chat_id: feed.chat_id,
|
|
1424
|
-
position: feed.last_message_position,
|
|
1425
|
-
sender_id: this.context.loginUser.userId,
|
|
1426
|
-
message_kind_id: MessageKind.text,
|
|
1427
|
-
message_type_id: MessageType.custom,
|
|
1428
|
-
send_at: '',
|
|
1429
|
-
reader_ids: [],
|
|
1430
|
-
hide_ids: [],
|
|
1431
|
-
operation: MessageOperation.normal,
|
|
1432
|
-
operation_user_id: '0',
|
|
1433
|
-
content: stringifyCustomMessageContent({
|
|
1434
|
-
client_msg_id: req.client_msg_id,
|
|
1435
|
-
content: req.content,
|
|
1436
|
-
}),
|
|
1437
|
-
};
|
|
1438
|
-
return createOkResponse(message);
|
|
1439
|
-
}
|
|
1440
|
-
/**
|
|
1441
|
-
* 发送一条消息到指定 Feed。
|
|
1442
|
-
*/
|
|
1443
|
-
async sendMessageToFeed(message) {
|
|
1444
|
-
if (message.message_type_id === MessageType.custom) {
|
|
1445
|
-
const content = parseCustomMessageContent(message.content);
|
|
1446
|
-
return this.httpClient.post('/message/send_custom', {
|
|
1447
|
-
chat_id: message.chat_id,
|
|
1448
|
-
client_id: content.client_msg_id,
|
|
1449
|
-
content: content.content == null ? undefined : JSON.stringify(content.content),
|
|
1450
|
-
});
|
|
1451
|
-
}
|
|
1452
|
-
return createErrorResponse(ApiResponseCode.SDK_ERROR, 'not support');
|
|
1453
|
-
}
|
|
1454
|
-
/**
|
|
1455
|
-
* 发送消息已读回执
|
|
1456
|
-
* @param req
|
|
1457
|
-
* @returns
|
|
1458
|
-
*/
|
|
1459
|
-
async sendMessageReadReceipt(req) {
|
|
1460
|
-
const feed = this.feedCache.get(req.feed_id);
|
|
1461
|
-
if (feed === null) {
|
|
1462
|
-
return createErrorResponse(ApiResponseCode.SDK_ERROR, 'not found feed');
|
|
1463
|
-
}
|
|
1464
|
-
return await this.httpClient.post('/message/read', {
|
|
1465
|
-
chat_id: feed.chat_id,
|
|
1466
|
-
message_ids: req.message_ids,
|
|
1467
|
-
});
|
|
1468
|
-
}
|
|
1469
|
-
/**
|
|
1470
|
-
* 加入直播间
|
|
1471
|
-
* @param {string} room_id 直播间id
|
|
1472
|
-
* @returns {Promise<HttpClientResponse>} 请求结果
|
|
1473
|
-
*/
|
|
1474
|
-
async joinRoom(room_id) {
|
|
1475
|
-
return await this.httpClient.post('/liveshow/join', {
|
|
1476
|
-
room_id,
|
|
1477
|
-
});
|
|
1478
|
-
}
|
|
1479
|
-
/**
|
|
1480
|
-
* 离开直播间
|
|
1481
|
-
* @param {string} room_id 直播间id
|
|
1482
|
-
* @returns {Promise<HttpClientResponse>} 请求结果
|
|
1483
|
-
*/
|
|
1484
|
-
async leaveRoom(room_id) {
|
|
1485
|
-
return await this.httpClient.post('/liveshow/leave', {
|
|
1486
|
-
room_id,
|
|
1487
|
-
});
|
|
1488
|
-
}
|
|
1489
|
-
/**
|
|
1490
|
-
* 获取直播间的最近50条消息
|
|
1491
|
-
* @param room_id 直播间id
|
|
1492
|
-
* @returns
|
|
1493
|
-
*/
|
|
1494
|
-
async getRoomRecentMessageList(room_id) {
|
|
1495
|
-
const resp = await this.httpClient.get('/liveshow/get_recent_message', {
|
|
1496
|
-
room_id,
|
|
1497
|
-
});
|
|
1498
|
-
if (!resp.ok) {
|
|
1499
|
-
return resp;
|
|
1500
|
-
}
|
|
1501
|
-
return createOkResponse(resp.data);
|
|
1502
|
-
}
|
|
1503
|
-
/**
|
|
1504
|
-
* 向直播间发送一条消息。
|
|
1505
|
-
* @param req
|
|
1506
|
-
* @returns
|
|
1507
|
-
*/
|
|
1508
|
-
async sendMessageToRoom(req) {
|
|
1509
|
-
return await this.httpClient.post('/liveshow/send_message', {
|
|
1510
|
-
room_id: req.room_id,
|
|
1511
|
-
content: req.content,
|
|
1512
|
-
});
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
|
-
|
|
1516
|
-
function create(config) {
|
|
1517
|
-
return new ChatSDK(config);
|
|
1518
|
-
}
|
|
1519
|
-
const utils = {
|
|
1520
|
-
parseSystemMessageContent,
|
|
1521
|
-
parseTextMessageContent,
|
|
1522
|
-
parseCustomMessageContent,
|
|
1523
|
-
};
|
|
1524
|
-
|
|
1525
|
-
export { ChatSDK, create, index$1 as enums, events, index as interfaces, utils };
|
|
1
|
+
import e from"js-cookie";import t from"axios";import s from"crypto-js";var i,n,o,r,a,c,h,d,l,_,E;!function(e){e[e.yes=1]="yes",e[e.no=0]="no"}(i||(i={})),function(e){e[e.SDK_ERROR=-1e4]="SDK_ERROR",e[e.NETWORK_ERROR=-9999]="NETWORK_ERROR",e[e.MAKE_SIGN_FAIL=-9998]="MAKE_SIGN_FAIL",e[e.REQUEST_SINGLETON_LOCK_FAIL=-9997]="REQUEST_SINGLETON_LOCK_FAIL",e[e.PARAMS_INVALID=-9996]="PARAMS_INVALID",e[e.SUCCESS=0]="SUCCESS"}(n||(n={})),function(e){e[e.CREATE_FEED=0]="CREATE_FEED",e[e.UPDATE_FEED=1]="UPDATE_FEED",e[e.ADD_MESSAGE=2]="ADD_MESSAGE",e[e.UPDATE_MESSAGE=3]="UPDATE_MESSAGE",e[e.UPDATE_USER_DATA=4]="UPDATE_USER_DATA",e[e.USER_LOGIN=5]="USER_LOGIN",e[e.READ_MESSAGE=6]="READ_MESSAGE",e[e.PONG_MESSAGE=8]="PONG_MESSAGE",e[e.DELETE_MESSAGE=9]="DELETE_MESSAGE",e[e.UPDATE_GROUP=12]="UPDATE_GROUP",e[e.DELETE_GROUP=13]="DELETE_GROUP",e[e.UPDATE_LOGIN_USER=17]="UPDATE_LOGIN_USER",e[e.UPDATE_MESSAGE_REACTION=19]="UPDATE_MESSAGE_REACTION",e[e.ADD_GROUP_MEMBER=21]="ADD_GROUP_MEMBER",e[e.DEL_GROUP_MEMBER=22]="DEL_GROUP_MEMBER",e[e.ADD_CONTACTS=23]="ADD_CONTACTS",e[e.UPDATE_CONTACTS=24]="UPDATE_CONTACTS",e[e.DEL_CONTACTS=25]="DEL_CONTACTS",e[e.BLOCK_USER=26]="BLOCK_USER",e[e.DEVICE_CODE=27]="DEVICE_CODE",e[e.MSG_OPEN_CLOCK=28]="MSG_OPEN_CLOCK",e[e.DELETE_SECRET_CHAT=29]="DELETE_SECRET_CHAT",e[e.DELETE_FEED=30]="DELETE_FEED",e[e.FAVORITE_UPDATE=31]="FAVORITE_UPDATE",e[e.STICKERS_UPDATE=32]="STICKERS_UPDATE",e[e.CUSTOM_STICKERS_UPDATE=33]="CUSTOM_STICKERS_UPDATE",e[e.MANAGE_DEVICE_UPDATE=34]="MANAGE_DEVICE_UPDATE",e[e.MASS_MESSAGE_UPDATE=35]="MASS_MESSAGE_UPDATE",e[e.CHAT_WALLPAPER_DELETE=36]="CHAT_WALLPAPER_DELETE",e[e.CHAT_WALLPAPER_ADD=37]="CHAT_WALLPAPER_ADD",e[e.CONTACT_REQUEST=40]="CONTACT_REQUEST",e[e.USER_PREFERENCE_CHANGE=38]="USER_PREFERENCE_CHANGE",e[e.UPDATE_GROUP_PIN=41]="UPDATE_GROUP_PIN",e[e.RTC_CANCEL=46]="RTC_CANCEL",e[e.ADD_FEED_FOLDER=43]="ADD_FEED_FOLDER",e[e.UPDATE_FEED_FOLDER=44]="UPDATE_FEED_FOLDER",e[e.UPDATE_FOLDER_FEED_LIST=47]="UPDATE_FOLDER_FEED_LIST",e[e.DELETE_FEED_FOLDER=45]="DELETE_FEED_FOLDER",e[e.UPDATE_CHATTER=42]="UPDATE_CHATTER",e[e.PAPER_KEY=48]="PAPER_KEY",e[e.PAPER_KEY_LOG=49]="PAPER_KEY_LOG",e[e.USER_LOGIN_SCAN=50]="USER_LOGIN_SCAN",e[e.RTC_RINGING=51]="RTC_RINGING",e[e.ON_UPDATE_PRIVATE_SETTING=52]="ON_UPDATE_PRIVATE_SETTING",e[e.ON_DELETE_PRIVATE_SETTING=53]="ON_DELETE_PRIVATE_SETTING",e[e.ON_UPDATE_LOGIN_PRIVATE_SETTING=54]="ON_UPDATE_LOGIN_PRIVATE_SETTING",e[e.PUSH_LIVE_MESSAGE=56]="PUSH_LIVE_MESSAGE",e[e.BIG_PACKAGE_PUSH=126]="BIG_PACKAGE_PUSH",e[e.KICK_OUT_DEVICE=127]="KICK_OUT_DEVICE",e[e.CLEAR_HISTORY=128]="CLEAR_HISTORY",e[e.CLEAR_DATA=255]="CLEAR_DATA"}(o||(o={})),function(e){e[e.singleChat=1]="singleChat",e[e.groupChat=2]="groupChat"}(r||(r={})),function(e){e[e.text=0]="text",e[e.system=1]="system"}(a||(a={})),function(e){e[e.text=0]="text",e[e.image=1]="image",e[e.card=2]="card",e[e.audio=3]="audio",e[e.video=4]="video",e[e.file=5]="file",e[e.mergeForward=6]="mergeForward",e[e.sticker=7]="sticker",e[e.richText=8]="richText",e[e.custom=9]="custom"}(c||(c={})),function(e){e[e.success=0]="success",e[e.sending=1]="sending",e[e.failed=2]="failed"}(h||(h={})),function(e){e[e.Owner=1]="Owner",e[e.Admin=2]="Admin",e[e.Common=3]="Common",e[e.Bot=4]="Bot"}(d||(d={})),function(e){e[e.normal=0]="normal",e[e.recall=1]="recall"}(l||(l={})),function(e){e[e.private=1]="private",e[e.public=0]="public",e[e.anonymous=2]="anonymous"}(_||(_={})),function(e){e.error="ERROR",e.warning="WARNING",e.info="INFO"}(E||(E={}));var g=Object.freeze({__proto__:null,get ApiResponseCode(){return n},get Boolean(){return i},get ChatType(){return r},get GroupRole(){return d},get GroupType(){return _},get LogLevel(){return E},get MessageKind(){return a},get MessageOperation(){return l},get MessageSendStatus(){return h},get MessageType(){return c},get PushMessageType(){return o}});const u={SDK_READY:"sdkReady",SDK_NOT_READY:"sdkNotReady",FEED_LIST_UPDATED:"feedListUpdated",MESSAGE_RECEIVED:"messageReceived",MESSAGE_UPDATED:"messageUpdated",MESSAGE_RECEIPT_RECEIVED:"messageReceiptReceived",ROOM_MESSAGE_RECEIVED:"roomMessageReceived"};var p=Object.freeze({__proto__:null,CHAT_SDK_EVENT:u}),m=Object.freeze({__proto__:null});function S(e,t){return{ok:!1,code:e,msg:t}}function f(e){return{ok:!0,data:e}}const T=e=>{try{return null==e||"string"!=typeof e||0===e.length?null:JSON.parse(e)}catch{return null}},A=e=>{const t=T(e);if(null!==t){let e;return null!=t.content&&(e=T(t.content)??void 0),{client_msg_id:t.client_msg_id,content:e}}return{}};class C{all;constructor(e){this.all=e||new Map}on(e,t){const s=this.all.get(e);return s?s.push(t):this.all.set(e,[t]),()=>{this.off(e,t)}}off(e,t){const s=this.all.get(e);s&&(t?s.splice(s.indexOf(t)>>>0,1):this.all.set(e,[]))}emit(e,t){let s=this.all.get(e);s&&s.slice().map(e=>{e(t)}),s=this.all.get("*"),s&&s.slice().map(s=>{s(e,t)})}once(e,t){if("*"===e){const s=(e,i)=>{t(e,i),this.off("*",s)};this.on(e,s)}else{const s=i=>{t(i),this.off(e,s)};this.on(e,s)}}}class R extends C{url;logger;ws;destroyed;pingTimeout;pongTimeout;reConnectTimeout;constructor(e){super(),this.url=e.url,this.logger=e.logger,this.ws=null,this.destroyed=!1,this.pingTimeout=null,this.pongTimeout=null,this.reConnectTimeout=null,this.reConnect=this.reConnect.bind(this),window.addEventListener("online",this.reConnect),this.connect()}disconnect(){this.ws&&(this.ws.onopen=null,this.ws.onerror=null,this.ws.onmessage=null,this.ws.onclose=null,this.ws.close(),this.ws=null),null!==this.pingTimeout&&(clearTimeout(this.pingTimeout),this.pingTimeout=null),null!==this.pongTimeout&&(clearTimeout(this.pongTimeout),this.pongTimeout=null),null!==this.reConnectTimeout&&(clearTimeout(this.reConnectTimeout),this.reConnectTimeout=null)}connect(){if(this.ws)return void this.logger.warn("ReConnectWebsocket try connect but already connected");const e=new WebSocket(this.url);this.ws=e,e.onopen=()=>{this.delaySendPing()},e.onerror=e=>{this.logger.info("ReConnectWebsocket error",e)},e.onmessage=e=>{try{const t=JSON.parse(e.data);if(t.type===o.PONG_MESSAGE?null!==this.pongTimeout&&(clearTimeout(this.pongTimeout),this.pongTimeout=null,this.delaySendPing()):this.sendAck(t.id),t.type===o.BIG_PACKAGE_PUSH)for(const e of t.content.big_push)this.emit("message",e);else this.emit("message",t)}catch(e){this.logger.error("ReConnectWebsocket message parse failed",e)}},e.onclose=e=>{4001===e.code?this.kickOff():this.reConnect()}}reConnect(){this.disconnect(),this.destroyed||(this.reConnectTimeout=setTimeout(()=>{this.reConnectTimeout=null,this.connect()},5e3))}send(e){if(this.destroyed)return!1;if(!this.ws)return this.logger.warn("ReConnectWebsocket send before connect"),!1;try{return this.ws.readyState!==this.ws.OPEN?(this.logger.warn("ReConnectWebsocket send but not open",this.ws.readyState),!1):(this.ws.send(JSON.stringify(e)),!0)}catch(e){return this.logger.warn("ReConnectWebsocket send error",e),!1}}sendAck(e){this.send({type:1,ids:[e]})}destroy(){this.destroyed||(this.destroyed=!0,this.disconnect(),window.removeEventListener("online",this.reConnect))}delaySendPing(){null!==this.pingTimeout&&(clearTimeout(this.pingTimeout),this.pingTimeout=null),this.pingTimeout=setTimeout(()=>{this.pingTimeout=null;this.send({type:0})?this.waitPong():this.reConnect()},2e4)}waitPong(){this.pongTimeout=setTimeout(()=>{this.logger.info("ReConnectWebsocket pong timeout"),this.reConnect()},1e4)}kickOff(){this.destroy(),this.emit("kickoff")}}class D extends C{host;context;logger;ws;constructor(e){super(),this.host=e.host,this.context=e.context,this.logger=e.logger,this.ws=null}connect(){if(!this.context.isLogin)return void this.logger.warn("BusinessWebsocket try connect but not login");if(this.ws)return void this.logger.warn("BusinessWebsocket try connect but already connected");const e=`${this.host}/ws?token=${this.context.loginUser.sessionId}&platform=web`;this.ws=new R({url:e,logger:this.logger}),this.ws.on("message",e=>{this.emit("message",e)}),this.ws.on("kickoff",()=>{this.emit("kickoff")})}disconnect(){this.ws?(this.ws.destroy(),this.ws=null):this.logger.info("BusinessWebsocket already disconnected")}}class y{_isSyncCompleted;map;chatMap;list;waitMap;constructor(){this._isSyncCompleted=!1,this.map=new Map,this.chatMap=new Map,this.waitMap=new Map,this.list=[]}getAll(){const e=[];for(const t of this.list){const s=this.map.get(t);s&&e.push(s)}return e}get(e){return this.map.get(e)??null}getByChatId(e){const t=this.chatMap.get(e);return void 0!==t?this.get(t):null}async waitFor(e){const t=this.get(e);if(t)return t;if(this._isSyncCompleted)return null;let s=this.waitMap.get(e);return s||(s=(()=>{let e=()=>{};return{promise:new Promise(t=>{e=t}),resolve:e}})(),this.waitMap.set(e,s)),await s.promise,this.get(e)}resolveWait(e){const t=this.waitMap.get(e);t&&(t.resolve(!0),this.waitMap.delete(e))}remove(e){const t=this.map.get(e);return!!t&&(this.map.delete(e),this.chatMap.delete(t.chat_id),this.list.splice(this.list.indexOf(e),1),!0)}push(e){this.remove(e.id),this.map.set(e.id,e),this.list.push(e.id),this.chatMap.set(e.chat_id,e.id),this.resolveWait(e.id)}unshift(e){this.remove(e.id),this.map.set(e.id,e),this.list.unshift(e.id),this.chatMap.set(e.chat_id,e.id),this.resolveWait(e.id)}update(e){this.map.has(e.id)&&this.map.set(e.id,e)}markSyncComplete(){this._isSyncCompleted=!0;for(const[e,t]of this.waitMap)t.resolve(!0);this.waitMap.clear()}isSyncCompleted(){return this._isSyncCompleted}reset(){this._isSyncCompleted=!1,this.map.clear(),this.chatMap.clear(),this.waitMap.clear(),this.list=[]}}function w(e){return(new TextEncoder).encode(e).buffer}async function L(e,t){return window.crypto&&window.crypto.subtle?await async function(e,t){const s=w(t),i=w(e),n=await crypto.subtle.importKey("raw",s,{name:"HMAC",hash:{name:"SHA-256"}},!1,["sign"]),o=await crypto.subtle.sign("HMAC",n,i);return Array.from(new Uint8Array(o)).map(e=>e.toString(16).padStart(2,"0")).join("")}(e,t):function(e,t){return s.HmacSHA256(e,t).toString(s.enc.Hex)}(e,t)}const P=e=>{if(null==e)return"";if("object"!=typeof e||Array.isArray(e)){if(Array.isArray(e)){let t="";for(const s of e)t+=P(s);return t}return"string"==typeof e?e:String(e)}{const t=Object.keys(e).sort();let s="";for(const i of t)s+=P(e[i]);return s}};function O(){return S(n.MAKE_SIGN_FAIL,"make sign fail")}class I{api;context;logger;constructor(e){this.api=t.create({baseURL:e.host,headers:{"x-developer":e.developer}}),this.context=e.context,this.logger=e.logger}async request(e){try{const{needSign:t=!0,...s}=e;if(s.headers||(s.headers={}),s.headers.platform="web",t&&"POST"===e.method?.toUpperCase()&&s.data){if(!this.context.isLogin)return this.logger.warn("HttpClient needs make sign but not login"),O();try{const e=await(async(e,t)=>{const s=await P(e);return await L(s,t)})(s.data,this.context.loginUser.secretKey);s.headers.sign=e}catch(e){return this.logger.error("HttpClient make sign error",e),O()}}const i=await this.api(s),o=i.data;return o.code===n.SUCCESS?{ok:!0,data:o.data}:(this.logger.info("HttpClient server error",i),{ok:!1,code:o.code,msg:o.msg,msg_i18n:o.msg_i18n})}catch{return{ok:!1,code:n.NETWORK_ERROR,msg:"network error"}}}get(e,t,s){return this.request({url:e,method:"GET",params:t,...s})}post(e,t,s){return this.request({url:e,method:"POST",data:t,...s})}}class U{level;logHandler;constructor(){this.level=E.error,this.logHandler=this.createLogHandler()}getPriority(e){switch(e){case E.error:return 3;case E.warning:return 2;case E.info:return 1;default:return 0}}createLogHandler(){return(e,t,...s)=>{const i=`[tlog] ${(new Date).toISOString().split("T")[0]}`,n=t?`${i} ${t}`:i;switch(e){case E.error:console.error(n,...s);break;case E.warning:console.warn(n,...s);break;case E.info:console.info(n,...s);break;default:console.log(n,...s)}}}log(e,t,...s){this.getPriority(e)>=this.getPriority(this.level)&&this.logHandler(e,t,...s)}setLogLevel(e){this.level=e,this.logHandler=this.createLogHandler()}info(e,...t){this.log(E.info,e,...t)}warn(e,...t){this.log(E.warning,e,...t)}error(e,...t){this.log(E.error,e,...t)}}class M{context;logger;emitter;feedCache;handleKickoff;constructor(e){this.context=e.context,this.logger=e.logger,this.emitter=e.emitter,this.feedCache=e.feedCache,this.handleKickoff=e.onKickoff}emitFeedListUpdated(){this.emitter.emit(u.FEED_LIST_UPDATED,{list:this.feedCache.getAll(),is_sync_completed:this.feedCache.isSyncCompleted()})}processCreateFeed(e){this.feedCache.unshift(e.content),this.emitFeedListUpdated()}processDeleteFeed(e){this.feedCache.remove(e.content.id)&&this.emitFeedListUpdated()}processAddMessage(e){if(!this.context.isLogin)return;const t=this.context.loginUser.userId,s=e.content,i=this.feedCache.getByChatId(s.chat_id);if(null!==i){let e=!1;const n={};if(s.message_kind_id!==a.system&&s.sender_id!==t){e=!0;const t=new Set(i.unread_message_ids);t.add(s.id),n.unread_message_ids=[...t],n.badge=t.size}i.last_message_position<s.position&&(e=!0,n.last_message_position=s.position,n.last_message_send_at=s.send_at,n.last_message=s.content,n.last_message_kind_id=s.message_kind_id,n.last_message_type_id=s.message_type_id),e&&(this.feedCache.unshift({...i,...n}),this.emitFeedListUpdated()),this.emitter.emit(u.MESSAGE_RECEIVED,s)}}processUpdateMessage(e){this.emitter.emit(u.MESSAGE_UPDATED,e.content)}processPushLiveMessage(e){this.emitter.emit(u.ROOM_MESSAGE_RECEIVED,e.content)}processReadMessage(e){if(!this.context.isLogin)return;const t=this.context.loginUser.userId,s=this.feedCache.getByChatId(e.content.chat_id);if(null!==s){let i=!1;const n={};if(e.content.reader_ids.includes(t)&&e.content.message_ids.length>0){i=!0;const t=new Set(s.unread_message_ids);e.content.message_ids.forEach(e=>{t.delete(e)}),n.unread_message_ids=[...t],n.badge=t.size}i&&(this.feedCache.update({...s,...n}),this.emitFeedListUpdated()),this.emitter.emit(u.MESSAGE_RECEIPT_RECEIVED,{feed_id:s.id,message_ids:e.content.message_ids,reader_user_ids:e.content.reader_ids})}}processKickOutDevice(){this.handleKickoff()}process(e){switch(this.logger.info("PushMessageProcessor process",e),e.type){case o.CREATE_FEED:this.processCreateFeed(e);break;case o.DELETE_FEED:this.processDeleteFeed(e);break;case o.ADD_MESSAGE:this.processAddMessage(e);break;case o.UPDATE_MESSAGE:this.processUpdateMessage(e);break;case o.PUSH_LIVE_MESSAGE:this.processPushLiveMessage(e);break;case o.READ_MESSAGE:this.processReadMessage(e);break;case o.KICK_OUT_DEVICE:this.processKickOutDevice()}}}var v=e=>new Promise(t=>setTimeout(t,e));class k{destroyed;promise;constructor(){this.destroyed=!1;let e=()=>{};const t=new Promise(t=>{e=t});this.promise=t,navigator.locks.request("typex-sdk",{ifAvailable:!0},async t=>{if(t)for(e(!0);!this.destroyed;)await v(1e4);else e(!1)})}async request(){return await this.promise}}class G{static key="__typex_sdk_data__";static set(e){localStorage.setItem(this.key,JSON.stringify(e))}static get(){try{const e=localStorage.getItem(this.key);if(!e)return null;return JSON.parse(e)}catch{return null}}static clear(){localStorage.removeItem(this.key)}}class N{version;constructor(){this.version=0}increment(){this.version++}get(){return this.version}check(e){return e===this.version}}class x extends C{context;version;singletonLock;logger;httpClient;feedCache;ws;pushMessageProcessor;constructor(e){super(),this.context={isLogin:!1,isReady:!1,deviceId:null},this.version=new N,this.singletonLock=new k,this.logger=new U,this.httpClient=new I({host:e.httpHost,context:this.context,logger:this.logger,developer:e.developer}),this.feedCache=new y,this.ws=new D({host:e.websocketHost,context:this.context,logger:this.logger}),this.pushMessageProcessor=new M({context:this.context,logger:this.logger,emitter:this,feedCache:this.feedCache,onKickoff:()=>{this.handleKickoff()}}),this.init()}init(){this.loadContext(),this.ws.on("message",e=>{this.pushMessageProcessor.process(e)}),this.ws.on("kickoff",()=>{this.handleKickoff()})}async requestDeviceId(){if(this.context.deviceId)return!0;const e=await this.httpClient.post("/device_api/device_id",{platform:"web",hash_str:""},{needSign:!1});return!!e.ok&&(this.context.deviceId=e.data,this.saveContext(),!0)}saveContext(){const{context:e}=this;G.set({deviceId:e.deviceId})}loadContext(){const e=G.get();e&&e.deviceId&&(this.context.deviceId=e.deviceId)}applyLoginStatus(){this.version.increment(),this.saveContext(),this.context.isLogin?(this.loadFeedList(),this.ws.connect(),this.context.isReady=!0,this.emit(u.SDK_READY)):(this.ws.disconnect(),this.feedCache.reset(),this.context.isReady=!1,this.emit(u.SDK_NOT_READY))}async loadFeedList(){const e=this.version.get(),t={refresh_at:null,feed_id:null};for(;;){const s=await this.httpClient.get("/feed",{limit:500,last_refresh_at:t.refresh_at??void 0,last_feed_id:t.feed_id??void 0});if(!this.version.check(e))break;if(!s.ok)break;if(t.refresh_at=s.data.refresh_at,t.feed_id=s.data.last_feed_id,s.data.feed_list.forEach(e=>{this.feedCache.push(e)}),!s.data.has_more)break}this.version.check(e)&&(this.feedCache.markSyncComplete(),this.emit(u.FEED_LIST_UPDATED,{list:this.feedCache.getAll(),is_sync_completed:!0}))}isReady(){return this.context.isReady}setLogLevel(e){this.logger.setLogLevel(e)}async login(t){if(!await this.singletonLock.request())return this.logger.error("Another active TypeX SDK instance detected"),S(n.REQUEST_SINGLETON_LOCK_FAIL,"Another active TypeX SDK instance detected");if(await this.requestDeviceId(),!this.context.deviceId)return this.logger.error("Get deviceId failed"),S(n.SDK_ERROR,"Login params invalid");const s=await this.httpClient.post("/user/session/login",{userCode:t.user_code,device_id:this.context.deviceId},{needSign:!1});return s.ok?(this.context.isLogin=!0,this.context.isLogin&&(this.context.loginUser={userId:s.data.id,secretKey:s.data.key,sessionId:e.get("sessionid")??""}),this.applyLoginStatus(),f(null)):s}async logout(){const e=await this.httpClient.get("/user/logout");return e.ok?(this.context.isLogin=!1,this.applyLoginStatus(),f(null)):e}async handleKickoff(){this.context.isLogin=!1,this.applyLoginStatus()}getLoginUser(){return this.context.isLogin?this.context.loginUser.userId:null}async createSingleChatFeed(e){return await this.httpClient.post("/chat/single",{receiver_id:e})}async deleteFeed(e){return await this.httpClient.post("/feed/delete",{feed_id_list:[e],delete_peer:!0})}async getFeedList(){return f({list:this.feedCache.getAll(),is_sync_completed:this.feedCache.isSyncCompleted()})}async getFeedDetail(e){return f(await this.feedCache.waitFor(e))}async getFeedMessageList(e){const t=await this.getFeedDetail(e.feed_id);if(t.ok&&t.data){const s=t.data,{chat_id:i,first_position:n,last_message_position:o}=s,r=(e=>{const t=[];let s=e.lastPosition??e.maxPosition;for(;t.length<e.limit&&s>=e.minPosition;)t.push(s),s--;return t})({maxPosition:o,minPosition:n,lastPosition:e.last_message_position,limit:e.limit});if(r.length>0){const e=await this.httpClient.post("/message/get_by_position",{chat_position:{[i]:r}},{needSign:!1});if(e.ok&&e.data[i]){const t=e.data[i].reverse();return f({has_more:r[0]===n,list:t,last_message_position:t[0]?.position})}}}return f({has_more:!1,list:[]})}createCustomMessage(e){if(!this.context.isLogin)return S(n.SDK_ERROR,"not login");const t=this.feedCache.get(e.feed_id);if(null===t)return S(n.SDK_ERROR,"not found feed");var s;return f({id:"",chat_id:t.chat_id,position:t.last_message_position,sender_id:this.context.loginUser.userId,message_kind_id:a.text,message_type_id:c.custom,send_at:"",reader_ids:[],hide_ids:[],operation:l.normal,operation_user_id:"0",content:(s={client_msg_id:e.client_msg_id,content:e.content},JSON.stringify({client_msg_id:s.client_msg_id,content:JSON.stringify(s.content)}))})}async sendMessageToFeed(e){if(e.message_type_id===c.custom){const t=A(e.content);return this.httpClient.post("/message/send_custom",{chat_id:e.chat_id,client_id:t.client_msg_id,content:null==t.content?void 0:JSON.stringify(t.content)})}return S(n.SDK_ERROR,"not support")}async sendMessageReadReceipt(e){const t=this.feedCache.get(e.feed_id);return null===t?S(n.SDK_ERROR,"not found feed"):await this.httpClient.post("/message/read",{chat_id:t.chat_id,message_ids:e.message_ids})}async joinRoom(e){return await this.httpClient.post("/liveshow/join",{room_id:e})}async leaveRoom(e){return await this.httpClient.post("/liveshow/leave",{room_id:e})}async getRoomRecentMessageList(e){const t=await this.httpClient.get("/liveshow/get_recent_message",{room_id:e});return t.ok?f(t.data):t}async sendMessageToRoom(e){return await this.httpClient.post("/liveshow/send_message",{room_id:e.room_id,content:e.content})}}function F(e){return new x(e)}const K={parseSystemMessageContent:e=>{const t=T(e);return null!==t?{text:t.text,text_i18n:t.text_i18n}:{}},parseTextMessageContent:e=>{const t=T(e);return null!==t?{text:t.text,client_msg_id:t.client_msg_id}:{}},parseCustomMessageContent:A};export{x as ChatSDK,F as create,g as enums,p as events,m as interfaces,K as utils};
|