@hangox/mg-cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cli.js +1574 -0
  2. package/dist/cli.js.map +1 -0
  3. package/dist/daemon-runner.js +794 -0
  4. package/dist/daemon-runner.js.map +1 -0
  5. package/dist/index-DNrszrq9.d.ts +568 -0
  6. package/dist/index.d.ts +129 -0
  7. package/dist/index.js +950 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/server.d.ts +2 -0
  10. package/dist/server.js +689 -0
  11. package/dist/server.js.map +1 -0
  12. package/package.json +5 -1
  13. package/.eslintrc.cjs +0 -26
  14. package/CLAUDE.md +0 -43
  15. package/src/cli/client.ts +0 -266
  16. package/src/cli/commands/execute-code.ts +0 -59
  17. package/src/cli/commands/export-image.ts +0 -193
  18. package/src/cli/commands/get-all-nodes.ts +0 -81
  19. package/src/cli/commands/get-all-pages.ts +0 -118
  20. package/src/cli/commands/get-node-by-id.ts +0 -83
  21. package/src/cli/commands/get-node-by-link.ts +0 -105
  22. package/src/cli/commands/server.ts +0 -130
  23. package/src/cli/index.ts +0 -33
  24. package/src/index.ts +0 -9
  25. package/src/server/connection-manager.ts +0 -211
  26. package/src/server/daemon-runner.ts +0 -22
  27. package/src/server/daemon.ts +0 -211
  28. package/src/server/index.ts +0 -8
  29. package/src/server/logger.ts +0 -117
  30. package/src/server/request-handler.ts +0 -192
  31. package/src/server/websocket-server.ts +0 -297
  32. package/src/shared/constants.ts +0 -90
  33. package/src/shared/errors.ts +0 -131
  34. package/src/shared/index.ts +0 -8
  35. package/src/shared/types.ts +0 -227
  36. package/src/shared/utils.ts +0 -352
  37. package/tests/unit/shared/constants.test.ts +0 -66
  38. package/tests/unit/shared/errors.test.ts +0 -82
  39. package/tests/unit/shared/utils.test.ts +0 -208
  40. package/tsconfig.json +0 -22
  41. package/tsup.config.ts +0 -33
  42. package/vitest.config.ts +0 -22
