@zhin.js/client 1.0.17 → 1.1.0
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/client/app.ts +228 -0
- package/client/bootstrap/loadConsoleEntries.test.ts +32 -0
- package/client/bootstrap/loadConsoleEntries.ts +149 -0
- package/client/console-utils/cn.ts +6 -0
- package/client/console-utils/remoteApi.ts +48 -0
- package/client/index.ts +61 -14
- package/client/persistence/idb-store.ts +76 -0
- package/client/runtime/index.ts +12 -0
- package/client/store/createRegistryStore.ts +46 -0
- package/client/websocket/hooks.ts +218 -510
- package/client/websocket/index.ts +6 -43
- package/client/websocket/instance.ts +10 -32
- package/client/websocket/manager.ts +301 -385
- package/client/websocket/remote-settings.ts +25 -0
- package/client/websocket/types.ts +58 -222
- package/dist/app.d.ts +95 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +147 -0
- package/dist/app.js.map +1 -0
- package/dist/bootstrap/loadConsoleEntries.d.ts +26 -0
- package/dist/bootstrap/loadConsoleEntries.d.ts.map +1 -0
- package/dist/bootstrap/loadConsoleEntries.js +111 -0
- package/dist/bootstrap/loadConsoleEntries.js.map +1 -0
- package/dist/bootstrap/loadConsoleEntries.test.d.ts +2 -0
- package/dist/bootstrap/loadConsoleEntries.test.d.ts.map +1 -0
- package/dist/bootstrap/loadConsoleEntries.test.js +27 -0
- package/dist/bootstrap/loadConsoleEntries.test.js.map +1 -0
- package/dist/console-utils/cn.d.ts +3 -0
- package/dist/console-utils/cn.d.ts.map +1 -0
- package/dist/console-utils/cn.js +6 -0
- package/dist/console-utils/cn.js.map +1 -0
- package/dist/console-utils/remoteApi.d.ts +7 -0
- package/dist/console-utils/remoteApi.d.ts.map +1 -0
- package/dist/console-utils/remoteApi.js +41 -0
- package/dist/console-utils/remoteApi.js.map +1 -0
- package/dist/index.d.ts +11 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -14
- package/dist/index.js.map +1 -1
- package/dist/persistence/idb-store.d.ts +15 -0
- package/dist/persistence/idb-store.d.ts.map +1 -0
- package/dist/persistence/idb-store.js +61 -0
- package/dist/persistence/idb-store.js.map +1 -0
- package/dist/runtime/index.d.ts +6 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +10 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/store/createRegistryStore.d.ts +9 -0
- package/dist/store/createRegistryStore.d.ts.map +1 -0
- package/dist/store/createRegistryStore.js +38 -0
- package/dist/store/createRegistryStore.js.map +1 -0
- package/dist/websocket/hooks.d.ts +35 -58
- package/dist/websocket/hooks.d.ts.map +1 -1
- package/dist/websocket/hooks.js +134 -410
- package/dist/websocket/hooks.js.map +1 -1
- package/dist/websocket/index.d.ts +5 -10
- package/dist/websocket/index.d.ts.map +1 -1
- package/dist/websocket/index.js +5 -25
- package/dist/websocket/index.js.map +1 -1
- package/dist/websocket/instance.d.ts +1 -14
- package/dist/websocket/instance.d.ts.map +1 -1
- package/dist/websocket/instance.js +2 -20
- package/dist/websocket/instance.js.map +1 -1
- package/dist/websocket/manager.d.ts +28 -93
- package/dist/websocket/manager.d.ts.map +1 -1
- package/dist/websocket/manager.js +213 -221
- package/dist/websocket/manager.js.map +1 -1
- package/dist/websocket/remote-settings.d.ts +5 -0
- package/dist/websocket/remote-settings.d.ts.map +1 -0
- package/dist/websocket/remote-settings.js +23 -0
- package/dist/websocket/remote-settings.js.map +1 -0
- package/dist/websocket/types.d.ts +35 -122
- package/dist/websocket/types.d.ts.map +1 -1
- package/dist/websocket/types.js +4 -13
- package/dist/websocket/types.js.map +1 -1
- package/package.json +13 -11
- package/client/router/index.tsx +0 -54
- package/client/store/index.ts +0 -132
- package/client/store/reducers/config.ts +0 -135
- package/client/store/reducers/index.ts +0 -19
- package/client/store/reducers/route.ts +0 -126
- package/client/store/reducers/script.ts +0 -109
- package/client/store/reducers/ui.ts +0 -31
- package/client/websocket/messageHandler.ts +0 -208
- package/dist/router/index.d.ts +0 -25
- package/dist/router/index.d.ts.map +0 -1
- package/dist/router/index.js +0 -48
- package/dist/router/index.js.map +0 -1
- package/dist/store/index.d.ts +0 -19
- package/dist/store/index.d.ts.map +0 -1
- package/dist/store/index.js +0 -67
- package/dist/store/index.js.map +0 -1
- package/dist/store/reducers/config.d.ts +0 -54
- package/dist/store/reducers/config.d.ts.map +0 -1
- package/dist/store/reducers/config.js +0 -78
- package/dist/store/reducers/config.js.map +0 -1
- package/dist/store/reducers/index.d.ts +0 -13
- package/dist/store/reducers/index.d.ts.map +0 -1
- package/dist/store/reducers/index.js +0 -11
- package/dist/store/reducers/index.js.map +0 -1
- package/dist/store/reducers/route.d.ts +0 -30
- package/dist/store/reducers/route.d.ts.map +0 -1
- package/dist/store/reducers/route.js +0 -85
- package/dist/store/reducers/route.js.map +0 -1
- package/dist/store/reducers/script.d.ts +0 -13
- package/dist/store/reducers/script.d.ts.map +0 -1
- package/dist/store/reducers/script.js +0 -78
- package/dist/store/reducers/script.js.map +0 -1
- package/dist/store/reducers/ui.d.ts +0 -8
- package/dist/store/reducers/ui.d.ts.map +0 -1
- package/dist/store/reducers/ui.js +0 -23
- package/dist/store/reducers/ui.js.map +0 -1
- package/dist/websocket/messageHandler.d.ts +0 -58
- package/dist/websocket/messageHandler.d.ts.map +0 -1
- package/dist/websocket/messageHandler.js +0 -174
- package/dist/websocket/messageHandler.js.map +0 -1
|
@@ -1,411 +1,403 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
*/
|
|
5
|
-
import { store, setConnected, updateConfigs, updateSchemas } from '../store';
|
|
6
|
-
import { MessageHandler } from './messageHandler';
|
|
7
|
-
import { ConnectionState, WebSocketError, ConnectionError, MessageError, RequestTimeoutError } from './types';
|
|
1
|
+
import { ConnectionState, WebSocketError, ConnectionError, MessageError, RequestTimeoutError, } from "./types";
|
|
2
|
+
import { getStoredToken, resolveApiUrl } from "./remote-settings.js";
|
|
3
|
+
import { applyConsoleEvent } from "../persistence/idb-store.js";
|
|
8
4
|
export class WebSocketManager {
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// 私有属性
|
|
11
|
-
// ============================================================================
|
|
12
5
|
ws = null;
|
|
13
6
|
config;
|
|
14
7
|
callbacks;
|
|
15
8
|
state = ConnectionState.DISCONNECTED;
|
|
16
|
-
// 重连相关
|
|
17
9
|
reconnectAttempts = 0;
|
|
18
10
|
reconnectTimer = null;
|
|
19
|
-
// 请求管理
|
|
20
11
|
requestId = 0;
|
|
21
12
|
pendingRequests = new Map();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
13
|
+
connectedListeners = new Set();
|
|
14
|
+
sseAbort = null;
|
|
15
|
+
useRestTransport = true;
|
|
25
16
|
constructor(config = {}, callbacks = {}) {
|
|
26
17
|
this.config = {
|
|
27
18
|
url: this.buildWebSocketUrl(config.url),
|
|
28
19
|
reconnectInterval: config.reconnectInterval ?? 3000,
|
|
29
20
|
maxReconnectAttempts: config.maxReconnectAttempts ?? 10,
|
|
30
|
-
requestTimeout: config.requestTimeout ?? 10000
|
|
21
|
+
requestTimeout: config.requestTimeout ?? 10000,
|
|
31
22
|
};
|
|
32
23
|
this.callbacks = callbacks;
|
|
33
24
|
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
25
|
+
onConnectionChange(listener) {
|
|
26
|
+
this.connectedListeners.add(listener);
|
|
27
|
+
return () => this.connectedListeners.delete(listener);
|
|
28
|
+
}
|
|
29
|
+
notifyConnection(connected) {
|
|
30
|
+
for (const l of this.connectedListeners)
|
|
31
|
+
l(connected);
|
|
32
|
+
}
|
|
40
33
|
connect() {
|
|
41
|
-
if (this.
|
|
34
|
+
if (this.useRestTransport) {
|
|
35
|
+
void this.connectRestSse();
|
|
42
36
|
return;
|
|
43
37
|
}
|
|
38
|
+
if (this.state === ConnectionState.CONNECTED || this.state === ConnectionState.CONNECTING)
|
|
39
|
+
return;
|
|
44
40
|
this.setState(ConnectionState.CONNECTING);
|
|
45
41
|
try {
|
|
46
42
|
this.ws = new WebSocket(this.config.url);
|
|
47
43
|
this.attachEventHandlers();
|
|
48
44
|
}
|
|
49
45
|
catch (error) {
|
|
50
|
-
this.handleConnectionError(new ConnectionError(
|
|
46
|
+
this.handleConnectionError(new ConnectionError("Failed to create WebSocket", error));
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
async connectRestSse() {
|
|
50
|
+
const token = getStoredToken();
|
|
51
|
+
if (!token) {
|
|
52
|
+
this.setState(ConnectionState.DISCONNECTED);
|
|
53
|
+
this.notifyConnection(false);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
this.setState(ConnectionState.CONNECTING);
|
|
57
|
+
this.sseAbort?.abort();
|
|
58
|
+
this.sseAbort = new AbortController();
|
|
59
|
+
try {
|
|
60
|
+
const res = await fetch(resolveApiUrl("/api/events"), {
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${token}`,
|
|
63
|
+
Accept: "text/event-stream",
|
|
64
|
+
},
|
|
65
|
+
signal: this.sseAbort.signal,
|
|
66
|
+
});
|
|
67
|
+
if (!res.ok || !res.body) {
|
|
68
|
+
throw new ConnectionError(`SSE failed: HTTP ${res.status}`);
|
|
69
|
+
}
|
|
70
|
+
this.setState(ConnectionState.CONNECTED);
|
|
71
|
+
this.reconnectAttempts = 0;
|
|
72
|
+
this.notifyConnection(true);
|
|
73
|
+
this.callbacks.onConnect?.();
|
|
74
|
+
void this.pumpSse(res.body);
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error.name === "AbortError")
|
|
78
|
+
return;
|
|
79
|
+
this.handleConnectionError(error instanceof ConnectionError ? error : new ConnectionError("SSE connect failed", error));
|
|
80
|
+
this.scheduleReconnect();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
async pumpSse(body) {
|
|
84
|
+
const reader = body.getReader();
|
|
85
|
+
const decoder = new TextDecoder();
|
|
86
|
+
let buffer = "";
|
|
87
|
+
try {
|
|
88
|
+
while (true) {
|
|
89
|
+
const { done, value } = await reader.read();
|
|
90
|
+
if (done)
|
|
91
|
+
break;
|
|
92
|
+
buffer += decoder.decode(value, { stream: true });
|
|
93
|
+
const parts = buffer.split("\n\n");
|
|
94
|
+
buffer = parts.pop() ?? "";
|
|
95
|
+
for (const part of parts) {
|
|
96
|
+
const line = part.split("\n").find((l) => l.startsWith("data: "));
|
|
97
|
+
if (!line)
|
|
98
|
+
continue;
|
|
99
|
+
const json = line.slice(6);
|
|
100
|
+
try {
|
|
101
|
+
const message = JSON.parse(json);
|
|
102
|
+
void applyConsoleEvent(message);
|
|
103
|
+
this.handleBroadcast(message);
|
|
104
|
+
this.callbacks.onMessage?.(message);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
/* skip */
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
/* closed */
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
if (this.state === ConnectionState.CONNECTED) {
|
|
117
|
+
this.notifyConnection(false);
|
|
118
|
+
this.setState(ConnectionState.RECONNECTING);
|
|
119
|
+
this.scheduleReconnect();
|
|
120
|
+
}
|
|
51
121
|
}
|
|
52
122
|
}
|
|
53
|
-
/**
|
|
54
|
-
* 断开 WebSocket 连接
|
|
55
|
-
*/
|
|
56
123
|
disconnect() {
|
|
57
124
|
this.clearReconnectTimer();
|
|
58
125
|
this.clearPendingRequests();
|
|
126
|
+
this.sseAbort?.abort();
|
|
127
|
+
this.sseAbort = null;
|
|
59
128
|
if (this.ws) {
|
|
60
129
|
this.ws.close();
|
|
61
130
|
this.ws = null;
|
|
62
131
|
}
|
|
63
132
|
this.setState(ConnectionState.DISCONNECTED);
|
|
64
|
-
|
|
133
|
+
this.notifyConnection(false);
|
|
65
134
|
}
|
|
66
|
-
/**
|
|
67
|
-
* 发送普通消息
|
|
68
|
-
*/
|
|
69
135
|
send(message) {
|
|
70
|
-
if (
|
|
71
|
-
|
|
136
|
+
if (this.useRestTransport) {
|
|
137
|
+
void this.sendRequest(message);
|
|
138
|
+
return;
|
|
72
139
|
}
|
|
140
|
+
if (!this.isConnected())
|
|
141
|
+
throw new WebSocketError("WebSocket is not connected", "NOT_CONNECTED");
|
|
73
142
|
try {
|
|
74
143
|
this.ws.send(JSON.stringify(message));
|
|
75
144
|
}
|
|
76
145
|
catch (error) {
|
|
77
|
-
throw new MessageError(
|
|
146
|
+
throw new MessageError("Failed to send message", error);
|
|
78
147
|
}
|
|
79
148
|
}
|
|
80
|
-
/**
|
|
81
|
-
* 发送请求并等待响应
|
|
82
|
-
*/
|
|
83
149
|
async sendRequest(message) {
|
|
84
|
-
if (
|
|
85
|
-
|
|
150
|
+
if (this.useRestTransport) {
|
|
151
|
+
return this.sendRestRequest(message);
|
|
86
152
|
}
|
|
153
|
+
if (!this.isConnected())
|
|
154
|
+
throw new WebSocketError("WebSocket is not connected", "NOT_CONNECTED");
|
|
87
155
|
return new Promise((resolve, reject) => {
|
|
88
156
|
const requestId = ++this.requestId;
|
|
89
157
|
const messageWithId = { ...message, requestId };
|
|
90
|
-
// 设置超时计时器
|
|
91
158
|
const timer = setTimeout(() => {
|
|
92
159
|
this.pendingRequests.delete(requestId);
|
|
93
160
|
reject(new RequestTimeoutError(requestId));
|
|
94
161
|
}, this.config.requestTimeout);
|
|
95
|
-
|
|
96
|
-
this.pendingRequests.set(requestId, { resolve, reject, timer });
|
|
162
|
+
this.pendingRequests.set(requestId, { resolve: resolve, reject, timer });
|
|
97
163
|
try {
|
|
98
164
|
this.ws.send(JSON.stringify(messageWithId));
|
|
99
165
|
}
|
|
100
166
|
catch (error) {
|
|
101
167
|
this.pendingRequests.delete(requestId);
|
|
102
168
|
clearTimeout(timer);
|
|
103
|
-
reject(new MessageError(
|
|
169
|
+
reject(new MessageError("Failed to send request", error));
|
|
104
170
|
}
|
|
105
171
|
});
|
|
106
172
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
173
|
+
async sendRestRequest(message) {
|
|
174
|
+
const token = getStoredToken();
|
|
175
|
+
if (!token)
|
|
176
|
+
throw new WebSocketError("Not authenticated", "NOT_CONNECTED");
|
|
177
|
+
const requestId = ++this.requestId;
|
|
178
|
+
const body = { ...message, requestId };
|
|
179
|
+
const timer = setTimeout(() => {
|
|
180
|
+
/* handled below */
|
|
181
|
+
}, this.config.requestTimeout);
|
|
182
|
+
try {
|
|
183
|
+
const res = await fetch(resolveApiUrl("/api/console/request"), {
|
|
184
|
+
method: "POST",
|
|
185
|
+
headers: {
|
|
186
|
+
Authorization: `Bearer ${token}`,
|
|
187
|
+
"Content-Type": "application/json",
|
|
188
|
+
},
|
|
189
|
+
body: JSON.stringify(body),
|
|
190
|
+
signal: AbortSignal.timeout(this.config.requestTimeout),
|
|
191
|
+
});
|
|
192
|
+
clearTimeout(timer);
|
|
193
|
+
const json = (await res.json());
|
|
194
|
+
if (!res.ok || json.success === false) {
|
|
195
|
+
throw new WebSocketError(json.error ?? `HTTP ${res.status}`, "SERVER_ERROR");
|
|
196
|
+
}
|
|
197
|
+
return json.data;
|
|
198
|
+
}
|
|
199
|
+
catch (error) {
|
|
200
|
+
clearTimeout(timer);
|
|
201
|
+
if (error instanceof WebSocketError)
|
|
202
|
+
throw error;
|
|
203
|
+
throw new MessageError("REST request failed", error);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
110
206
|
isConnected() {
|
|
207
|
+
if (this.useRestTransport) {
|
|
208
|
+
return this.state === ConnectionState.CONNECTED;
|
|
209
|
+
}
|
|
111
210
|
return this.ws?.readyState === WebSocket.OPEN;
|
|
112
211
|
}
|
|
113
|
-
/**
|
|
114
|
-
* 获取当前状态
|
|
115
|
-
*/
|
|
116
212
|
getState() {
|
|
117
213
|
return this.state;
|
|
118
214
|
}
|
|
119
|
-
// ============================================================================
|
|
120
|
-
// 配置管理 API
|
|
121
|
-
// ============================================================================
|
|
122
|
-
/**
|
|
123
|
-
* 获取插件配置
|
|
124
|
-
*/
|
|
125
215
|
async getConfig(pluginName) {
|
|
126
|
-
return this.sendRequest({
|
|
127
|
-
type: 'config:get',
|
|
128
|
-
pluginName
|
|
129
|
-
});
|
|
216
|
+
return this.sendRequest({ type: "config:get", pluginName });
|
|
130
217
|
}
|
|
131
|
-
/**
|
|
132
|
-
* 设置插件配置
|
|
133
|
-
*/
|
|
134
218
|
async setConfig(pluginName, config) {
|
|
135
219
|
return this.sendRequest({
|
|
136
|
-
type:
|
|
220
|
+
type: "config:set",
|
|
137
221
|
pluginName,
|
|
138
|
-
data: config
|
|
222
|
+
data: config,
|
|
139
223
|
});
|
|
140
224
|
}
|
|
141
|
-
/**
|
|
142
|
-
* 获取插件 Schema
|
|
143
|
-
*/
|
|
144
225
|
async getSchema(pluginName) {
|
|
145
|
-
return this.sendRequest({
|
|
146
|
-
type: 'schema:get',
|
|
147
|
-
pluginName
|
|
148
|
-
});
|
|
226
|
+
return this.sendRequest({ type: "schema:get", pluginName });
|
|
149
227
|
}
|
|
150
|
-
/**
|
|
151
|
-
* 获取所有配置
|
|
152
|
-
*/
|
|
153
228
|
async getAllConfigs() {
|
|
154
|
-
return this.sendRequest({
|
|
155
|
-
type: 'config:get-all'
|
|
156
|
-
});
|
|
229
|
+
return this.sendRequest({ type: "config:get-all" });
|
|
157
230
|
}
|
|
158
|
-
/**
|
|
159
|
-
* 获取所有 Schema
|
|
160
|
-
*/
|
|
161
231
|
async getAllSchemas() {
|
|
162
|
-
return this.sendRequest({
|
|
163
|
-
type: 'schema:get-all'
|
|
164
|
-
});
|
|
232
|
+
return this.sendRequest({ type: "schema:get-all" });
|
|
165
233
|
}
|
|
166
|
-
// ============================================================================
|
|
167
|
-
// 配置文件原始 YAML 读写
|
|
168
|
-
// ============================================================================
|
|
169
234
|
async getConfigYaml() {
|
|
170
|
-
return this.sendRequest({ type:
|
|
235
|
+
return this.sendRequest({ type: "config:get-yaml" });
|
|
171
236
|
}
|
|
172
237
|
async saveConfigYaml(yaml) {
|
|
173
|
-
return this.sendRequest({ type:
|
|
238
|
+
return this.sendRequest({ type: "config:save-yaml", yaml });
|
|
174
239
|
}
|
|
175
|
-
// ============================================================================
|
|
176
|
-
// 环境变量文件管理
|
|
177
|
-
// ============================================================================
|
|
178
240
|
async getEnvList() {
|
|
179
|
-
return this.sendRequest({ type:
|
|
241
|
+
return this.sendRequest({ type: "env:list" });
|
|
180
242
|
}
|
|
181
243
|
async getEnvFile(filename) {
|
|
182
|
-
return this.sendRequest({ type:
|
|
244
|
+
return this.sendRequest({ type: "env:get", filename });
|
|
183
245
|
}
|
|
184
246
|
async saveEnvFile(filename, content) {
|
|
185
|
-
return this.sendRequest({ type:
|
|
247
|
+
return this.sendRequest({ type: "env:save", filename, content });
|
|
186
248
|
}
|
|
187
|
-
// ============================================================================
|
|
188
|
-
// 文件管理
|
|
189
|
-
// ============================================================================
|
|
190
249
|
async getFileTree() {
|
|
191
|
-
return this.sendRequest({ type:
|
|
250
|
+
return this.sendRequest({ type: "files:tree" });
|
|
192
251
|
}
|
|
193
252
|
async readFile(filePath) {
|
|
194
|
-
return this.sendRequest({ type:
|
|
253
|
+
return this.sendRequest({ type: "files:read", filePath });
|
|
195
254
|
}
|
|
196
255
|
async saveFile(filePath, content) {
|
|
197
|
-
return this.sendRequest({ type:
|
|
256
|
+
return this.sendRequest({ type: "files:save", filePath, content });
|
|
198
257
|
}
|
|
199
|
-
// ============================================================================
|
|
200
|
-
// 数据库管理
|
|
201
|
-
// ============================================================================
|
|
202
258
|
async getDbInfo() {
|
|
203
|
-
return this.sendRequest({ type:
|
|
259
|
+
return this.sendRequest({ type: "db:info" });
|
|
204
260
|
}
|
|
205
261
|
async getDbTables() {
|
|
206
|
-
return this.sendRequest({ type:
|
|
262
|
+
return this.sendRequest({ type: "db:tables" });
|
|
207
263
|
}
|
|
208
264
|
async dbSelect(table, page, pageSize, where) {
|
|
209
|
-
return this.sendRequest({ type:
|
|
265
|
+
return this.sendRequest({ type: "db:select", table, page, pageSize, where });
|
|
210
266
|
}
|
|
211
267
|
async dbInsert(table, row) {
|
|
212
|
-
return this.sendRequest({ type:
|
|
268
|
+
return this.sendRequest({ type: "db:insert", table, row });
|
|
213
269
|
}
|
|
214
270
|
async dbUpdate(table, row, where) {
|
|
215
|
-
return this.sendRequest({ type:
|
|
271
|
+
return this.sendRequest({ type: "db:update", table, row, where });
|
|
216
272
|
}
|
|
217
273
|
async dbDelete(table, where) {
|
|
218
|
-
return this.sendRequest({ type:
|
|
274
|
+
return this.sendRequest({ type: "db:delete", table, where });
|
|
219
275
|
}
|
|
220
276
|
async dbDropTable(table) {
|
|
221
|
-
return this.sendRequest({ type:
|
|
277
|
+
return this.sendRequest({ type: "db:drop-table", table });
|
|
222
278
|
}
|
|
223
279
|
async kvGet(table, key) {
|
|
224
|
-
return this.sendRequest({ type:
|
|
280
|
+
return this.sendRequest({ type: "db:kv:get", table, key });
|
|
225
281
|
}
|
|
226
282
|
async kvSet(table, key, value, ttl) {
|
|
227
|
-
return this.sendRequest({ type:
|
|
283
|
+
return this.sendRequest({ type: "db:kv:set", table, key, value, ttl });
|
|
228
284
|
}
|
|
229
285
|
async kvDelete(table, key) {
|
|
230
|
-
return this.sendRequest({ type:
|
|
286
|
+
return this.sendRequest({ type: "db:kv:delete", table, key });
|
|
231
287
|
}
|
|
232
288
|
async kvGetEntries(table) {
|
|
233
|
-
return this.sendRequest({ type:
|
|
234
|
-
}
|
|
235
|
-
// ============================================================================
|
|
236
|
-
// 私有方法
|
|
237
|
-
// ============================================================================
|
|
238
|
-
/**
|
|
239
|
-
* 构建 WebSocket URL
|
|
240
|
-
*/
|
|
289
|
+
return this.sendRequest({ type: "db:kv:entries", table });
|
|
290
|
+
}
|
|
241
291
|
buildWebSocketUrl(customUrl) {
|
|
242
|
-
if (customUrl)
|
|
292
|
+
if (customUrl)
|
|
243
293
|
return customUrl;
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
const host = window.location.host;
|
|
247
|
-
return `${protocol}//${host}/server`;
|
|
294
|
+
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
|
|
295
|
+
return `${protocol}//${window.location.host}/server`;
|
|
248
296
|
}
|
|
249
|
-
/**
|
|
250
|
-
* 设置连接状态
|
|
251
|
-
*/
|
|
252
297
|
setState(newState) {
|
|
253
|
-
const oldState = this.state;
|
|
254
298
|
this.state = newState;
|
|
255
|
-
console.debug(`[WebSocket] State changed: ${oldState} -> ${newState}`);
|
|
256
299
|
}
|
|
257
|
-
/**
|
|
258
|
-
* 绑定事件处理器
|
|
259
|
-
*/
|
|
260
300
|
attachEventHandlers() {
|
|
261
301
|
if (!this.ws)
|
|
262
302
|
return;
|
|
263
|
-
this.ws.onopen = () =>
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
this.ws.
|
|
267
|
-
this.handleMessage(event);
|
|
268
|
-
};
|
|
269
|
-
this.ws.onclose = (event) => {
|
|
270
|
-
this.handleConnectionClose(event);
|
|
271
|
-
};
|
|
272
|
-
this.ws.onerror = (event) => {
|
|
273
|
-
this.handleConnectionError(new ConnectionError('WebSocket error', event));
|
|
274
|
-
};
|
|
303
|
+
this.ws.onopen = () => this.handleConnectionOpen();
|
|
304
|
+
this.ws.onmessage = (event) => this.handleMessage(event);
|
|
305
|
+
this.ws.onclose = () => this.handleConnectionClose();
|
|
306
|
+
this.ws.onerror = (event) => this.handleConnectionError(new ConnectionError("WebSocket error", event));
|
|
275
307
|
}
|
|
276
|
-
/**
|
|
277
|
-
* 处理连接打开
|
|
278
|
-
*/
|
|
279
308
|
handleConnectionOpen() {
|
|
280
309
|
this.setState(ConnectionState.CONNECTED);
|
|
281
310
|
this.reconnectAttempts = 0;
|
|
282
|
-
|
|
283
|
-
store.dispatch(setConnected(true));
|
|
284
|
-
// 初始化数据
|
|
285
|
-
this.initializeData();
|
|
286
|
-
// 触发回调
|
|
311
|
+
this.notifyConnection(true);
|
|
287
312
|
this.callbacks.onConnect?.();
|
|
288
313
|
}
|
|
289
|
-
/**
|
|
290
|
-
* 处理消息接收
|
|
291
|
-
*/
|
|
292
314
|
handleMessage(event) {
|
|
293
315
|
try {
|
|
294
316
|
const message = JSON.parse(event.data);
|
|
295
|
-
// 处理请求响应
|
|
296
317
|
if (message.requestId && this.pendingRequests.has(message.requestId)) {
|
|
297
318
|
this.handleRequestResponse(message);
|
|
298
319
|
return;
|
|
299
320
|
}
|
|
300
|
-
|
|
301
|
-
|
|
321
|
+
void applyConsoleEvent(message);
|
|
322
|
+
this.handleBroadcast(message);
|
|
302
323
|
this.callbacks.onMessage?.(message);
|
|
303
324
|
}
|
|
304
325
|
catch (error) {
|
|
305
|
-
console.error(
|
|
326
|
+
console.error("[WebSocket] Message parsing error:", error);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
handleBroadcast(message) {
|
|
330
|
+
const t = message.type;
|
|
331
|
+
if (t === "bot:request" || t === "bot:notice" || t === "bot:message") {
|
|
332
|
+
if (typeof window !== "undefined") {
|
|
333
|
+
window.dispatchEvent(new CustomEvent("zhin-console-bot-push", { detail: message }));
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (t === "hmr:reload") {
|
|
338
|
+
console.info(`[HMR] File changed, reloading...`);
|
|
339
|
+
window.location.reload();
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
if (t === "system:restarting") {
|
|
343
|
+
console.info("[System] Server restarting, will reload shortly...");
|
|
344
|
+
setTimeout(() => window.location.reload(), 3000);
|
|
345
|
+
return;
|
|
306
346
|
}
|
|
307
347
|
}
|
|
308
|
-
/**
|
|
309
|
-
* 处理请求响应
|
|
310
|
-
*/
|
|
311
348
|
handleRequestResponse(message) {
|
|
312
349
|
const { requestId } = message;
|
|
313
|
-
const
|
|
314
|
-
if (!
|
|
350
|
+
const pending = this.pendingRequests.get(requestId);
|
|
351
|
+
if (!pending)
|
|
315
352
|
return;
|
|
316
|
-
}
|
|
317
353
|
this.pendingRequests.delete(requestId);
|
|
318
|
-
clearTimeout(
|
|
354
|
+
clearTimeout(pending.timer);
|
|
319
355
|
if (message.error) {
|
|
320
|
-
|
|
356
|
+
pending.reject(new WebSocketError(message.error, "SERVER_ERROR"));
|
|
321
357
|
}
|
|
322
358
|
else {
|
|
323
|
-
|
|
359
|
+
pending.resolve(message.data);
|
|
324
360
|
}
|
|
325
361
|
}
|
|
326
|
-
|
|
327
|
-
* 处理连接关闭
|
|
328
|
-
*/
|
|
329
|
-
handleConnectionClose(event) {
|
|
362
|
+
handleConnectionClose() {
|
|
330
363
|
this.ws = null;
|
|
331
|
-
|
|
332
|
-
if (this.state === ConnectionState.DISCONNECTED)
|
|
333
|
-
// 主动断开,不重连
|
|
364
|
+
this.notifyConnection(false);
|
|
365
|
+
if (this.state === ConnectionState.DISCONNECTED)
|
|
334
366
|
return;
|
|
335
|
-
}
|
|
336
367
|
this.setState(ConnectionState.RECONNECTING);
|
|
337
368
|
this.callbacks.onDisconnect?.();
|
|
338
369
|
this.scheduleReconnect();
|
|
339
370
|
}
|
|
340
|
-
/**
|
|
341
|
-
* 处理连接错误
|
|
342
|
-
*/
|
|
343
371
|
handleConnectionError(error) {
|
|
344
372
|
this.clearReconnectTimer();
|
|
345
373
|
this.setState(ConnectionState.ERROR);
|
|
346
|
-
console.error(
|
|
374
|
+
console.error("[Console transport] Connection error:", error);
|
|
347
375
|
this.callbacks.onError?.(error);
|
|
348
376
|
}
|
|
349
|
-
/**
|
|
350
|
-
* 安排重连
|
|
351
|
-
*/
|
|
352
377
|
scheduleReconnect() {
|
|
353
378
|
if (this.reconnectAttempts >= this.config.maxReconnectAttempts) {
|
|
354
|
-
console.error('[WebSocket] Max reconnect attempts reached');
|
|
355
379
|
this.setState(ConnectionState.ERROR);
|
|
356
380
|
return;
|
|
357
381
|
}
|
|
358
382
|
this.reconnectAttempts++;
|
|
359
383
|
const delay = this.config.reconnectInterval * this.reconnectAttempts;
|
|
360
|
-
console.debug(`[WebSocket] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts}/${this.config.maxReconnectAttempts})`);
|
|
361
384
|
this.reconnectTimer = setTimeout(() => {
|
|
362
|
-
if (this.state === ConnectionState.RECONNECTING)
|
|
385
|
+
if (this.state === ConnectionState.RECONNECTING)
|
|
363
386
|
this.connect();
|
|
364
|
-
}
|
|
365
387
|
}, delay);
|
|
366
388
|
}
|
|
367
|
-
/**
|
|
368
|
-
* 清除重连计时器
|
|
369
|
-
*/
|
|
370
389
|
clearReconnectTimer() {
|
|
371
390
|
if (this.reconnectTimer) {
|
|
372
391
|
clearTimeout(this.reconnectTimer);
|
|
373
392
|
this.reconnectTimer = null;
|
|
374
393
|
}
|
|
375
394
|
}
|
|
376
|
-
/**
|
|
377
|
-
* 清除所有待处理请求
|
|
378
|
-
*/
|
|
379
395
|
clearPendingRequests() {
|
|
380
|
-
for (const [
|
|
396
|
+
for (const [, { reject, timer }] of this.pendingRequests) {
|
|
381
397
|
clearTimeout(timer);
|
|
382
|
-
reject(new WebSocketError(
|
|
398
|
+
reject(new WebSocketError("Connection closed", "CONNECTION_CLOSED"));
|
|
383
399
|
}
|
|
384
400
|
this.pendingRequests.clear();
|
|
385
401
|
}
|
|
386
|
-
/**
|
|
387
|
-
* 初始化数据
|
|
388
|
-
*/
|
|
389
|
-
async initializeData() {
|
|
390
|
-
try {
|
|
391
|
-
// 并行获取所有配置和 Schema
|
|
392
|
-
const [configs, schemas] = await Promise.all([
|
|
393
|
-
this.getAllConfigs().catch(error => {
|
|
394
|
-
console.warn('[WebSocket] Failed to load configs:', error);
|
|
395
|
-
return {};
|
|
396
|
-
}),
|
|
397
|
-
this.getAllSchemas().catch(error => {
|
|
398
|
-
console.warn('[WebSocket] Failed to load schemas:', error);
|
|
399
|
-
return {};
|
|
400
|
-
})
|
|
401
|
-
]);
|
|
402
|
-
// 更新 Redux 状态
|
|
403
|
-
store.dispatch(updateConfigs(configs));
|
|
404
|
-
store.dispatch(updateSchemas(schemas));
|
|
405
|
-
}
|
|
406
|
-
catch (error) {
|
|
407
|
-
console.error('[WebSocket] Data initialization failed:', error);
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
402
|
}
|
|
411
403
|
//# sourceMappingURL=manager.js.map
|