@zhin.js/client 1.0.3 → 1.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +117 -3
  2. package/{src → client}/index.ts +0 -1
  3. package/{src → client}/store/index.ts +22 -1
  4. package/client/store/reducers/config.ts +135 -0
  5. package/{src → client}/store/reducers/index.ts +4 -1
  6. package/client/websocket/hooks.ts +280 -0
  7. package/client/websocket/index.ts +48 -0
  8. package/client/websocket/instance.ts +46 -0
  9. package/client/websocket/manager.ts +412 -0
  10. package/client/websocket/messageHandler.ts +166 -0
  11. package/client/websocket/types.ts +208 -0
  12. package/dist/index.d.ts +8 -0
  13. package/dist/index.d.ts.map +1 -0
  14. package/dist/index.js +0 -1
  15. package/dist/index.js.map +1 -0
  16. package/dist/router/index.d.ts +25 -0
  17. package/dist/router/index.d.ts.map +1 -0
  18. package/dist/router/index.js +49 -0
  19. package/dist/router/index.js.map +1 -0
  20. package/dist/store/index.d.ts +19 -0
  21. package/dist/store/index.d.ts.map +1 -0
  22. package/dist/store/index.js +67 -0
  23. package/dist/store/index.js.map +1 -0
  24. package/dist/store/reducers/config.d.ts +54 -0
  25. package/dist/store/reducers/config.d.ts.map +1 -0
  26. package/dist/store/reducers/config.js +78 -0
  27. package/dist/store/reducers/config.js.map +1 -0
  28. package/dist/store/reducers/index.d.ts +13 -0
  29. package/dist/store/reducers/index.d.ts.map +1 -0
  30. package/dist/store/reducers/index.js +11 -0
  31. package/dist/store/reducers/index.js.map +1 -0
  32. package/dist/store/reducers/route.d.ts +37 -0
  33. package/dist/store/reducers/route.d.ts.map +1 -0
  34. package/dist/store/reducers/route.js +85 -0
  35. package/dist/store/reducers/route.js.map +1 -0
  36. package/dist/store/reducers/script.d.ts +17 -0
  37. package/dist/store/reducers/script.d.ts.map +1 -0
  38. package/dist/store/reducers/script.js +74 -0
  39. package/dist/store/reducers/script.js.map +1 -0
  40. package/dist/store/reducers/ui.d.ts +14 -0
  41. package/dist/store/reducers/ui.d.ts.map +1 -0
  42. package/dist/store/reducers/ui.js +23 -0
  43. package/dist/store/reducers/ui.js.map +1 -0
  44. package/dist/types.d.ts +7 -0
  45. package/dist/types.d.ts.map +1 -0
  46. package/dist/types.js +2 -0
  47. package/dist/types.js.map +1 -0
  48. package/dist/websocket/hooks.d.ts +55 -0
  49. package/dist/websocket/hooks.d.ts.map +1 -0
  50. package/dist/websocket/hooks.js +225 -0
  51. package/dist/websocket/hooks.js.map +1 -0
  52. package/dist/websocket/index.d.ts +13 -0
  53. package/dist/websocket/index.d.ts.map +1 -0
  54. package/dist/websocket/index.js +31 -0
  55. package/dist/websocket/index.js.map +1 -0
  56. package/dist/websocket/instance.d.ts +18 -0
  57. package/dist/websocket/instance.d.ts.map +1 -0
  58. package/dist/websocket/instance.js +39 -0
  59. package/dist/websocket/instance.js.map +1 -0
  60. package/dist/websocket/manager.d.ts +110 -0
  61. package/dist/websocket/manager.d.ts.map +1 -0
  62. package/dist/websocket/manager.js +341 -0
  63. package/dist/websocket/manager.js.map +1 -0
  64. package/dist/websocket/messageHandler.d.ts +48 -0
  65. package/dist/websocket/messageHandler.d.ts.map +1 -0
  66. package/dist/websocket/messageHandler.js +140 -0
  67. package/dist/websocket/messageHandler.js.map +1 -0
  68. package/dist/websocket/types.d.ts +125 -0
  69. package/dist/websocket/types.d.ts.map +1 -0
  70. package/dist/websocket/types.js +43 -0
  71. package/dist/websocket/types.js.map +1 -0
  72. package/package.json +8 -18
  73. package/app/index.html +0 -13
  74. package/app/postcss.config.js +0 -5
  75. package/app/src/components/ThemeToggle.tsx +0 -21
  76. package/app/src/hooks/useTheme.ts +0 -17
  77. package/app/src/layouts/dashboard.tsx +0 -259
  78. package/app/src/main.tsx +0 -121
  79. package/app/src/pages/dashboard-bots.tsx +0 -198
  80. package/app/src/pages/dashboard-home.tsx +0 -301
  81. package/app/src/pages/dashboard-logs.tsx +0 -298
  82. package/app/src/pages/dashboard-plugin-detail.tsx +0 -349
  83. package/app/src/pages/dashboard-plugins.tsx +0 -166
  84. package/app/src/style.css +0 -1105
  85. package/app/src/theme/index.ts +0 -92
  86. package/app/tailwind.config.js +0 -70
  87. package/app/tsconfig.json +0 -16
  88. package/src/websocket/index.ts +0 -193
  89. package/src/websocket/useWebSocket.ts +0 -42
  90. /package/{src → client}/router/index.tsx +0 -0
  91. /package/{src → client}/store/reducers/route.ts +0 -0
  92. /package/{src → client}/store/reducers/script.ts +0 -0
  93. /package/{src → client}/store/reducers/ui.ts +0 -0
  94. /package/{src → client}/types.ts +0 -0
