@zhin.js/client 1.0.3 → 1.0.4
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 +117 -3
- package/app/src/components/PluginConfigForm/BasicFieldRenderers.tsx +253 -0
- package/app/src/components/PluginConfigForm/CollectionFieldRenderers.tsx +261 -0
- package/app/src/components/PluginConfigForm/CompositeFieldRenderers.tsx +105 -0
- package/app/src/components/PluginConfigForm/FieldRenderer.tsx +110 -0
- package/app/src/components/PluginConfigForm/NestedFieldRenderer.tsx +95 -0
- package/app/src/components/PluginConfigForm/index.tsx +237 -0
- package/app/src/components/PluginConfigForm/types.ts +46 -0
- package/app/src/pages/dashboard-plugin-detail.tsx +22 -11
- package/dist/index.js +0 -1
- package/package.json +1 -1
- package/src/index.ts +0 -1
- package/src/store/index.ts +22 -1
- package/src/store/reducers/config.ts +135 -0
- package/src/store/reducers/index.ts +4 -1
- package/src/websocket/hooks.ts +280 -0
- package/src/websocket/index.ts +45 -190
- package/src/websocket/instance.ts +46 -0
- package/src/websocket/manager.ts +412 -0
- package/src/websocket/messageHandler.ts +166 -0
- package/src/websocket/types.ts +208 -0
- package/src/websocket/useWebSocket.ts +0 -42
package/src/websocket/index.ts
CHANGED
|
@@ -1,193 +1,48 @@
|
|
|
1
|
-
import { store, loadScripts, loadScript, unloadScript } from '../store'
|
|
2
1
|
/**
|
|
3
|
-
* WebSocket
|
|
4
|
-
*
|
|
5
|
-
* 脚本加载状态由 Redux store 管理
|
|
2
|
+
* WebSocket 客户端模块
|
|
3
|
+
* 提供统一的 WebSocket 连接管理、消息处理和 React Hook 接口
|
|
6
4
|
*/
|
|
7
5
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
export
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
try {
|
|
53
|
-
this.ws = new WebSocket(this.url)
|
|
54
|
-
|
|
55
|
-
this.ws.onopen = () => {
|
|
56
|
-
this.reconnectAttempts = 0
|
|
57
|
-
this.options.onConnect?.()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
this.ws.onmessage = (event) => {
|
|
61
|
-
try {
|
|
62
|
-
const message: WebSocketMessage = JSON.parse(event.data)
|
|
63
|
-
this.handleMessage(message)
|
|
64
|
-
this.options.onMessage?.(message)
|
|
65
|
-
} catch (error) {
|
|
66
|
-
console.error('[WS] Parse error:', error)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
this.ws.onclose = () => {
|
|
71
|
-
this.options.onDisconnect?.()
|
|
72
|
-
this.attemptReconnect()
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
this.ws.onerror = () => {
|
|
76
|
-
this.options.onError?.(new Event('error'))
|
|
77
|
-
}
|
|
78
|
-
} catch (error) {
|
|
79
|
-
this.attemptReconnect()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* 处理服务器消息
|
|
85
|
-
*/
|
|
86
|
-
private handleMessage(message: WebSocketMessage) {
|
|
87
|
-
switch (message.type) {
|
|
88
|
-
case 'sync':
|
|
89
|
-
// 同步所有入口 - dispatch 到 Redux
|
|
90
|
-
if (message.data?.key === 'entries') {
|
|
91
|
-
const entries = Array.isArray(message.data.value)
|
|
92
|
-
? message.data.value
|
|
93
|
-
: [message.data.value]
|
|
94
|
-
store.dispatch({ type: 'script/syncEntries', payload: entries })
|
|
95
|
-
// 使用 AsyncThunk 加载脚本
|
|
96
|
-
store.dispatch(loadScripts(entries))
|
|
97
|
-
}
|
|
98
|
-
break
|
|
99
|
-
|
|
100
|
-
case 'add':
|
|
101
|
-
// 添加新入口 - dispatch 到 Redux
|
|
102
|
-
if (message.data?.key === 'entries') {
|
|
103
|
-
store.dispatch({ type: 'script/addEntry', payload: message.data.value })
|
|
104
|
-
// 使用 AsyncThunk 加载脚本
|
|
105
|
-
store.dispatch(loadScript(message.data.value))
|
|
106
|
-
}
|
|
107
|
-
break
|
|
108
|
-
|
|
109
|
-
case 'delete':
|
|
110
|
-
// 删除入口 - dispatch 到 Redux
|
|
111
|
-
if (message.data?.key === 'entries') {
|
|
112
|
-
store.dispatch({ type: 'script/removeEntry', payload: message.data.value })
|
|
113
|
-
// 使用 AsyncThunk 卸载脚本
|
|
114
|
-
store.dispatch(unloadScript(message.data.value))
|
|
115
|
-
}
|
|
116
|
-
break
|
|
117
|
-
|
|
118
|
-
case 'init-data':
|
|
119
|
-
case 'data-update':
|
|
120
|
-
break
|
|
121
|
-
|
|
122
|
-
default:
|
|
123
|
-
console.warn('[WS] Unknown type:', message.type)
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* 尝试重新连接
|
|
129
|
-
*/
|
|
130
|
-
private attemptReconnect() {
|
|
131
|
-
if (this.reconnectAttempts >= this.maxReconnectAttempts) return
|
|
132
|
-
|
|
133
|
-
this.reconnectAttempts++
|
|
134
|
-
this.reconnectTimer = setTimeout(() => {
|
|
135
|
-
this.connect()
|
|
136
|
-
}, this.reconnectInterval)
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* 断开连接
|
|
141
|
-
*/
|
|
142
|
-
disconnect() {
|
|
143
|
-
if (this.reconnectTimer) {
|
|
144
|
-
clearTimeout(this.reconnectTimer)
|
|
145
|
-
this.reconnectTimer = null
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
if (this.ws) {
|
|
149
|
-
this.ws.close()
|
|
150
|
-
this.ws = null
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* 发送消息
|
|
156
|
-
*/
|
|
157
|
-
send(message: any) {
|
|
158
|
-
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
159
|
-
this.ws.send(JSON.stringify(message))
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* 检查是否已连接
|
|
165
|
-
*/
|
|
166
|
-
isConnected() {
|
|
167
|
-
return this.ws?.readyState === WebSocket.OPEN
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// 创建全局单例并自动连接
|
|
172
|
-
const globalWebSocketManager = new WebSocketManager()
|
|
173
|
-
|
|
174
|
-
// 模块加载时自动连接
|
|
175
|
-
if (typeof window !== 'undefined') {
|
|
176
|
-
globalWebSocketManager.connect()
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
/**
|
|
180
|
-
* 获取全局 WebSocket 管理器
|
|
181
|
-
*/
|
|
182
|
-
export function getWebSocketManager(): WebSocketManager {
|
|
183
|
-
return globalWebSocketManager
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* 销毁全局 WebSocket 管理器
|
|
188
|
-
*/
|
|
189
|
-
export function destroyWebSocketManager() {
|
|
190
|
-
globalWebSocketManager.disconnect()
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export default WebSocketManager
|
|
6
|
+
// ============================================================================
|
|
7
|
+
// 类型定义
|
|
8
|
+
// ============================================================================
|
|
9
|
+
export * from './types'
|
|
10
|
+
|
|
11
|
+
// ============================================================================
|
|
12
|
+
// 核心类
|
|
13
|
+
// ============================================================================
|
|
14
|
+
export { WebSocketManager } from './manager'
|
|
15
|
+
export { MessageHandler } from './messageHandler'
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// 实例管理
|
|
19
|
+
// ============================================================================
|
|
20
|
+
export {
|
|
21
|
+
getWebSocketManager,
|
|
22
|
+
destroyWebSocketManager,
|
|
23
|
+
resetWebSocketManager
|
|
24
|
+
} from './instance'
|
|
25
|
+
|
|
26
|
+
// ============================================================================
|
|
27
|
+
// React Hooks
|
|
28
|
+
// ============================================================================
|
|
29
|
+
export {
|
|
30
|
+
useWebSocket,
|
|
31
|
+
useConfig,
|
|
32
|
+
useAllConfigs,
|
|
33
|
+
useWebSocketState,
|
|
34
|
+
useWebSocketMessages
|
|
35
|
+
} from './hooks'
|
|
36
|
+
|
|
37
|
+
// ============================================================================
|
|
38
|
+
// 向后兼容的导出(保持与旧代码的兼容性)
|
|
39
|
+
// ============================================================================
|
|
40
|
+
|
|
41
|
+
// 为了保持与现有代码的兼容性,重新导出一些常用的接口
|
|
42
|
+
import { getWebSocketManager } from './instance'
|
|
43
|
+
|
|
44
|
+
// 兼容旧的 useConfig 导出
|
|
45
|
+
export { useConfig as useConfigLegacy } from './hooks'
|
|
46
|
+
|
|
47
|
+
// 兼容旧的 WebSocketManager 默认导出
|
|
48
|
+
export default getWebSocketManager
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket 实例管理
|
|
3
|
+
* 提供全局 WebSocket 管理器单例
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { WebSocketManager } from './manager'
|
|
7
|
+
|
|
8
|
+
// ============================================================================
|
|
9
|
+
// 全局实例
|
|
10
|
+
// ============================================================================
|
|
11
|
+
|
|
12
|
+
let globalWebSocketManager: WebSocketManager | null = null
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 获取全局 WebSocket 管理器实例
|
|
16
|
+
*/
|
|
17
|
+
export function getWebSocketManager(): WebSocketManager {
|
|
18
|
+
if (!globalWebSocketManager) {
|
|
19
|
+
globalWebSocketManager = new WebSocketManager()
|
|
20
|
+
|
|
21
|
+
// 浏览器环境下自动连接
|
|
22
|
+
if (typeof window !== 'undefined') {
|
|
23
|
+
globalWebSocketManager.connect()
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return globalWebSocketManager
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* 销毁全局 WebSocket 管理器
|
|
32
|
+
*/
|
|
33
|
+
export function destroyWebSocketManager(): void {
|
|
34
|
+
if (globalWebSocketManager) {
|
|
35
|
+
globalWebSocketManager.disconnect()
|
|
36
|
+
globalWebSocketManager = null
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 重置 WebSocket 管理器(主要用于测试)
|
|
42
|
+
*/
|
|
43
|
+
export function resetWebSocketManager(): void {
|
|
44
|
+
destroyWebSocketManager()
|
|
45
|
+
// 下次调用 getWebSocketManager() 时会创建新实例
|
|
46
|
+
}
|
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WebSocket 核心管理器
|
|
3
|
+
* 负责 WebSocket 连接管理、消息发送、重连逻辑等核心功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { store, setConnected, updateConfigs, updateSchemas } from '../store'
|
|
7
|
+
import { MessageHandler } from './messageHandler'
|
|
8
|
+
import type {
|
|
9
|
+
WebSocketMessage,
|
|
10
|
+
WebSocketConfig,
|
|
11
|
+
WebSocketCallbacks
|
|
12
|
+
} from './types'
|
|
13
|
+
import {
|
|
14
|
+
ConnectionState,
|
|
15
|
+
WebSocketError,
|
|
16
|
+
ConnectionError,
|
|
17
|
+
MessageError,
|
|
18
|
+
RequestTimeoutError
|
|
19
|
+
} from './types'
|
|
20
|
+
|
|
21
|
+
export class WebSocketManager {
|
|
22
|
+
// ============================================================================
|
|
23
|
+
// 私有属性
|
|
24
|
+
// ============================================================================
|
|
25
|
+
|
|
26
|
+
private ws: WebSocket | null = null
|
|
27
|
+
private config: Required<WebSocketConfig>
|
|
28
|
+
private callbacks: WebSocketCallbacks
|
|
29
|
+
private state: ConnectionState = ConnectionState.DISCONNECTED
|
|
30
|
+
|
|
31
|
+
// 重连相关
|
|
32
|
+
private reconnectAttempts = 0
|
|
33
|
+
private reconnectTimer: NodeJS.Timeout | null = null
|
|
34
|
+
|
|
35
|
+
// 请求管理
|
|
36
|
+
private requestId = 0
|
|
37
|
+
private pendingRequests = new Map<number, {
|
|
38
|
+
resolve: (value: any) => void
|
|
39
|
+
reject: (error: Error) => void
|
|
40
|
+
timer: NodeJS.Timeout
|
|
41
|
+
}>()
|
|
42
|
+
|
|
43
|
+
// ============================================================================
|
|
44
|
+
// 构造函数
|
|
45
|
+
// ============================================================================
|
|
46
|
+
|
|
47
|
+
constructor(config: WebSocketConfig = {}, callbacks: WebSocketCallbacks = {}) {
|
|
48
|
+
this.config = {
|
|
49
|
+
url: this.buildWebSocketUrl(config.url),
|
|
50
|
+
reconnectInterval: config.reconnectInterval ?? 3000,
|
|
51
|
+
maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
|
|
52
|
+
requestTimeout: config.requestTimeout ?? 10000
|
|
53
|
+
}
|
|
54
|
+
this.callbacks = callbacks
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ============================================================================
|
|
58
|
+
// 公共 API
|
|
59
|
+
// ============================================================================
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* 建立 WebSocket 连接
|
|
63
|
+
*/
|
|
64
|
+
connect(): void {
|
|
65
|
+
if (this.state === ConnectionState.CONNECTED || this.state === ConnectionState.CONNECTING) {
|
|
66
|
+
return
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.setState(ConnectionState.CONNECTING)
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
this.ws = new WebSocket(this.config.url)
|
|
73
|
+
this.attachEventHandlers()
|
|
74
|
+
} catch (error) {
|
|
75
|
+
this.handleConnectionError(new ConnectionError('Failed to create WebSocket', error as Error))
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 断开 WebSocket 连接
|
|
81
|
+
*/
|
|
82
|
+
disconnect(): void {
|
|
83
|
+
this.clearReconnectTimer()
|
|
84
|
+
this.clearPendingRequests()
|
|
85
|
+
|
|
86
|
+
if (this.ws) {
|
|
87
|
+
this.ws.close()
|
|
88
|
+
this.ws = null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
this.setState(ConnectionState.DISCONNECTED)
|
|
92
|
+
store.dispatch(setConnected(false))
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* 发送普通消息
|
|
97
|
+
*/
|
|
98
|
+
send(message: any): void {
|
|
99
|
+
if (!this.isConnected()) {
|
|
100
|
+
throw new WebSocketError('WebSocket is not connected', 'NOT_CONNECTED')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
this.ws!.send(JSON.stringify(message))
|
|
105
|
+
} catch (error) {
|
|
106
|
+
throw new MessageError('Failed to send message', error as Error)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 发送请求并等待响应
|
|
112
|
+
*/
|
|
113
|
+
async sendRequest<T = any>(message: any): Promise<T> {
|
|
114
|
+
if (!this.isConnected()) {
|
|
115
|
+
throw new WebSocketError('WebSocket is not connected', 'NOT_CONNECTED')
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const requestId = ++this.requestId
|
|
120
|
+
const messageWithId = { ...message, requestId }
|
|
121
|
+
|
|
122
|
+
// 设置超时计时器
|
|
123
|
+
const timer = setTimeout(() => {
|
|
124
|
+
this.pendingRequests.delete(requestId)
|
|
125
|
+
reject(new RequestTimeoutError(requestId))
|
|
126
|
+
}, this.config.requestTimeout)
|
|
127
|
+
|
|
128
|
+
// 存储请求信息
|
|
129
|
+
this.pendingRequests.set(requestId, { resolve, reject, timer })
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
this.ws!.send(JSON.stringify(messageWithId))
|
|
133
|
+
} catch (error) {
|
|
134
|
+
this.pendingRequests.delete(requestId)
|
|
135
|
+
clearTimeout(timer)
|
|
136
|
+
reject(new MessageError('Failed to send request', error as Error))
|
|
137
|
+
}
|
|
138
|
+
})
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* 检查连接状态
|
|
143
|
+
*/
|
|
144
|
+
isConnected(): boolean {
|
|
145
|
+
return this.ws?.readyState === WebSocket.OPEN
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* 获取当前状态
|
|
150
|
+
*/
|
|
151
|
+
getState(): ConnectionState {
|
|
152
|
+
return this.state
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// 配置管理 API
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* 获取插件配置
|
|
161
|
+
*/
|
|
162
|
+
async getConfig(pluginName: string): Promise<any> {
|
|
163
|
+
return this.sendRequest({
|
|
164
|
+
type: 'config:get',
|
|
165
|
+
pluginName
|
|
166
|
+
})
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* 设置插件配置
|
|
171
|
+
*/
|
|
172
|
+
async setConfig(pluginName: string, config: any): Promise<void> {
|
|
173
|
+
await this.sendRequest({
|
|
174
|
+
type: 'config:set',
|
|
175
|
+
pluginName,
|
|
176
|
+
data: config
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 获取插件 Schema
|
|
182
|
+
*/
|
|
183
|
+
async getSchema(pluginName: string): Promise<any> {
|
|
184
|
+
return this.sendRequest({
|
|
185
|
+
type: 'schema:get',
|
|
186
|
+
pluginName
|
|
187
|
+
})
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* 获取所有配置
|
|
192
|
+
*/
|
|
193
|
+
async getAllConfigs(): Promise<Record<string, any>> {
|
|
194
|
+
return this.sendRequest({
|
|
195
|
+
type: 'config:get-all'
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* 获取所有 Schema
|
|
201
|
+
*/
|
|
202
|
+
async getAllSchemas(): Promise<Record<string, any>> {
|
|
203
|
+
return this.sendRequest({
|
|
204
|
+
type: 'schema:get-all'
|
|
205
|
+
})
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// ============================================================================
|
|
209
|
+
// 私有方法
|
|
210
|
+
// ============================================================================
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 构建 WebSocket URL
|
|
214
|
+
*/
|
|
215
|
+
private buildWebSocketUrl(customUrl?: string): string {
|
|
216
|
+
if (customUrl) {
|
|
217
|
+
return customUrl
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
221
|
+
const host = window.location.host
|
|
222
|
+
return `${protocol}//${host}/server`
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* 设置连接状态
|
|
227
|
+
*/
|
|
228
|
+
private setState(newState: ConnectionState): void {
|
|
229
|
+
const oldState = this.state
|
|
230
|
+
this.state = newState
|
|
231
|
+
|
|
232
|
+
console.log(`[WebSocket] State changed: ${oldState} -> ${newState}`)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* 绑定事件处理器
|
|
237
|
+
*/
|
|
238
|
+
private attachEventHandlers(): void {
|
|
239
|
+
if (!this.ws) return
|
|
240
|
+
|
|
241
|
+
this.ws.onopen = () => {
|
|
242
|
+
this.handleConnectionOpen()
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
this.ws.onmessage = (event) => {
|
|
246
|
+
this.handleMessage(event)
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
this.ws.onclose = (event) => {
|
|
250
|
+
this.handleConnectionClose(event)
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
this.ws.onerror = (event) => {
|
|
254
|
+
this.handleConnectionError(new ConnectionError('WebSocket error', event as any))
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* 处理连接打开
|
|
260
|
+
*/
|
|
261
|
+
private handleConnectionOpen(): void {
|
|
262
|
+
this.setState(ConnectionState.CONNECTED)
|
|
263
|
+
this.reconnectAttempts = 0
|
|
264
|
+
|
|
265
|
+
// 更新 Redux 状态
|
|
266
|
+
store.dispatch(setConnected(true))
|
|
267
|
+
|
|
268
|
+
// 初始化数据
|
|
269
|
+
this.initializeData()
|
|
270
|
+
|
|
271
|
+
// 触发回调
|
|
272
|
+
this.callbacks.onConnect?.()
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* 处理消息接收
|
|
277
|
+
*/
|
|
278
|
+
private handleMessage(event: MessageEvent): void {
|
|
279
|
+
try {
|
|
280
|
+
const message: WebSocketMessage = JSON.parse(event.data)
|
|
281
|
+
|
|
282
|
+
// 处理请求响应
|
|
283
|
+
if (message.requestId && this.pendingRequests.has(message.requestId)) {
|
|
284
|
+
this.handleRequestResponse(message)
|
|
285
|
+
return
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// 处理广播消息
|
|
289
|
+
MessageHandler.handle(message)
|
|
290
|
+
this.callbacks.onMessage?.(message)
|
|
291
|
+
|
|
292
|
+
} catch (error) {
|
|
293
|
+
console.error('[WebSocket] Message parsing error:', error)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* 处理请求响应
|
|
299
|
+
*/
|
|
300
|
+
private handleRequestResponse(message: any): void {
|
|
301
|
+
const { requestId } = message
|
|
302
|
+
const pendingRequest = this.pendingRequests.get(requestId)
|
|
303
|
+
|
|
304
|
+
if (!pendingRequest) {
|
|
305
|
+
return
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
this.pendingRequests.delete(requestId)
|
|
309
|
+
clearTimeout(pendingRequest.timer)
|
|
310
|
+
|
|
311
|
+
if (message.error) {
|
|
312
|
+
pendingRequest.reject(new WebSocketError(message.error, 'SERVER_ERROR'))
|
|
313
|
+
} else {
|
|
314
|
+
pendingRequest.resolve(message.data)
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* 处理连接关闭
|
|
320
|
+
*/
|
|
321
|
+
private handleConnectionClose(event: CloseEvent): void {
|
|
322
|
+
this.ws = null
|
|
323
|
+
store.dispatch(setConnected(false))
|
|
324
|
+
|
|
325
|
+
if (this.state === ConnectionState.DISCONNECTED) {
|
|
326
|
+
// 主动断开,不重连
|
|
327
|
+
return
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
this.setState(ConnectionState.RECONNECTING)
|
|
331
|
+
this.callbacks.onDisconnect?.()
|
|
332
|
+
this.scheduleReconnect()
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* 处理连接错误
|
|
337
|
+
*/
|
|
338
|
+
private handleConnectionError(error: Error): void {
|
|
339
|
+
this.setState(ConnectionState.ERROR)
|
|
340
|
+
console.error('[WebSocket] Connection error:', error)
|
|
341
|
+
this.callbacks.onError?.(error as any)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* 安排重连
|
|
346
|
+
*/
|
|
347
|
+
private scheduleReconnect(): void {
|
|
348
|
+
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
349
|
+
console.error('[WebSocket] Max reconnect attempts reached')
|
|
350
|
+
this.setState(ConnectionState.ERROR)
|
|
351
|
+
return
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
this.reconnectAttempts++
|
|
355
|
+
const delay = this.config.reconnectInterval * this.reconnectAttempts
|
|
356
|
+
|
|
357
|
+
console.log(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`)
|
|
358
|
+
|
|
359
|
+
this.reconnectTimer = setTimeout(() => {
|
|
360
|
+
if (this.state === ConnectionState.RECONNECTING) {
|
|
361
|
+
this.connect()
|
|
362
|
+
}
|
|
363
|
+
}, delay)
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* 清除重连计时器
|
|
368
|
+
*/
|
|
369
|
+
private clearReconnectTimer(): void {
|
|
370
|
+
if (this.reconnectTimer) {
|
|
371
|
+
clearTimeout(this.reconnectTimer)
|
|
372
|
+
this.reconnectTimer = null
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* 清除所有待处理请求
|
|
378
|
+
*/
|
|
379
|
+
private clearPendingRequests(): void {
|
|
380
|
+
for (const [requestId, { reject, timer }] of this.pendingRequests) {
|
|
381
|
+
clearTimeout(timer)
|
|
382
|
+
reject(new WebSocketError('Connection closed', 'CONNECTION_CLOSED'))
|
|
383
|
+
}
|
|
384
|
+
this.pendingRequests.clear()
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* 初始化数据
|
|
389
|
+
*/
|
|
390
|
+
private async initializeData(): Promise<void> {
|
|
391
|
+
try {
|
|
392
|
+
// 并行获取所有配置和 Schema
|
|
393
|
+
const [configs, schemas] = await Promise.all([
|
|
394
|
+
this.getAllConfigs().catch(error => {
|
|
395
|
+
console.warn('[WebSocket] Failed to load configs:', error)
|
|
396
|
+
return {}
|
|
397
|
+
}),
|
|
398
|
+
this.getAllSchemas().catch(error => {
|
|
399
|
+
console.warn('[WebSocket] Failed to load schemas:', error)
|
|
400
|
+
return {}
|
|
401
|
+
})
|
|
402
|
+
])
|
|
403
|
+
|
|
404
|
+
// 更新 Redux 状态
|
|
405
|
+
store.dispatch(updateConfigs(configs))
|
|
406
|
+
store.dispatch(updateSchemas(schemas))
|
|
407
|
+
|
|
408
|
+
} catch (error) {
|
|
409
|
+
console.error('[WebSocket] Data initialization failed:', error)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
}
|