package/dist/index.js ADDED
@@ -0,0 +1,950 @@
1
+ // src/shared/constants.ts
2
+ import { homedir } from "os";
3
+ import { join } from "path";
4
+ var DEFAULT_PORT = 9527;
5
+ var PORT_RANGE_START = 9527;
6
+ var PORT_RANGE_END = 9536;
7
+ var MAX_PORT_ATTEMPTS = 10;
8
+ var PORT_SCAN_TIMEOUT = 500;
9
+ var CONFIG_DIR = join(homedir(), ".mg-plugin");
10
+ var SERVER_INFO_FILE = join(CONFIG_DIR, "server.json");
11
+ var LOG_DIR = join(CONFIG_DIR, "logs");
12
+ var SERVER_LOG_FILE = join(LOG_DIR, "server.log");
13
+ var HEARTBEAT_INTERVAL = 3e4;
14
+ var HEARTBEAT_TIMEOUT = 9e4;
15
+ var REQUEST_TIMEOUT = 3e4;
16
+ var SERVER_START_TIMEOUT = 5e3;
17
+ var RETRY_INTERVALS = [1e3, 2e3, 4e3];
18
+ var MAX_RETRY_COUNT = 3;
19
+ var ConnectionType = /* @__PURE__ */ ((ConnectionType2) => {
20
+ ConnectionType2["CONSUMER"] = "consumer";
21
+ ConnectionType2["PROVIDER"] = "provider";
22
+ return ConnectionType2;
23
+ })(ConnectionType || {});
24
+ var MessageType = /* @__PURE__ */ ((MessageType2) => {
25
+ MessageType2["PING"] = "ping";
26
+ MessageType2["PONG"] = "pong";
27
+ MessageType2["REGISTER"] = "register";
28
+ MessageType2["REGISTER_ACK"] = "register_ack";
29
+ MessageType2["GET_NODE_BY_ID"] = "get_node_by_id";
30
+ MessageType2["GET_ALL_NODES"] = "get_all_nodes";
31
+ MessageType2["GET_SELECTION"] = "get_selection";
32
+ MessageType2["EXPORT_IMAGE"] = "export_image";
33
+ MessageType2["EXECUTE_CODE"] = "execute_code";
34
+ MessageType2["GET_ALL_PAGES"] = "get_all_pages";
35
+ MessageType2["RESPONSE"] = "response";
36
+ MessageType2["ERROR"] = "error";
37
+ return MessageType2;
38
+ })(MessageType || {});
39
+
40
+ // src/shared/errors.ts
41
+ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
42
+ ErrorCode2["CONNECTION_FAILED"] = "E001";
43
+ ErrorCode2["CONNECTION_TIMEOUT"] = "E002";
44
+ ErrorCode2["NO_PAGE_CONNECTED"] = "E003";
45
+ ErrorCode2["PAGE_NOT_FOUND"] = "E004";
46
+ ErrorCode2["NODE_NOT_FOUND"] = "E005";
47
+ ErrorCode2["NO_SELECTION"] = "E006";
48
+ ErrorCode2["MG_UNAVAILABLE"] = "E007";
49
+ ErrorCode2["EXPORT_FAILED"] = "E008";
50
+ ErrorCode2["FILE_WRITE_FAILED"] = "E009";
51
+ ErrorCode2["INVALID_LINK"] = "E010";
52
+ ErrorCode2["INVALID_PARAMS"] = "E011";
53
+ ErrorCode2["REQUEST_TIMEOUT"] = "E012";
54
+ ErrorCode2["PORT_EXHAUSTED"] = "E013";
55
+ ErrorCode2["SERVER_DISCOVERY_FAILED"] = "E014";
56
+ ErrorCode2["SERVER_START_FAILED"] = "E015";
57
+ ErrorCode2["SERVER_ALREADY_RUNNING"] = "E016";
58
+ ErrorCode2["CONNECTION_LOST"] = "E017";
59
+ ErrorCode2["UNKNOWN_ERROR"] = "E099";
60
+ return ErrorCode2;
61
+ })(ErrorCode || {});
62
+ var ErrorNames = {
63
+ ["E001" /* CONNECTION_FAILED */]: "CONNECTION_FAILED",
64
+ ["E002" /* CONNECTION_TIMEOUT */]: "CONNECTION_TIMEOUT",
65
+ ["E003" /* NO_PAGE_CONNECTED */]: "NO_PAGE_CONNECTED",
66
+ ["E004" /* PAGE_NOT_FOUND */]: "PAGE_NOT_FOUND",
67
+ ["E005" /* NODE_NOT_FOUND */]: "NODE_NOT_FOUND",
68
+ ["E006" /* NO_SELECTION */]: "NO_SELECTION",
69
+ ["E007" /* MG_UNAVAILABLE */]: "MG_UNAVAILABLE",
70
+ ["E008" /* EXPORT_FAILED */]: "EXPORT_FAILED",
71
+ ["E009" /* FILE_WRITE_FAILED */]: "FILE_WRITE_FAILED",
72
+ ["E010" /* INVALID_LINK */]: "INVALID_LINK",
73
+ ["E011" /* INVALID_PARAMS */]: "INVALID_PARAMS",
74
+ ["E012" /* REQUEST_TIMEOUT */]: "REQUEST_TIMEOUT",
75
+ ["E013" /* PORT_EXHAUSTED */]: "PORT_EXHAUSTED",
76
+ ["E014" /* SERVER_DISCOVERY_FAILED */]: "SERVER_DISCOVERY_FAILED",
77
+ ["E015" /* SERVER_START_FAILED */]: "SERVER_START_FAILED",
78
+ ["E016" /* SERVER_ALREADY_RUNNING */]: "SERVER_ALREADY_RUNNING",
79
+ ["E017" /* CONNECTION_LOST */]: "CONNECTION_LOST",
80
+ ["E099" /* UNKNOWN_ERROR */]: "UNKNOWN_ERROR"
81
+ };
82
+ var ErrorMessages = {
83
+ ["E001" /* CONNECTION_FAILED */]: "\u65E0\u6CD5\u8FDE\u63A5\u5230 MG Server",
84
+ ["E002" /* CONNECTION_TIMEOUT */]: "\u8FDE\u63A5\u8D85\u65F6",
85
+ ["E003" /* NO_PAGE_CONNECTED */]: "\u6CA1\u6709 MasterGo \u9875\u9762\u8FDE\u63A5\u5230 Server",
86
+ ["E004" /* PAGE_NOT_FOUND */]: "\u672A\u627E\u5230\u5339\u914D\u7684\u9875\u9762",
87
+ ["E005" /* NODE_NOT_FOUND */]: "\u8282\u70B9\u4E0D\u5B58\u5728",
88
+ ["E006" /* NO_SELECTION */]: "\u6CA1\u6709\u9009\u4E2D\u4EFB\u4F55\u8282\u70B9",
89
+ ["E007" /* MG_UNAVAILABLE */]: "mg \u5BF9\u8C61\u4E0D\u53EF\u7528",
90
+ ["E008" /* EXPORT_FAILED */]: "\u5BFC\u51FA\u56FE\u7247\u5931\u8D25",
91
+ ["E009" /* FILE_WRITE_FAILED */]: "\u6587\u4EF6\u5199\u5165\u5931\u8D25",
92
+ ["E010" /* INVALID_LINK */]: "\u65E0\u6548\u7684 mgp:// \u94FE\u63A5\u683C\u5F0F",
93
+ ["E011" /* INVALID_PARAMS */]: "\u53C2\u6570\u6821\u9A8C\u5931\u8D25",
94
+ ["E012" /* REQUEST_TIMEOUT */]: "\u8BF7\u6C42\u8D85\u65F6",
95
+ ["E013" /* PORT_EXHAUSTED */]: "\u6240\u6709\u5907\u9009\u7AEF\u53E3\u5747\u88AB\u5360\u7528",
96
+ ["E014" /* SERVER_DISCOVERY_FAILED */]: "\u65E0\u6CD5\u53D1\u73B0 Server (\u7AEF\u53E3\u626B\u63CF\u5931\u8D25)",
97
+ ["E015" /* SERVER_START_FAILED */]: "\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25",
98
+ ["E016" /* SERVER_ALREADY_RUNNING */]: "Server \u5DF2\u5728\u8FD0\u884C\u4E2D",
99
+ ["E017" /* CONNECTION_LOST */]: "\u8FDE\u63A5\u65AD\u5F00",
100
+ ["E099" /* UNKNOWN_ERROR */]: "\u672A\u77E5\u9519\u8BEF"
101
+ };
102
+ var MGError = class extends Error {
103
+ /** 错误码 */
104
+ code;
105
+ /** 错误名称 */
106
+ errorName;
107
+ /** 额外详情 */
108
+ details;
109
+ constructor(code, message, details) {
110
+ super(message || ErrorMessages[code]);
111
+ this.name = "MGError";
112
+ this.code = code;
113
+ this.errorName = ErrorNames[code];
114
+ this.details = details;
115
+ }
116
+ /** 转换为 JSON 格式 */
117
+ toJSON() {
118
+ return {
119
+ code: this.code,
120
+ name: this.errorName,
121
+ message: this.message,
122
+ details: this.details
123
+ };
124
+ }
125
+ /** 格式化输出 */
126
+ toString() {
127
+ return `\u9519\u8BEF [${this.code}]: ${this.message}`;
128
+ }
129
+ };
130
+ function createError(code, message, details) {
131
+ return new MGError(code, message, details);
132
+ }
133
+
134
+ // src/shared/utils.ts
135
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from "fs";
136
+ import { dirname, resolve, isAbsolute } from "path";
137
+ function ensureDir(dir) {
138
+ if (!existsSync(dir)) {
139
+ mkdirSync(dir, { recursive: true });
140
+ }
141
+ }
142
+ function ensureConfigDir() {
143
+ ensureDir(CONFIG_DIR);
144
+ ensureDir(LOG_DIR);
145
+ }
146
+ function readServerInfo() {
147
+ try {
148
+ if (!existsSync(SERVER_INFO_FILE)) {
149
+ return null;
150
+ }
151
+ const content = readFileSync(SERVER_INFO_FILE, "utf-8");
152
+ return JSON.parse(content);
153
+ } catch {
154
+ return null;
155
+ }
156
+ }
157
+ function writeServerInfo(info) {
158
+ ensureConfigDir();
159
+ writeFileSync(SERVER_INFO_FILE, JSON.stringify(info, null, 2), "utf-8");
160
+ }
161
+ function deleteServerInfo() {
162
+ try {
163
+ if (existsSync(SERVER_INFO_FILE)) {
164
+ unlinkSync(SERVER_INFO_FILE);
165
+ }
166
+ } catch {
167
+ }
168
+ }
169
+ function resolveOutputPath(outputPath) {
170
+ if (isAbsolute(outputPath)) {
171
+ return outputPath;
172
+ }
173
+ return resolve(process.cwd(), outputPath);
174
+ }
175
+ function ensureOutputDir(outputPath) {
176
+ const dir = dirname(outputPath);
177
+ ensureDir(dir);
178
+ }
179
+ function isProcessRunning(pid) {
180
+ try {
181
+ process.kill(pid, 0);
182
+ return true;
183
+ } catch {
184
+ return false;
185
+ }
186
+ }
187
+ function killProcess(pid) {
188
+ try {
189
+ process.kill(pid, "SIGTERM");
190
+ return true;
191
+ } catch {
192
+ return false;
193
+ }
194
+ }
195
+ function normalizePageUrl(url) {
196
+ try {
197
+ const urlObj = new URL(url);
198
+ return urlObj.host + urlObj.pathname;
199
+ } catch {
200
+ return url;
201
+ }
202
+ }
203
+ function isDesignPageUrl(url) {
204
+ return /\/file\/\d+/.test(url);
205
+ }
206
+ function parseMgpLink(link) {
207
+ if (!link.startsWith("mgp://")) {
208
+ return null;
209
+ }
210
+ try {
211
+ const urlPart = link.slice(6);
212
+ const questionMarkIndex = urlPart.indexOf("?");
213
+ if (questionMarkIndex === -1) {
214
+ return null;
215
+ }
216
+ const pageUrl = urlPart.slice(0, questionMarkIndex);
217
+ const queryString = urlPart.slice(questionMarkIndex + 1);
218
+ const params = new URLSearchParams(queryString);
219
+ const encodedNodeId = params.get("nodeId");
220
+ if (!encodedNodeId) {
221
+ return null;
222
+ }
223
+ const nodeId = decodeURIComponent(encodedNodeId);
224
+ if (!/^(\d+:\d+)(\/\d+:\d+)*$/.test(nodeId)) {
225
+ return null;
226
+ }
227
+ const encodedNodePath = params.get("nodePath");
228
+ let nodePath;
229
+ if (encodedNodePath) {
230
+ const decodedNodePath = decodeURIComponent(encodedNodePath);
231
+ nodePath = decodedNodePath.split("/").filter(Boolean);
232
+ if (!nodePath.every((segment) => /^\d+:\d+$/.test(segment))) {
233
+ return null;
234
+ }
235
+ }
236
+ return {
237
+ pageUrl,
238
+ nodeId,
239
+ nodePath
240
+ };
241
+ } catch {
242
+ return null;
243
+ }
244
+ }
245
+ function generateMgpLink(pageUrl, nodeId, nodePath) {
246
+ const normalizedUrl = normalizePageUrl(pageUrl);
247
+ const encodedNodeId = encodeURIComponent(nodeId);
248
+ let link = `mgp://${normalizedUrl}?nodeId=${encodedNodeId}`;
249
+ if (nodePath && nodePath.length > 0) {
250
+ const encodedNodePath = encodeURIComponent(nodePath.join("/"));
251
+ link += `&nodePath=${encodedNodePath}`;
252
+ }
253
+ return link;
254
+ }
255
+ function formatFileSize(bytes) {
256
+ if (bytes < 1024) {
257
+ return `${bytes} \u5B57\u8282`;
258
+ }
259
+ const kb = bytes / 1024;
260
+ if (kb < 1024) {
261
+ return `${kb.toFixed(2)} KB`;
262
+ }
263
+ const mb = kb / 1024;
264
+ return `${mb.toFixed(2)} MB`;
265
+ }
266
+ function formatDuration(ms) {
267
+ const seconds = Math.floor(ms / 1e3);
268
+ const minutes = Math.floor(seconds / 60);
269
+ const hours = Math.floor(minutes / 60);
270
+ const days = Math.floor(hours / 24);
271
+ if (days > 0) {
272
+ return `${days} \u5929 ${hours % 24} \u5C0F\u65F6`;
273
+ }
274
+ if (hours > 0) {
275
+ return `${hours} \u5C0F\u65F6 ${minutes % 60} \u5206\u949F`;
276
+ }
277
+ if (minutes > 0) {
278
+ return `${minutes} \u5206\u949F ${seconds % 60} \u79D2`;
279
+ }
280
+ return `${seconds} \u79D2`;
281
+ }
282
+ function generateId() {
283
+ return `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
284
+ }
285
+ function getCurrentISOTime() {
286
+ return (/* @__PURE__ */ new Date()).toISOString();
287
+ }
288
+ function formatLogTime(date = /* @__PURE__ */ new Date()) {
289
+ const year = date.getFullYear();
290
+ const month = String(date.getMonth() + 1).padStart(2, "0");
291
+ const day = String(date.getDate()).padStart(2, "0");
292
+ const hours = String(date.getHours()).padStart(2, "0");
293
+ const minutes = String(date.getMinutes()).padStart(2, "0");
294
+ const seconds = String(date.getSeconds()).padStart(2, "0");
295
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
296
+ }
297
+ function extractFileIdFromUrl(url) {
298
+ const match = url.match(/\/file\/(\d+)/);
299
+ return match ? match[1] : null;
300
+ }
301
+ function extractFileIdFromMgpLink(link) {
302
+ if (!link.startsWith("mgp://")) {
303
+ return null;
304
+ }
305
+ return extractFileIdFromUrl(link);
306
+ }
307
+ function extractFileId(input) {
308
+ const trimmed = input.trim();
309
+ if (/^\d+$/.test(trimmed)) {
310
+ return trimmed;
311
+ }
312
+ if (trimmed.startsWith("mgp://")) {
313
+ return extractFileIdFromMgpLink(trimmed);
314
+ }
315
+ if (trimmed.startsWith("http://") || trimmed.startsWith("https://")) {
316
+ return extractFileIdFromUrl(trimmed);
317
+ }
318
+ return extractFileIdFromUrl(trimmed);
319
+ }
320
+
321
+ // src/server/websocket-server.ts
322
+ import { WebSocketServer } from "ws";
323
+
324
+ // src/server/logger.ts
325
+ import { appendFileSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
326
+ import { dirname as dirname2 } from "path";
327
+ var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
328
+ LogLevel2["DEBUG"] = "DEBUG";
329
+ LogLevel2["INFO"] = "INFO";
330
+ LogLevel2["WARN"] = "WARN";
331
+ LogLevel2["ERROR"] = "ERROR";
332
+ return LogLevel2;
333
+ })(LogLevel || {});
334
+ var levelPriority = {
335
+ ["DEBUG" /* DEBUG */]: 0,
336
+ ["INFO" /* INFO */]: 1,
337
+ ["WARN" /* WARN */]: 2,
338
+ ["ERROR" /* ERROR */]: 3
339
+ };
340
+ var Logger = class {
341
+ options;
342
+ constructor(options = {}) {
343
+ this.options = {
344
+ console: options.console ?? true,
345
+ file: options.file ?? false,
346
+ filePath: options.filePath ?? SERVER_LOG_FILE,
347
+ minLevel: options.minLevel ?? "INFO" /* INFO */
348
+ };
349
+ if (this.options.file) {
350
+ const dir = dirname2(this.options.filePath);
351
+ if (!existsSync2(dir)) {
352
+ mkdirSync2(dir, { recursive: true });
353
+ }
354
+ }
355
+ }
356
+ /**
357
+ * 记录日志
358
+ */
359
+ log(level, message, ...args) {
360
+ if (levelPriority[level] < levelPriority[this.options.minLevel]) {
361
+ return;
362
+ }
363
+ const timestamp = formatLogTime();
364
+ const formattedMessage = `[${timestamp}] [${level}] ${message}`;
365
+ if (this.options.console) {
366
+ const consoleMethod = level === "ERROR" /* ERROR */ ? console.error : console.log;
367
+ if (args.length > 0) {
368
+ consoleMethod(formattedMessage, ...args);
369
+ } else {
370
+ consoleMethod(formattedMessage);
371
+ }
372
+ }
373
+ if (this.options.file) {
374
+ try {
375
+ const fileMessage = args.length > 0 ? `${formattedMessage} ${JSON.stringify(args)}
376
+ ` : `${formattedMessage}
377
+ `;
378
+ appendFileSync(this.options.filePath, fileMessage);
379
+ } catch (error) {
380
+ if (this.options.console) {
381
+ console.error("\u65E5\u5FD7\u6587\u4EF6\u5199\u5165\u5931\u8D25:", error);
382
+ }
383
+ }
384
+ }
385
+ }
386
+ debug(message, ...args) {
387
+ this.log("DEBUG" /* DEBUG */, message, ...args);
388
+ }
389
+ info(message, ...args) {
390
+ this.log("INFO" /* INFO */, message, ...args);
391
+ }
392
+ warn(message, ...args) {
393
+ this.log("WARN" /* WARN */, message, ...args);
394
+ }
395
+ error(message, ...args) {
396
+ this.log("ERROR" /* ERROR */, message, ...args);
397
+ }
398
+ };
399
+ function createLogger(options) {
400
+ return new Logger(options);
401
+ }
402
+
403
+ // src/server/connection-manager.ts
404
+ var ConnectionManager = class {
405
+ logger;
406
+ /** Provider 连接(按页面 URL 索引) */
407
+ providers = /* @__PURE__ */ new Map();
408
+ /** Consumer 连接 */
409
+ consumers = /* @__PURE__ */ new Map();
410
+ /** 所有连接(按 ID 索引) */
411
+ allConnections = /* @__PURE__ */ new Map();
412
+ /** 心跳检查定时器 */
413
+ heartbeatTimer = null;
414
+ constructor(logger) {
415
+ this.logger = logger;
416
+ }
417
+ /**
418
+ * 启动心跳检查
419
+ */
420
+ startHeartbeatCheck(interval = 3e4) {
421
+ if (this.heartbeatTimer) {
422
+ clearInterval(this.heartbeatTimer);
423
+ }
424
+ this.heartbeatTimer = setInterval(() => {
425
+ this.checkHeartbeats();
426
+ }, interval);
427
+ }
428
+ /**
429
+ * 停止心跳检查
430
+ */
431
+ stopHeartbeatCheck() {
432
+ if (this.heartbeatTimer) {
433
+ clearInterval(this.heartbeatTimer);
434
+ this.heartbeatTimer = null;
435
+ }
436
+ }
437
+ /**
438
+ * 检查所有连接的心跳
439
+ */
440
+ checkHeartbeats() {
441
+ const now = Date.now();
442
+ for (const [id, ws] of this.allConnections) {
443
+ const lastActive = ws.connectionInfo.lastActiveAt.getTime();
444
+ const elapsed = now - lastActive;
445
+ if (elapsed > HEARTBEAT_TIMEOUT) {
446
+ this.logger.warn(`\u8FDE\u63A5 ${id} \u5FC3\u8DF3\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5`);
447
+ this.removeConnection(ws);
448
+ ws.terminate();
449
+ }
450
+ }
451
+ }
452
+ /**
453
+ * 添加连接
454
+ */
455
+ addConnection(ws, type, pageUrl, pageId) {
456
+ const connectionId = generateId();
457
+ const now = /* @__PURE__ */ new Date();
458
+ const connectionInfo = {
459
+ id: connectionId,
460
+ type,
461
+ pageUrl,
462
+ pageId,
463
+ connectedAt: now,
464
+ lastActiveAt: now
465
+ };
466
+ const managedWs = ws;
467
+ managedWs.connectionId = connectionId;
468
+ managedWs.connectionInfo = connectionInfo;
469
+ managedWs.isAlive = true;
470
+ this.allConnections.set(connectionId, managedWs);
471
+ if (type === "provider" /* PROVIDER */ && pageUrl) {
472
+ const existing = this.providers.get(pageUrl);
473
+ if (existing) {
474
+ this.logger.info(`\u9875\u9762 ${pageUrl} \u5DF2\u6709\u8FDE\u63A5\uFF0C\u66FF\u6362\u4E3A\u65B0\u8FDE\u63A5`);
475
+ this.removeConnection(existing);
476
+ existing.close();
477
+ }
478
+ this.providers.set(pageUrl, managedWs);
479
+ this.logger.info(`Provider \u8FDE\u63A5: ${pageUrl}`);
480
+ } else if (type === "consumer" /* CONSUMER */) {
481
+ this.consumers.set(connectionId, managedWs);
482
+ this.logger.info(`Consumer \u8FDE\u63A5: ${connectionId}`);
483
+ }
484
+ return managedWs;
485
+ }
486
+ /**
487
+ * 移除连接
488
+ */
489
+ removeConnection(ws) {
490
+ const { connectionId, connectionInfo } = ws;
491
+ this.allConnections.delete(connectionId);
492
+ if (connectionInfo.type === "provider" /* PROVIDER */ && connectionInfo.pageUrl) {
493
+ this.providers.delete(connectionInfo.pageUrl);
494
+ this.logger.info(`Provider \u65AD\u5F00: ${connectionInfo.pageUrl}`);
495
+ } else if (connectionInfo.type === "consumer" /* CONSUMER */) {
496
+ this.consumers.delete(connectionId);
497
+ this.logger.info(`Consumer \u65AD\u5F00: ${connectionId}`);
498
+ }
499
+ }
500
+ /**
501
+ * 更新连接活跃时间
502
+ */
503
+ updateLastActive(ws) {
504
+ ws.connectionInfo.lastActiveAt = /* @__PURE__ */ new Date();
505
+ ws.isAlive = true;
506
+ }
507
+ /**
508
+ * 根据页面 URL 查找 Provider
509
+ */
510
+ findProviderByPageUrl(pageUrl) {
511
+ return this.providers.get(pageUrl);
512
+ }
513
+ /**
514
+ * 获取第一个可用的 Provider
515
+ */
516
+ getFirstProvider() {
517
+ const iterator = this.providers.values();
518
+ const first = iterator.next();
519
+ return first.value;
520
+ }
521
+ /**
522
+ * 获取所有 Provider 信息
523
+ */
524
+ getAllProviders() {
525
+ return Array.from(this.providers.values()).map((ws) => ws.connectionInfo);
526
+ }
527
+ /**
528
+ * 获取连接统计
529
+ */
530
+ getStats() {
531
+ return {
532
+ providers: this.providers.size,
533
+ consumers: this.consumers.size,
534
+ total: this.allConnections.size
535
+ };
536
+ }
537
+ /**
538
+ * 获取所有已连接的页面 URL
539
+ */
540
+ getConnectedPageUrls() {
541
+ return Array.from(this.providers.keys());
542
+ }
543
+ /**
544
+ * 关闭所有连接
545
+ */
546
+ closeAll() {
547
+ this.stopHeartbeatCheck();
548
+ for (const ws of this.allConnections.values()) {
549
+ ws.close();
550
+ }
551
+ this.providers.clear();
552
+ this.consumers.clear();
553
+ this.allConnections.clear();
554
+ }
555
+ };
556
+
557
+ // src/server/request-handler.ts
558
+ var RequestHandler = class {
559
+ logger;
560
+ connectionManager;
561
+ /** 待处理的请求 */
562
+ pendingRequests = /* @__PURE__ */ new Map();
563
+ constructor(connectionManager, logger) {
564
+ this.connectionManager = connectionManager;
565
+ this.logger = logger;
566
+ }
567
+ /**
568
+ * 处理 Consumer 请求
569
+ */
570
+ async handleRequest(consumer, message) {
571
+ const requestId = message.id || generateId();
572
+ const { type, pageUrl, params } = message;
573
+ this.logger.info(`\u6536\u5230\u8BF7\u6C42: ${type} (${requestId})`, { pageUrl });
574
+ let provider;
575
+ if (pageUrl) {
576
+ provider = this.connectionManager.findProviderByPageUrl(pageUrl);
577
+ if (!provider) {
578
+ this.sendError(consumer, requestId, "E004" /* PAGE_NOT_FOUND */, `\u672A\u627E\u5230\u9875\u9762: ${pageUrl}`);
579
+ return;
580
+ }
581
+ } else {
582
+ provider = this.connectionManager.getFirstProvider();
583
+ if (!provider) {
584
+ this.sendError(consumer, requestId, "E003" /* NO_PAGE_CONNECTED */, "\u6CA1\u6709\u9875\u9762\u8FDE\u63A5\u5230 Server");
585
+ return;
586
+ }
587
+ }
588
+ const timer = setTimeout(() => {
589
+ this.handleTimeout(requestId);
590
+ }, REQUEST_TIMEOUT);
591
+ this.pendingRequests.set(requestId, {
592
+ id: requestId,
593
+ consumer,
594
+ timer,
595
+ timestamp: Date.now()
596
+ });
597
+ const forwardMessage = {
598
+ id: requestId,
599
+ type,
600
+ pageUrl: pageUrl || provider.connectionInfo.pageUrl,
601
+ params,
602
+ timestamp: Date.now()
603
+ };
604
+ try {
605
+ provider.send(JSON.stringify(forwardMessage));
606
+ this.logger.info(`\u8BF7\u6C42\u8F6C\u53D1: ${type} -> ${provider.connectionInfo.pageUrl}`);
607
+ } catch {
608
+ this.cleanupRequest(requestId);
609
+ this.sendError(consumer, requestId, "E001" /* CONNECTION_FAILED */, "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25");
610
+ }
611
+ }
612
+ /**
613
+ * 处理 Provider 响应
614
+ */
615
+ handleResponse(response) {
616
+ const { id } = response;
617
+ const pending = this.pendingRequests.get(id);
618
+ if (!pending) {
619
+ this.logger.warn(`\u6536\u5230\u672A\u77E5\u8BF7\u6C42\u7684\u54CD\u5E94: ${id}`);
620
+ return;
621
+ }
622
+ this.cleanupRequest(id);
623
+ try {
624
+ pending.consumer.send(JSON.stringify(response));
625
+ this.logger.info(
626
+ `\u54CD\u5E94\u8FD4\u56DE: ${id} (${response.success ? "\u6210\u529F" : "\u5931\u8D25"})`
627
+ );
628
+ } catch (error) {
629
+ this.logger.error(`\u54CD\u5E94\u8F6C\u53D1\u5931\u8D25: ${id}`, error);
630
+ }
631
+ }
632
+ /**
633
+ * 处理请求超时
634
+ */
635
+ handleTimeout(requestId) {
636
+ const pending = this.pendingRequests.get(requestId);
637
+ if (!pending) return;
638
+ this.logger.warn(`\u8BF7\u6C42\u8D85\u65F6: ${requestId}`);
639
+ this.cleanupRequest(requestId);
640
+ this.sendError(pending.consumer, requestId, "E012" /* REQUEST_TIMEOUT */, "\u8BF7\u6C42\u8D85\u65F6");
641
+ }
642
+ /**
643
+ * 清理请求
644
+ */
645
+ cleanupRequest(requestId) {
646
+ const pending = this.pendingRequests.get(requestId);
647
+ if (pending) {
648
+ clearTimeout(pending.timer);
649
+ this.pendingRequests.delete(requestId);
650
+ }
651
+ }
652
+ /**
653
+ * 发送错误响应
654
+ */
655
+ sendError(consumer, requestId, code, message) {
656
+ const error = new MGError(code, message);
657
+ const response = {
658
+ id: requestId,
659
+ type: "error" /* ERROR */,
660
+ success: false,
661
+ data: null,
662
+ error: error.toJSON()
663
+ };
664
+ try {
665
+ consumer.send(JSON.stringify(response));
666
+ } catch (err) {
667
+ this.logger.error(`\u53D1\u9001\u9519\u8BEF\u54CD\u5E94\u5931\u8D25: ${requestId}`, err);
668
+ }
669
+ }
670
+ /**
671
+ * 清理特定连接的所有待处理请求
672
+ */
673
+ cleanupConnectionRequests(connectionId) {
674
+ for (const [requestId, pending] of this.pendingRequests) {
675
+ if (pending.consumer.connectionId === connectionId) {
676
+ this.cleanupRequest(requestId);
677
+ }
678
+ }
679
+ }
680
+ /**
681
+ * 清理所有待处理请求
682
+ */
683
+ cleanupAll() {
684
+ for (const [requestId] of this.pendingRequests) {
685
+ this.cleanupRequest(requestId);
686
+ }
687
+ }
688
+ };
689
+
690
+ // src/server/websocket-server.ts
691
+ var MGServer = class {
692
+ wss = null;
693
+ logger;
694
+ connectionManager;
695
+ requestHandler;
696
+ port;
697
+ isRunning = false;
698
+ constructor(options = {}) {
699
+ this.port = options.port || DEFAULT_PORT;
700
+ this.logger = options.logger || createLogger();
701
+ this.connectionManager = new ConnectionManager(this.logger);
702
+ this.requestHandler = new RequestHandler(this.connectionManager, this.logger);
703
+ }
704
+ /**
705
+ * 启动服务器
706
+ */
707
+ async start() {
708
+ if (this.isRunning) {
709
+ throw new MGError("E016" /* SERVER_ALREADY_RUNNING */, "Server \u5DF2\u5728\u8FD0\u884C\u4E2D");
710
+ }
711
+ const port = await this.findAvailablePort();
712
+ return new Promise((resolve2, reject) => {
713
+ this.wss = new WebSocketServer({ port });
714
+ this.wss.on("listening", () => {
715
+ this.port = port;
716
+ this.isRunning = true;
717
+ this.logger.info(`Server \u542F\u52A8\u6210\u529F\uFF0C\u76D1\u542C\u7AEF\u53E3: ${port}`);
718
+ this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL);
719
+ resolve2(port);
720
+ });
721
+ this.wss.on("error", (error) => {
722
+ this.logger.error("Server \u9519\u8BEF:", error);
723
+ reject(error);
724
+ });
725
+ this.wss.on("connection", (ws, request) => {
726
+ this.handleConnection(ws, request);
727
+ });
728
+ });
729
+ }
730
+ /**
731
+ * 查找可用端口
732
+ */
733
+ async findAvailablePort() {
734
+ for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
735
+ const available = await this.isPortAvailable(port);
736
+ if (available) {
737
+ return port;
738
+ }
739
+ this.logger.debug(`\u7AEF\u53E3 ${port} \u88AB\u5360\u7528\uFF0C\u5C1D\u8BD5\u4E0B\u4E00\u4E2A`);
740
+ }
741
+ throw new MGError(
742
+ "E013" /* PORT_EXHAUSTED */,
743
+ `\u7AEF\u53E3 ${PORT_RANGE_START}-${PORT_RANGE_END} \u5747\u88AB\u5360\u7528`
744
+ );
745
+ }
746
+ /**
747
+ * 检查端口是否可用
748
+ */
749
+ isPortAvailable(port) {
750
+ return new Promise((resolve2) => {
751
+ const testServer = new WebSocketServer({ port });
752
+ testServer.on("listening", () => {
753
+ testServer.close();
754
+ resolve2(true);
755
+ });
756
+ testServer.on("error", () => {
757
+ resolve2(false);
758
+ });
759
+ });
760
+ }
761
+ /**
762
+ * 处理新连接
763
+ */
764
+ handleConnection(ws, _request) {
765
+ this.logger.info("\u65B0\u8FDE\u63A5\u5EFA\u7ACB");
766
+ const registerTimeout = setTimeout(() => {
767
+ this.logger.warn("\u8FDE\u63A5\u6CE8\u518C\u8D85\u65F6\uFF0C\u5173\u95ED\u8FDE\u63A5");
768
+ ws.close();
769
+ }, 5e3);
770
+ ws.on("message", (data) => {
771
+ try {
772
+ const message = JSON.parse(data.toString());
773
+ if (message.type === "register" /* REGISTER */) {
774
+ clearTimeout(registerTimeout);
775
+ this.handleRegister(ws, message);
776
+ return;
777
+ }
778
+ const managedWs = ws;
779
+ if (!managedWs.connectionId) {
780
+ this.logger.warn("\u672A\u6CE8\u518C\u7684\u8FDE\u63A5\u53D1\u9001\u6D88\u606F\uFF0C\u5FFD\u7565");
781
+ return;
782
+ }
783
+ this.handleMessage(managedWs, message);
784
+ } catch (error) {
785
+ this.logger.error("\u6D88\u606F\u89E3\u6790\u5931\u8D25:", error);
786
+ }
787
+ });
788
+ ws.on("close", () => {
789
+ clearTimeout(registerTimeout);
790
+ const managedWs = ws;
791
+ if (managedWs.connectionId) {
792
+ this.requestHandler.cleanupConnectionRequests(managedWs.connectionId);
793
+ this.connectionManager.removeConnection(managedWs);
794
+ }
795
+ });
796
+ ws.on("error", (error) => {
797
+ this.logger.error("WebSocket \u9519\u8BEF:", error);
798
+ });
799
+ }
800
+ /**
801
+ * 处理注册消息
802
+ */
803
+ handleRegister(ws, message) {
804
+ const { connectionType, pageUrl, pageId } = message.data;
805
+ const managedWs = this.connectionManager.addConnection(
806
+ ws,
807
+ connectionType,
808
+ pageUrl,
809
+ pageId
810
+ );
811
+ const ack = {
812
+ id: message.id || "",
813
+ type: "register_ack" /* REGISTER_ACK */,
814
+ success: true,
815
+ data: {
816
+ connectionId: managedWs.connectionId,
817
+ pageUrl
818
+ }
819
+ };
820
+ ws.send(JSON.stringify(ack));
821
+ }
822
+ /**
823
+ * 处理消息
824
+ */
825
+ handleMessage(ws, message) {
826
+ this.connectionManager.updateLastActive(ws);
827
+ switch (message.type) {
828
+ case "ping" /* PING */:
829
+ this.handlePing(ws, message);
830
+ break;
831
+ case "response" /* RESPONSE */:
832
+ case "error" /* ERROR */:
833
+ this.requestHandler.handleResponse(message);
834
+ break;
835
+ default:
836
+ if (ws.connectionInfo.type === "consumer" /* CONSUMER */) {
837
+ this.requestHandler.handleRequest(ws, message);
838
+ }
839
+ break;
840
+ }
841
+ }
842
+ /**
843
+ * 处理心跳
844
+ */
845
+ handlePing(ws, message) {
846
+ const pong = {
847
+ type: "pong" /* PONG */,
848
+ timestamp: message.timestamp || Date.now()
849
+ };
850
+ ws.send(JSON.stringify(pong));
851
+ }
852
+ /**
853
+ * 停止服务器
854
+ */
855
+ async stop() {
856
+ if (!this.isRunning || !this.wss) {
857
+ return;
858
+ }
859
+ this.logger.info("\u6B63\u5728\u505C\u6B62 Server...");
860
+ this.requestHandler.cleanupAll();
861
+ this.connectionManager.closeAll();
862
+ return new Promise((resolve2) => {
863
+ this.wss.close(() => {
864
+ this.isRunning = false;
865
+ this.wss = null;
866
+ this.logger.info("Server \u5DF2\u505C\u6B62");
867
+ resolve2();
868
+ });
869
+ });
870
+ }
871
+ /**
872
+ * 获取运行状态
873
+ */
874
+ getStatus() {
875
+ return {
876
+ running: this.isRunning,
877
+ port: this.port,
878
+ stats: this.connectionManager.getStats(),
879
+ connectedPages: this.connectionManager.getConnectedPageUrls()
880
+ };
881
+ }
882
+ /**
883
+ * 获取端口
884
+ */
885
+ getPort() {
886
+ return this.port;
887
+ }
888
+ /**
889
+ * 是否运行中
890
+ */
891
+ isServerRunning() {
892
+ return this.isRunning;
893
+ }
894
+ };
895
+ function createServer(options) {
896
+ return new MGServer(options);
897
+ }
898
+ export {
899
+ CONFIG_DIR,
900
+ ConnectionManager,
901
+ ConnectionType,
902
+ DEFAULT_PORT,
903
+ ErrorCode,
904
+ ErrorMessages,
905
+ ErrorNames,
906
+ HEARTBEAT_INTERVAL,
907
+ HEARTBEAT_TIMEOUT,
908
+ LOG_DIR,
909
+ LogLevel,
910
+ Logger,
911
+ MAX_PORT_ATTEMPTS,
912
+ MAX_RETRY_COUNT,
913
+ MGError,
914
+ MGServer,
915
+ MessageType,
916
+ PORT_RANGE_END,
917
+ PORT_RANGE_START,
918
+ PORT_SCAN_TIMEOUT,
919
+ REQUEST_TIMEOUT,
920
+ RETRY_INTERVALS,
921
+ RequestHandler,
922
+ SERVER_INFO_FILE,
923
+ SERVER_LOG_FILE,
924
+ SERVER_START_TIMEOUT,
925
+ createError,
926
+ createLogger,
927
+ createServer,
928
+ deleteServerInfo,
929
+ ensureConfigDir,
930
+ ensureDir,
931
+ ensureOutputDir,
932
+ extractFileId,
933
+ extractFileIdFromMgpLink,
934
+ extractFileIdFromUrl,
935
+ formatDuration,
936
+ formatFileSize,
937
+ formatLogTime,
938
+ generateId,
939
+ generateMgpLink,
940
+ getCurrentISOTime,
941
+ isDesignPageUrl,
942
+ isProcessRunning,
943
+ killProcess,
944
+ normalizePageUrl,
945
+ parseMgpLink,
946
+ readServerInfo,
947
+ resolveOutputPath,
948
+ writeServerInfo
949
+ };
950
+ //# sourceMappingURL=index.js.map