@@ -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
+ }
@@ -0,0 +1,166 @@
1
+ /**
2
+ * WebSocket 消息处理器
3
+ * 负责处理不同类型的 WebSocket 消息并分发到对应的处理逻辑
4
+ */
5
+
6
+ import {
7
+ store,
8
+ loadScripts,
9
+ loadScript,
10
+ unloadScript,
11
+ setConnected,
12
+ updateConfig,
13
+ updateSchema,
14
+ updateConfigs,
15
+ updateSchemas,
16
+ setError
17
+ } from '../store'
18
+ import type { WebSocketMessage } from './types'
19
+
20
+ export class MessageHandler {
21
+ /**
22
+ * 处理 WebSocket 消息
23
+ */
24
+ static handle(message: WebSocketMessage): void {
25
+ try {
26
+ switch (message.type) {
27
+ // 脚本管理相关消息
28
+ case 'sync':
29
+ this.handleScriptSync(message)
30
+ break
31
+ case 'add':
32
+ this.handleScriptAdd(message)
33
+ break
34
+ case 'delete':
35
+ this.handleScriptDelete(message)
36
+ break
37
+
38
+ // 配置管理相关消息
39
+ case 'config:updated':
40
+ this.handleConfigUpdated(message)
41
+ break
42
+ case 'config:batch':
43
+ this.handleConfigBatch(message)
44
+ break
45
+ case 'config:error':
46
+ this.handleConfigError(message)
47
+ break
48
+
49
+ // Schema 管理相关消息
50
+ case 'schema:updated':
51
+ this.handleSchemaUpdated(message)
52
+ break
53
+ case 'schema:batch':
54
+ this.handleSchemaBatch(message)
55
+ break
56
+
57
+ // 系统消息
58
+ case 'init-data':
59
+ case 'data-update':
60
+ this.handleSystemMessage(message)
61
+ break
62
+
63
+ default:
64
+ console.warn('[WebSocket] Unknown message type:', message.type)
65
+ }
66
+ } catch (error) {
67
+ console.error('[WebSocket] Message handling error:', error)
68
+ }
69
+ }
70
+
71
+ /**
72
+ * 处理脚本同步消息
73
+ */
74
+ private static handleScriptSync(message: any): void {
75
+ if (message.data?.key === 'entries') {
76
+ const entries = Array.isArray(message.data.value)
77
+ ? message.data.value
78
+ : [message.data.value]
79
+
80
+ store.dispatch({ type: 'script/syncEntries', payload: entries })
81
+ store.dispatch(loadScripts(entries))
82
+ }
83
+ }
84
+
85
+ /**
86
+ * 处理脚本添加消息
87
+ */
88
+ private static handleScriptAdd(message: any): void {
89
+ if (message.data?.key === 'entries') {
90
+ store.dispatch({ type: 'script/addEntry', payload: message.data.value })
91
+ store.dispatch(loadScript(message.data.value))
92
+ }
93
+ }
94
+
95
+ /**
96
+ * 处理脚本删除消息
97
+ */
98
+ private static handleScriptDelete(message: any): void {
99
+ if (message.data?.key === 'entries') {
100
+ store.dispatch({ type: 'script/removeEntry', payload: message.data.value })
101
+ store.dispatch(unloadScript(message.data.value))
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 处理配置更新消息
107
+ */
108
+ private static handleConfigUpdated(message: any): void {
109
+ if (message.pluginName && message.data !== undefined) {
110
+ store.dispatch(updateConfig({
111
+ pluginName: message.pluginName,
112
+ config: message.data
113
+ }))
114
+ }
115
+ }
116
+
117
+ /**
118
+ * 处理批量配置消息
119
+ */
120
+ private static handleConfigBatch(message: any): void {
121
+ if (message.data) {
122
+ store.dispatch(updateConfigs(message.data))
123
+ }
124
+ }
125
+
126
+ /**
127
+ * 处理配置错误消息
128
+ */
129
+ private static handleConfigError(message: any): void {
130
+ if (message.pluginName && message.error) {
131
+ store.dispatch(setError({
132
+ pluginName: message.pluginName,
133
+ error: message.error
134
+ }))
135
+ }
136
+ }
137
+
138
+ /**
139
+ * 处理 Schema 更新消息
140
+ */
141
+ private static handleSchemaUpdated(message: any): void {
142
+ if (message.pluginName && message.data !== undefined) {
143
+ store.dispatch(updateSchema({
144
+ pluginName: message.pluginName,
145
+ schema: message.data
146
+ }))
147
+ }
148
+ }
149
+
150
+ /**
151
+ * 处理批量 Schema 消息
152
+ */
153
+ private static handleSchemaBatch(message: any): void {
154
+ if (message.data) {
155
+ store.dispatch(updateSchemas(message.data))
156
+ }
157
+ }
158
+
159
+ /**
160
+ * 处理系统消息
161
+ */
162
+ private static handleSystemMessage(message: WebSocketMessage): void {
163
+ // 系统消息暂时不需要特殊处理
164
+ // 可以在这里添加系统级别的消息处理逻辑
165
+ }
166
+ }