paul-ai-assistant 1.0.5 → 1.0.7
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 +48 -0
- package/es/AssistantChat/AssistantChatApp/index.js +4 -2
- package/es/AssistantChat/AssistantChatCard/OperationCard/AigcTipsRender/index.js +1 -1
- package/es/AssistantChat/AssistantChatCard/OperationCard/FeedBack/index.js +21 -1
- package/es/AssistantChat/AssistantChatInput/index.scss +5 -0
- package/es/components/InspectionCard/index.js +1 -2
- package/es/context/index.js +220 -67
- package/es/hooks/index.js +1 -0
- package/es/hooks/useAutoSend.js +95 -0
- package/es/index.js +8 -1
- package/es/service/conversation.js +74 -0
- package/es/service/index.js +1 -0
- package/es/utils/apushAssistant.js +31 -20
- package/es/utils/im.js +395 -0
- package/es/utils/request.js +16 -12
- package/package.json +2 -1
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { useCallback, useRef, useEffect } from 'react';
|
|
2
|
+
import { assistantChat } from "../service/conversation";
|
|
3
|
+
import { getAssistantWsStatus, reconnectAssistantWs } from "../utils/apushAssistant";
|
|
4
|
+
import { REQUEST_SUCCESS_CODE } from "../constants/common";
|
|
5
|
+
/**
|
|
6
|
+
* 内置自动发送消息逻辑的 Hook
|
|
7
|
+
* 如果用户传入了 onSend,直接返回用户的 onSend
|
|
8
|
+
* 否则返回内置的发送逻辑
|
|
9
|
+
*/
|
|
10
|
+
export function useAutoSend(props) {
|
|
11
|
+
var bizId = props.bizId,
|
|
12
|
+
_props$bizDomain = props.bizDomain,
|
|
13
|
+
bizDomain = _props$bizDomain === void 0 ? 'CHAT' : _props$bizDomain,
|
|
14
|
+
userOnSend = props.onSend,
|
|
15
|
+
onSendSuccess = props.onSendSuccess,
|
|
16
|
+
onSendError = props.onSendError;
|
|
17
|
+
var pendingSendRef = useRef(null);
|
|
18
|
+
|
|
19
|
+
// 监听 WebSocket 连接状态,处理待发送消息
|
|
20
|
+
useEffect(function () {
|
|
21
|
+
if (pendingSendRef.current) {
|
|
22
|
+
var _getAssistantWsStatus;
|
|
23
|
+
var status = (getAssistantWsStatus === null || getAssistantWsStatus === void 0 || (_getAssistantWsStatus = getAssistantWsStatus()) === null || _getAssistantWsStatus === void 0 ? void 0 : _getAssistantWsStatus.status) || 'disconnected';
|
|
24
|
+
if (status === 'connected') {
|
|
25
|
+
var pendingSend = pendingSendRef.current;
|
|
26
|
+
pendingSendRef.current = null;
|
|
27
|
+
pendingSend();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}, [bizId]);
|
|
31
|
+
|
|
32
|
+
// 内置 onSend 逻辑
|
|
33
|
+
var internalOnSend = useCallback(function (data, onSuccessCallback) {
|
|
34
|
+
var _getAssistantWsStatus2;
|
|
35
|
+
var _ref = (data === null || data === void 0 ? void 0 : data.params) || {},
|
|
36
|
+
query = _ref.query;
|
|
37
|
+
if (!bizId) {
|
|
38
|
+
console.warn('useAutoSend: bizId is required for sending message');
|
|
39
|
+
onSendError === null || onSendError === void 0 || onSendError(new Error('bizId is required'));
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
var doSend = function doSend() {
|
|
43
|
+
var _data$params;
|
|
44
|
+
assistantChat({
|
|
45
|
+
subjectId: String(bizId),
|
|
46
|
+
content: query,
|
|
47
|
+
featureConfig: (data === null || data === void 0 ? void 0 : data.featureConfig) || {},
|
|
48
|
+
attachments: (data === null || data === void 0 || (_data$params = data.params) === null || _data$params === void 0 ? void 0 : _data$params.attachments) || []
|
|
49
|
+
}).then(function (res) {
|
|
50
|
+
if ((res === null || res === void 0 ? void 0 : res.code) === REQUEST_SUCCESS_CODE) {
|
|
51
|
+
onSuccessCallback === null || onSuccessCallback === void 0 || onSuccessCallback(res);
|
|
52
|
+
onSendSuccess === null || onSendSuccess === void 0 || onSendSuccess(res);
|
|
53
|
+
} else {
|
|
54
|
+
onSendError === null || onSendError === void 0 || onSendError(res);
|
|
55
|
+
}
|
|
56
|
+
}).catch(function (error) {
|
|
57
|
+
console.error('useAutoSend: send message failed', error);
|
|
58
|
+
onSendError === null || onSendError === void 0 || onSendError(error);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// 检查 WebSocket 连接状态
|
|
63
|
+
var status = (getAssistantWsStatus === null || getAssistantWsStatus === void 0 || (_getAssistantWsStatus2 = getAssistantWsStatus()) === null || _getAssistantWsStatus2 === void 0 ? void 0 : _getAssistantWsStatus2.status) || 'disconnected';
|
|
64
|
+
if (status === 'connected') {
|
|
65
|
+
doSend();
|
|
66
|
+
} else {
|
|
67
|
+
// 保存待发送消息
|
|
68
|
+
pendingSendRef.current = doSend;
|
|
69
|
+
|
|
70
|
+
// 如果连接断开,触发重连
|
|
71
|
+
if (status === 'disconnected') {
|
|
72
|
+
reconnectAssistantWs === null || reconnectAssistantWs === void 0 || reconnectAssistantWs({
|
|
73
|
+
bizId: String(bizId),
|
|
74
|
+
bizDomain: bizDomain
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 设置超时,避免无限等待
|
|
79
|
+
setTimeout(function () {
|
|
80
|
+
if (pendingSendRef.current === doSend) {
|
|
81
|
+
pendingSendRef.current = null;
|
|
82
|
+
// 超时后直接尝试发送
|
|
83
|
+
doSend();
|
|
84
|
+
}
|
|
85
|
+
}, 5000);
|
|
86
|
+
}
|
|
87
|
+
}, [bizId, bizDomain, onSendSuccess, onSendError]);
|
|
88
|
+
|
|
89
|
+
// 如果用户传入了 onSend,直接返回用户的
|
|
90
|
+
if (userOnSend) {
|
|
91
|
+
return userOnSend;
|
|
92
|
+
}
|
|
93
|
+
return internalOnSend;
|
|
94
|
+
}
|
|
95
|
+
export default useAutoSend;
|
package/es/index.js
CHANGED
|
@@ -25,6 +25,9 @@ import { ASSIST_CHAT_EVENT } from "./constants/assistChatEvent";
|
|
|
25
25
|
import { BIZ_DOMAIN_TYPE } from "./constants/common";
|
|
26
26
|
import ErrorBoundary from "./ErrorBoundary";
|
|
27
27
|
import { parseLlmComponentUrl } from "./utils/parseUrl";
|
|
28
|
+
// 开箱即用相关导出
|
|
29
|
+
import { getConversationConnection, assistantChat } from "./service/conversation";
|
|
30
|
+
import { useAutoSend } from "./hooks/useAutoSend";
|
|
28
31
|
import "./index.scss";
|
|
29
32
|
|
|
30
33
|
// 导出大模型卡片组件
|
|
@@ -60,4 +63,8 @@ registerAssistChatForm,
|
|
|
60
63
|
// 表单组件
|
|
61
64
|
PaulButtonGroupProps, SelectBlurry, ArrayInput,
|
|
62
65
|
// utils
|
|
63
|
-
parseLlmComponentUrl, ShareContent
|
|
66
|
+
parseLlmComponentUrl, ShareContent,
|
|
67
|
+
// 开箱即用相关
|
|
68
|
+
getConversationConnection, assistantChat, useAutoSend };
|
|
69
|
+
|
|
70
|
+
// 导出开箱即用相关类型
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
|
|
2
|
+
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
|
|
3
|
+
import { customRequest } from "../utils/request";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 会话连接信息
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 获取会话连接 API 响应
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 获取会话连接信息
|
|
15
|
+
* 用于自动创建或获取现有会话
|
|
16
|
+
* @param params 请求参数
|
|
17
|
+
* @returns Promise<IConversationConnectionResponse>
|
|
18
|
+
*/
|
|
19
|
+
export function getConversationConnection(_x) {
|
|
20
|
+
return _getConversationConnection.apply(this, arguments);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 发送聊天消息
|
|
25
|
+
* @param params 消息参数
|
|
26
|
+
* @returns Promise<any>
|
|
27
|
+
*/
|
|
28
|
+
function _getConversationConnection() {
|
|
29
|
+
_getConversationConnection = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee(params) {
|
|
30
|
+
return _regeneratorRuntime().wrap(function _callee$(_context) {
|
|
31
|
+
while (1) switch (_context.prev = _context.next) {
|
|
32
|
+
case 0:
|
|
33
|
+
return _context.abrupt("return", customRequest({
|
|
34
|
+
method: 'GET',
|
|
35
|
+
url: '/cognipilot/api/conversation/connection',
|
|
36
|
+
query: {
|
|
37
|
+
bizDomain: params.bizDomain || 'CHAT',
|
|
38
|
+
subjectId: params.subjectId
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
case 1:
|
|
42
|
+
case "end":
|
|
43
|
+
return _context.stop();
|
|
44
|
+
}
|
|
45
|
+
}, _callee);
|
|
46
|
+
}));
|
|
47
|
+
return _getConversationConnection.apply(this, arguments);
|
|
48
|
+
}
|
|
49
|
+
export function assistantChat(_x2) {
|
|
50
|
+
return _assistantChat.apply(this, arguments);
|
|
51
|
+
}
|
|
52
|
+
function _assistantChat() {
|
|
53
|
+
_assistantChat = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee2(params) {
|
|
54
|
+
return _regeneratorRuntime().wrap(function _callee2$(_context2) {
|
|
55
|
+
while (1) switch (_context2.prev = _context2.next) {
|
|
56
|
+
case 0:
|
|
57
|
+
return _context2.abrupt("return", customRequest({
|
|
58
|
+
method: 'POST',
|
|
59
|
+
url: '/cognipilot/api/assistant/chat',
|
|
60
|
+
query: {
|
|
61
|
+
subjectId: params.subjectId,
|
|
62
|
+
content: params.content,
|
|
63
|
+
featureConfig: params.featureConfig || {},
|
|
64
|
+
attachments: params.attachments || []
|
|
65
|
+
}
|
|
66
|
+
}));
|
|
67
|
+
case 1:
|
|
68
|
+
case "end":
|
|
69
|
+
return _context2.stop();
|
|
70
|
+
}
|
|
71
|
+
}, _callee2);
|
|
72
|
+
}));
|
|
73
|
+
return _assistantChat.apply(this, arguments);
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { getConversationConnection, assistantChat } from "./conversation";
|
|
@@ -4,6 +4,7 @@ import { customRequest } from "./request";
|
|
|
4
4
|
import { JsonParse } from "./formatData";
|
|
5
5
|
import { AesCustomLogEventEnum } from "./log";
|
|
6
6
|
import { logger } from "./websocketLogger";
|
|
7
|
+
import IM from "./im";
|
|
7
8
|
|
|
8
9
|
// 扩展 Window 接口,声明全局属性
|
|
9
10
|
|
|
@@ -50,29 +51,38 @@ var msgCallback = function msgCallback(v) {
|
|
|
50
51
|
return;
|
|
51
52
|
}
|
|
52
53
|
|
|
53
|
-
// 检查 ticketId 是否匹配
|
|
54
|
+
// 检查 ticketId 或 subjectId 是否匹配
|
|
55
|
+
// STREAM 消息使用 subjectId,其他消息使用 ticketId
|
|
54
56
|
var ticketId = argJson === null || argJson === void 0 ? void 0 : argJson.ticketId;
|
|
55
|
-
var
|
|
57
|
+
var subjectId = argJson === null || argJson === void 0 ? void 0 : argJson.subjectId;
|
|
58
|
+
var isMatch = ticketId === bizId || subjectId === bizId;
|
|
56
59
|
if (isMatch) {
|
|
57
60
|
event.emit(msgType, argJson);
|
|
58
61
|
logger.debug('消息已分发', {
|
|
59
62
|
msgType: msgType,
|
|
60
|
-
ticketId: ticketId
|
|
63
|
+
ticketId: ticketId,
|
|
64
|
+
subjectId: subjectId
|
|
61
65
|
});
|
|
62
66
|
} else {
|
|
63
67
|
// 消息被过滤,记录调试日志
|
|
64
|
-
logger.debug('消息 ticketId 不匹配,已过滤', {
|
|
68
|
+
logger.debug('消息 ticketId/subjectId 不匹配,已过滤', {
|
|
65
69
|
expected: bizId,
|
|
66
|
-
|
|
70
|
+
receivedTicketId: ticketId,
|
|
71
|
+
receivedSubjectId: subjectId,
|
|
67
72
|
msgType: msgType
|
|
68
73
|
});
|
|
69
74
|
}
|
|
70
75
|
};
|
|
71
|
-
var callback = function callback(
|
|
76
|
+
var callback = function callback(message, bizId) {
|
|
72
77
|
try {
|
|
78
|
+
// message 是完整的 WebSocket 消息对象,真正的消息体在 message.msgBody 中
|
|
79
|
+
var msgBody = (message === null || message === void 0 ? void 0 : message.msgBody) || message;
|
|
73
80
|
var _ref3 = msgBody || {},
|
|
74
|
-
|
|
75
|
-
msgDetail = _ref3.msgDetail
|
|
81
|
+
msgVersion = _ref3.msgVersion,
|
|
82
|
+
msgDetail = _ref3.msgDetail,
|
|
83
|
+
msgType = _ref3.msgType;
|
|
84
|
+
// 兼容旧版本的 version 字段
|
|
85
|
+
var version = msgVersion || (msgBody === null || msgBody === void 0 ? void 0 : msgBody.version);
|
|
76
86
|
if (version === '1.0.0') {
|
|
77
87
|
var _msgDetail$, _msgDetail$2;
|
|
78
88
|
if (msgDetail !== null && msgDetail !== void 0 && (_msgDetail$ = msgDetail[0]) !== null && _msgDetail$ !== void 0 && _msgDetail$.arg && msgDetail !== null && msgDetail !== void 0 && (_msgDetail$2 = msgDetail[0]) !== null && _msgDetail$2 !== void 0 && _msgDetail$2.app) {
|
|
@@ -93,8 +103,8 @@ var callback = function callback(msgBody, bizId) {
|
|
|
93
103
|
} else if (version === '2.0.0' || !version) {
|
|
94
104
|
// version 不传默认为 2.0.0
|
|
95
105
|
msgCallback({
|
|
96
|
-
msgType:
|
|
97
|
-
arg:
|
|
106
|
+
msgType: msgType,
|
|
107
|
+
arg: msgDetail,
|
|
98
108
|
bizId: bizId
|
|
99
109
|
});
|
|
100
110
|
}
|
|
@@ -191,15 +201,16 @@ export var startAssistantWs = function startAssistantWs(wsProps) {
|
|
|
191
201
|
// const url = window.location.host.includes('aliyun.com') ? Landlord.paulAiAliyunCom : 'paul-ai.aliyun.work';
|
|
192
202
|
|
|
193
203
|
var serverDomain = function serverDomain() {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
204
|
+
return 'ai-space.work';
|
|
205
|
+
// if (isThirdEmbed) {
|
|
206
|
+
// return 'paul.aliyun.com';
|
|
207
|
+
// }
|
|
208
|
+
// if (window.location.host === 'pre.gts.work') {
|
|
209
|
+
// return 'pre-paul-ai.aliyun.work';
|
|
210
|
+
// } else if (window.location.host === 'gts.work') {
|
|
211
|
+
// return 'paul-ai.aliyun.work';
|
|
212
|
+
// }
|
|
213
|
+
// return window.location.host;
|
|
203
214
|
};
|
|
204
215
|
var url = "wss://".concat(serverDomain(), "/paulWebPush/ws");
|
|
205
216
|
var webPushTokenUrl = '/sso/webpush/getToken';
|
|
@@ -239,11 +250,11 @@ export var startAssistantWs = function startAssistantWs(wsProps) {
|
|
|
239
250
|
});
|
|
240
251
|
}
|
|
241
252
|
};
|
|
253
|
+
console.log(appId, userId, '=====');
|
|
242
254
|
var im = new IM(_objectSpread({
|
|
243
255
|
url: url,
|
|
244
256
|
appKey: "".concat(appId, ",").concat(userId),
|
|
245
257
|
// appKey: `${appId},1000`,
|
|
246
|
-
|
|
247
258
|
commonParams: {
|
|
248
259
|
appId: appId,
|
|
249
260
|
userId: userId
|
package/es/utils/im.js
ADDED
|
@@ -0,0 +1,395 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
|
|
3
|
+
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
|
|
4
|
+
import _classCallCheck from "@babel/runtime/helpers/esm/classCallCheck";
|
|
5
|
+
import _createClass from "@babel/runtime/helpers/esm/createClass";
|
|
6
|
+
import _defineProperty from "@babel/runtime/helpers/esm/defineProperty";
|
|
7
|
+
/**
|
|
8
|
+
* @file WebSocket IM 客户端
|
|
9
|
+
* 本地实现,替代 @alife/magic-chat-im
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { logger } from "./websocketLogger";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* IM 配置选项
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 事件类型
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* 事件回调类型
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 重连配置
|
|
28
|
+
*/
|
|
29
|
+
var RECONNECT_INTERVAL_BASE = 1000; // 基础重连间隔 1 秒
|
|
30
|
+
var RECONNECT_INTERVAL_MAX = 30000; // 最大重连间隔 30 秒
|
|
31
|
+
var HEARTBEAT_INTERVAL = 30000; // 心跳间隔 30 秒
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* IM WebSocket 客户端
|
|
35
|
+
*/
|
|
36
|
+
var IM = /*#__PURE__*/function () {
|
|
37
|
+
function IM(options) {
|
|
38
|
+
_classCallCheck(this, IM);
|
|
39
|
+
_defineProperty(this, "url", void 0);
|
|
40
|
+
_defineProperty(this, "appKey", void 0);
|
|
41
|
+
_defineProperty(this, "commonParams", void 0);
|
|
42
|
+
_defineProperty(this, "connectCallback", void 0);
|
|
43
|
+
_defineProperty(this, "token", void 0);
|
|
44
|
+
_defineProperty(this, "getToken", void 0);
|
|
45
|
+
_defineProperty(this, "ws", null);
|
|
46
|
+
_defineProperty(this, "eventListeners", new Map());
|
|
47
|
+
_defineProperty(this, "reconnectAttempts", 0);
|
|
48
|
+
_defineProperty(this, "reconnectTimer", null);
|
|
49
|
+
_defineProperty(this, "heartbeatTimer", null);
|
|
50
|
+
_defineProperty(this, "isManualClose", false);
|
|
51
|
+
_defineProperty(this, "isConnecting", false);
|
|
52
|
+
this.url = options.url;
|
|
53
|
+
this.appKey = options.appKey;
|
|
54
|
+
this.commonParams = options.commonParams;
|
|
55
|
+
this.connectCallback = options.connectCallback;
|
|
56
|
+
this.token = options.token;
|
|
57
|
+
this.getToken = options.getToken;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* 注册事件监听器
|
|
62
|
+
*/
|
|
63
|
+
_createClass(IM, [{
|
|
64
|
+
key: "on",
|
|
65
|
+
value: function on(event, callback) {
|
|
66
|
+
if (!this.eventListeners.has(event)) {
|
|
67
|
+
this.eventListeners.set(event, []);
|
|
68
|
+
}
|
|
69
|
+
this.eventListeners.get(event).push(callback);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 触发事件
|
|
74
|
+
*/
|
|
75
|
+
}, {
|
|
76
|
+
key: "emit",
|
|
77
|
+
value: function emit(event, data) {
|
|
78
|
+
var listeners = this.eventListeners.get(event);
|
|
79
|
+
if (listeners) {
|
|
80
|
+
listeners.forEach(function (callback) {
|
|
81
|
+
try {
|
|
82
|
+
callback(data);
|
|
83
|
+
} catch (e) {
|
|
84
|
+
logger.error("\u4E8B\u4EF6 ".concat(event, " \u56DE\u8C03\u6267\u884C\u5931\u8D25"), e);
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* 建立连接
|
|
92
|
+
*/
|
|
93
|
+
}, {
|
|
94
|
+
key: "connect",
|
|
95
|
+
value: (function () {
|
|
96
|
+
var _connect = _asyncToGenerator( /*#__PURE__*/_regeneratorRuntime().mark(function _callee() {
|
|
97
|
+
var _this = this;
|
|
98
|
+
var token, wsUrl;
|
|
99
|
+
return _regeneratorRuntime().wrap(function _callee$(_context) {
|
|
100
|
+
while (1) switch (_context.prev = _context.next) {
|
|
101
|
+
case 0:
|
|
102
|
+
if (!this.isConnecting) {
|
|
103
|
+
_context.next = 3;
|
|
104
|
+
break;
|
|
105
|
+
}
|
|
106
|
+
logger.warn('正在连接中,跳过重复连接请求');
|
|
107
|
+
return _context.abrupt("return");
|
|
108
|
+
case 3:
|
|
109
|
+
this.isConnecting = true;
|
|
110
|
+
this.isManualClose = false;
|
|
111
|
+
_context.prev = 5;
|
|
112
|
+
// 获取 token
|
|
113
|
+
token = this.token;
|
|
114
|
+
if (!(!token && this.getToken)) {
|
|
115
|
+
_context.next = 11;
|
|
116
|
+
break;
|
|
117
|
+
}
|
|
118
|
+
_context.next = 10;
|
|
119
|
+
return this.getToken();
|
|
120
|
+
case 10:
|
|
121
|
+
token = _context.sent;
|
|
122
|
+
case 11:
|
|
123
|
+
// 构建 WebSocket URL
|
|
124
|
+
wsUrl = this.buildUrl(token);
|
|
125
|
+
logger.info('开始建立 WebSocket 连接', {
|
|
126
|
+
url: this.url
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// 创建 WebSocket 连接
|
|
130
|
+
this.ws = new WebSocket(wsUrl);
|
|
131
|
+
this.ws.onopen = function () {
|
|
132
|
+
var _this$connectCallback;
|
|
133
|
+
logger.info('WebSocket 连接已建立');
|
|
134
|
+
_this.isConnecting = false;
|
|
135
|
+
_this.reconnectAttempts = 0;
|
|
136
|
+
|
|
137
|
+
// 发送鉴权消息
|
|
138
|
+
_this.sendAuth(token);
|
|
139
|
+
|
|
140
|
+
// 启动心跳
|
|
141
|
+
_this.startHeartbeat();
|
|
142
|
+
|
|
143
|
+
// 触发连接回调
|
|
144
|
+
(_this$connectCallback = _this.connectCallback) === null || _this$connectCallback === void 0 || _this$connectCallback.call(_this);
|
|
145
|
+
};
|
|
146
|
+
this.ws.onmessage = function (event) {
|
|
147
|
+
try {
|
|
148
|
+
var _data = JSON.parse(event.data);
|
|
149
|
+
|
|
150
|
+
// 处理心跳响应
|
|
151
|
+
if (_data.type === 'pong' || _data.msgType === 'pong') {
|
|
152
|
+
logger.debug('收到心跳响应');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// 触发消息事件
|
|
157
|
+
_this.emit('message', _data);
|
|
158
|
+
} catch (e) {
|
|
159
|
+
logger.error('消息解析失败', e);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
this.ws.onclose = function (event) {
|
|
163
|
+
logger.info('WebSocket 连接已关闭', {
|
|
164
|
+
code: event.code,
|
|
165
|
+
reason: event.reason,
|
|
166
|
+
wasClean: event.wasClean
|
|
167
|
+
});
|
|
168
|
+
_this.isConnecting = false;
|
|
169
|
+
_this.stopHeartbeat();
|
|
170
|
+
var closeInfo = {
|
|
171
|
+
code: event.wasClean ? 'CONNECTION_CLOSED_CLEANLY' : event.code,
|
|
172
|
+
reason: event.reason
|
|
173
|
+
};
|
|
174
|
+
_this.emit('close', closeInfo);
|
|
175
|
+
|
|
176
|
+
// 非手动关闭时尝试重连
|
|
177
|
+
if (!_this.isManualClose) {
|
|
178
|
+
_this.scheduleReconnect();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
this.ws.onerror = function (error) {
|
|
182
|
+
logger.error('WebSocket 连接错误', error);
|
|
183
|
+
_this.isConnecting = false;
|
|
184
|
+
_this.emit('error', error);
|
|
185
|
+
};
|
|
186
|
+
_context.next = 26;
|
|
187
|
+
break;
|
|
188
|
+
case 20:
|
|
189
|
+
_context.prev = 20;
|
|
190
|
+
_context.t0 = _context["catch"](5);
|
|
191
|
+
this.isConnecting = false;
|
|
192
|
+
logger.error('建立连接失败', _context.t0);
|
|
193
|
+
this.emit('error', _context.t0);
|
|
194
|
+
this.scheduleReconnect();
|
|
195
|
+
case 26:
|
|
196
|
+
case "end":
|
|
197
|
+
return _context.stop();
|
|
198
|
+
}
|
|
199
|
+
}, _callee, this, [[5, 20]]);
|
|
200
|
+
}));
|
|
201
|
+
function connect() {
|
|
202
|
+
return _connect.apply(this, arguments);
|
|
203
|
+
}
|
|
204
|
+
return connect;
|
|
205
|
+
}()
|
|
206
|
+
/**
|
|
207
|
+
* 解析 JWT token 的 payload
|
|
208
|
+
*/
|
|
209
|
+
)
|
|
210
|
+
}, {
|
|
211
|
+
key: "parseJwtPayload",
|
|
212
|
+
value: function parseJwtPayload(token) {
|
|
213
|
+
try {
|
|
214
|
+
var parts = token.split('.');
|
|
215
|
+
if (parts.length !== 3) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
// Base64Url 解码
|
|
219
|
+
var payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
220
|
+
var decoded = atob(payload);
|
|
221
|
+
return JSON.parse(decoded);
|
|
222
|
+
} catch (e) {
|
|
223
|
+
logger.error('JWT 解析失败', e);
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* 构建 WebSocket URL
|
|
230
|
+
* 格式:wss://domain/paulWebPush/ws?param={bizDomain},{userId},{token}
|
|
231
|
+
* 其中 userId 从 token 的 payload 中获取
|
|
232
|
+
*/
|
|
233
|
+
}, {
|
|
234
|
+
key: "buildUrl",
|
|
235
|
+
value: function buildUrl(token) {
|
|
236
|
+
var _this$commonParams;
|
|
237
|
+
var bizDomain = ((_this$commonParams = this.commonParams) === null || _this$commonParams === void 0 ? void 0 : _this$commonParams.appId) || '';
|
|
238
|
+
|
|
239
|
+
// 从 token 中解析 userId
|
|
240
|
+
var userId = '';
|
|
241
|
+
if (token) {
|
|
242
|
+
var payload = this.parseJwtPayload(token);
|
|
243
|
+
userId = (payload === null || payload === void 0 ? void 0 : payload.userId) || '';
|
|
244
|
+
}
|
|
245
|
+
var param = "".concat(bizDomain, ",").concat(userId, ",").concat(token || '');
|
|
246
|
+
return "".concat(this.url, "?param=").concat(param);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 发送鉴权消息
|
|
251
|
+
*/
|
|
252
|
+
}, {
|
|
253
|
+
key: "sendAuth",
|
|
254
|
+
value: function sendAuth(token) {
|
|
255
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
256
|
+
return;
|
|
257
|
+
}
|
|
258
|
+
var authMessage = _objectSpread({
|
|
259
|
+
type: 'auth',
|
|
260
|
+
appKey: this.appKey,
|
|
261
|
+
token: token
|
|
262
|
+
}, this.commonParams);
|
|
263
|
+
this.ws.send(JSON.stringify(authMessage));
|
|
264
|
+
logger.debug('已发送鉴权消息');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* 启动心跳
|
|
269
|
+
*/
|
|
270
|
+
}, {
|
|
271
|
+
key: "startHeartbeat",
|
|
272
|
+
value: function startHeartbeat() {
|
|
273
|
+
var _this2 = this;
|
|
274
|
+
this.stopHeartbeat();
|
|
275
|
+
this.heartbeatTimer = setInterval(function () {
|
|
276
|
+
if (_this2.ws && _this2.ws.readyState === WebSocket.OPEN) {
|
|
277
|
+
_this2.ws.send(JSON.stringify({
|
|
278
|
+
type: 'ping'
|
|
279
|
+
}));
|
|
280
|
+
logger.debug('发送心跳');
|
|
281
|
+
}
|
|
282
|
+
}, HEARTBEAT_INTERVAL);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* 停止心跳
|
|
287
|
+
*/
|
|
288
|
+
}, {
|
|
289
|
+
key: "stopHeartbeat",
|
|
290
|
+
value: function stopHeartbeat() {
|
|
291
|
+
if (this.heartbeatTimer) {
|
|
292
|
+
clearInterval(this.heartbeatTimer);
|
|
293
|
+
this.heartbeatTimer = null;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 安排重连
|
|
299
|
+
*/
|
|
300
|
+
}, {
|
|
301
|
+
key: "scheduleReconnect",
|
|
302
|
+
value: function scheduleReconnect() {
|
|
303
|
+
var _this3 = this;
|
|
304
|
+
if (this.isManualClose) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// 计算重连间隔(指数退避)
|
|
309
|
+
var interval = Math.min(RECONNECT_INTERVAL_BASE * Math.pow(2, this.reconnectAttempts), RECONNECT_INTERVAL_MAX);
|
|
310
|
+
this.reconnectAttempts++;
|
|
311
|
+
logger.info("\u5C06\u5728 ".concat(interval, "ms \u540E\u5C1D\u8BD5\u7B2C ").concat(this.reconnectAttempts, " \u6B21\u91CD\u8FDE"));
|
|
312
|
+
|
|
313
|
+
// 触发重连事件
|
|
314
|
+
this.emit('reconnect', {
|
|
315
|
+
code: 'RECONNECTING',
|
|
316
|
+
err: null,
|
|
317
|
+
attempt: this.reconnectAttempts
|
|
318
|
+
});
|
|
319
|
+
this.reconnectTimer = setTimeout(function () {
|
|
320
|
+
_this3.connect();
|
|
321
|
+
}, interval);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/**
|
|
325
|
+
* 关闭连接
|
|
326
|
+
*/
|
|
327
|
+
}, {
|
|
328
|
+
key: "close",
|
|
329
|
+
value: function close() {
|
|
330
|
+
this.isManualClose = true;
|
|
331
|
+
|
|
332
|
+
// 清除重连定时器
|
|
333
|
+
if (this.reconnectTimer) {
|
|
334
|
+
clearTimeout(this.reconnectTimer);
|
|
335
|
+
this.reconnectTimer = null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// 停止心跳
|
|
339
|
+
this.stopHeartbeat();
|
|
340
|
+
|
|
341
|
+
// 关闭 WebSocket
|
|
342
|
+
if (this.ws) {
|
|
343
|
+
try {
|
|
344
|
+
this.ws.close(1000, 'Manual close');
|
|
345
|
+
} catch (e) {
|
|
346
|
+
logger.error('关闭 WebSocket 失败', e);
|
|
347
|
+
}
|
|
348
|
+
this.ws = null;
|
|
349
|
+
}
|
|
350
|
+
logger.info('WebSocket 连接已手动关闭');
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* 发送消息
|
|
355
|
+
*/
|
|
356
|
+
}, {
|
|
357
|
+
key: "send",
|
|
358
|
+
value: function send(data) {
|
|
359
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) {
|
|
360
|
+
logger.warn('WebSocket 未连接,无法发送消息');
|
|
361
|
+
return false;
|
|
362
|
+
}
|
|
363
|
+
try {
|
|
364
|
+
var message = typeof data === 'string' ? data : JSON.stringify(data);
|
|
365
|
+
this.ws.send(message);
|
|
366
|
+
return true;
|
|
367
|
+
} catch (e) {
|
|
368
|
+
logger.error('发送消息失败', e);
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* 获取连接状态
|
|
375
|
+
*/
|
|
376
|
+
}, {
|
|
377
|
+
key: "getReadyState",
|
|
378
|
+
value: function getReadyState() {
|
|
379
|
+
var _this$ws$readyState, _this$ws;
|
|
380
|
+
return (_this$ws$readyState = (_this$ws = this.ws) === null || _this$ws === void 0 ? void 0 : _this$ws.readyState) !== null && _this$ws$readyState !== void 0 ? _this$ws$readyState : WebSocket.CLOSED;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* 是否已连接
|
|
385
|
+
*/
|
|
386
|
+
}, {
|
|
387
|
+
key: "isConnected",
|
|
388
|
+
value: function isConnected() {
|
|
389
|
+
var _this$ws2;
|
|
390
|
+
return ((_this$ws2 = this.ws) === null || _this$ws2 === void 0 ? void 0 : _this$ws2.readyState) === WebSocket.OPEN;
|
|
391
|
+
}
|
|
392
|
+
}]);
|
|
393
|
+
return IM;
|
|
394
|
+
}();
|
|
395
|
+
export default IM;
|