@hangox/mg-cli 1.0.7 → 1.0.9
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/bin/mg-cli.js +0 -0
- package/dist/cli.js +518 -35
- package/dist/cli.js.map +1 -1
- package/dist/daemon-runner.js +267 -8
- package/dist/daemon-runner.js.map +1 -1
- package/dist/{index-DUQN5gSR.d.ts → index-cEKly9mt.d.ts} +98 -6
- package/dist/index.d.ts +53 -4
- package/dist/index.js +527 -8
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -1
- package/dist/server.js +267 -8
- package/dist/server.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -26,6 +26,7 @@ var MAX_RETRY_COUNT = 3;
|
|
|
26
26
|
var ConnectionType = /* @__PURE__ */ ((ConnectionType2) => {
|
|
27
27
|
ConnectionType2["CONSUMER"] = "consumer";
|
|
28
28
|
ConnectionType2["PROVIDER"] = "provider";
|
|
29
|
+
ConnectionType2["CHROME_EXTENSION"] = "chrome_extension";
|
|
29
30
|
return ConnectionType2;
|
|
30
31
|
})(ConnectionType || {});
|
|
31
32
|
var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
|
@@ -40,6 +41,9 @@ var MessageType = /* @__PURE__ */ ((MessageType2) => {
|
|
|
40
41
|
MessageType2["EXPORT_IMAGE"] = "export_image";
|
|
41
42
|
MessageType2["EXECUTE_CODE"] = "execute_code";
|
|
42
43
|
MessageType2["GET_ALL_PAGES"] = "get_all_pages";
|
|
44
|
+
MessageType2["LIST_EXTENSIONS"] = "list_extensions";
|
|
45
|
+
MessageType2["OPEN_PAGE"] = "open_page";
|
|
46
|
+
MessageType2["NAVIGATE_TO_NODE"] = "navigate_to_node";
|
|
43
47
|
MessageType2["GET_SERVER_STATUS"] = "get_server_status";
|
|
44
48
|
MessageType2["RESPONSE"] = "response";
|
|
45
49
|
MessageType2["ERROR"] = "error";
|
|
@@ -65,6 +69,7 @@ var ErrorCode = /* @__PURE__ */ ((ErrorCode2) => {
|
|
|
65
69
|
ErrorCode2["SERVER_START_FAILED"] = "E015";
|
|
66
70
|
ErrorCode2["SERVER_ALREADY_RUNNING"] = "E016";
|
|
67
71
|
ErrorCode2["CONNECTION_LOST"] = "E017";
|
|
72
|
+
ErrorCode2["NO_EXTENSION_CONNECTED"] = "E018";
|
|
68
73
|
ErrorCode2["UNKNOWN_ERROR"] = "E099";
|
|
69
74
|
return ErrorCode2;
|
|
70
75
|
})(ErrorCode || {});
|
|
@@ -86,6 +91,7 @@ var ErrorNames = {
|
|
|
86
91
|
["E015" /* SERVER_START_FAILED */]: "SERVER_START_FAILED",
|
|
87
92
|
["E016" /* SERVER_ALREADY_RUNNING */]: "SERVER_ALREADY_RUNNING",
|
|
88
93
|
["E017" /* CONNECTION_LOST */]: "CONNECTION_LOST",
|
|
94
|
+
["E018" /* NO_EXTENSION_CONNECTED */]: "NO_EXTENSION_CONNECTED",
|
|
89
95
|
["E099" /* UNKNOWN_ERROR */]: "UNKNOWN_ERROR"
|
|
90
96
|
};
|
|
91
97
|
var ErrorMessages = {
|
|
@@ -106,6 +112,7 @@ var ErrorMessages = {
|
|
|
106
112
|
["E015" /* SERVER_START_FAILED */]: "\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25",
|
|
107
113
|
["E016" /* SERVER_ALREADY_RUNNING */]: "Server \u5DF2\u5728\u8FD0\u884C\u4E2D",
|
|
108
114
|
["E017" /* CONNECTION_LOST */]: "\u8FDE\u63A5\u65AD\u5F00",
|
|
115
|
+
["E018" /* NO_EXTENSION_CONNECTED */]: "\u6CA1\u6709 Chrome \u6269\u5C55\u8FDE\u63A5\u5230 Server\u3002\u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u6253\u5F00\u5E76\u5B89\u88C5\u4E86 MG Plugin",
|
|
109
116
|
["E099" /* UNKNOWN_ERROR */]: "\u672A\u77E5\u9519\u8BEF"
|
|
110
117
|
};
|
|
111
118
|
var MGError = class extends Error {
|
|
@@ -489,6 +496,12 @@ function extractSpaceInfo(node) {
|
|
|
489
496
|
width: roundToOneDecimal(typeof node.width === "number" ? node.width : 0),
|
|
490
497
|
height: roundToOneDecimal(typeof node.height === "number" ? node.height : 0)
|
|
491
498
|
};
|
|
499
|
+
if (typeof node.absoluteX === "number") {
|
|
500
|
+
result.absoluteX = roundToOneDecimal(node.absoluteX);
|
|
501
|
+
}
|
|
502
|
+
if (typeof node.absoluteY === "number") {
|
|
503
|
+
result.absoluteY = roundToOneDecimal(node.absoluteY);
|
|
504
|
+
}
|
|
492
505
|
if (node.children && Array.isArray(node.children) && node.children.length > 0) {
|
|
493
506
|
result.children = node.children.map((child) => extractSpaceInfo(child));
|
|
494
507
|
}
|
|
@@ -584,6 +597,12 @@ var ConnectionManager = class {
|
|
|
584
597
|
providers = /* @__PURE__ */ new Map();
|
|
585
598
|
/** Consumer 连接 */
|
|
586
599
|
consumers = /* @__PURE__ */ new Map();
|
|
600
|
+
/** Chrome 扩展连接(按扩展 ID 索引) */
|
|
601
|
+
chromeExtensions = /* @__PURE__ */ new Map();
|
|
602
|
+
/** 扩展序号映射(扩展 ID → 序号) */
|
|
603
|
+
extensionIndexMap = /* @__PURE__ */ new Map();
|
|
604
|
+
/** 下一个可用的扩展序号 */
|
|
605
|
+
nextExtensionIndex = 1;
|
|
587
606
|
/** 所有连接(按 ID 索引) */
|
|
588
607
|
allConnections = /* @__PURE__ */ new Map();
|
|
589
608
|
/** 心跳检查定时器 */
|
|
@@ -678,6 +697,16 @@ var ConnectionManager = class {
|
|
|
678
697
|
} else if (connectionInfo.type === "consumer" /* CONSUMER */) {
|
|
679
698
|
this.consumers.delete(connectionId);
|
|
680
699
|
this.logger.info(`Consumer \u65AD\u5F00: ${connectionId}`);
|
|
700
|
+
} else if (connectionInfo.type === "chrome_extension" /* CHROME_EXTENSION */) {
|
|
701
|
+
for (const [extensionId, extWs] of this.chromeExtensions) {
|
|
702
|
+
if (extWs.connectionId === connectionId) {
|
|
703
|
+
const index = this.extensionIndexMap.get(extensionId);
|
|
704
|
+
this.chromeExtensions.delete(extensionId);
|
|
705
|
+
this.extensionIndexMap.delete(extensionId);
|
|
706
|
+
this.logger.info(`Chrome \u6269\u5C55\u65AD\u5F00: ${extensionId} (\u5E8F\u53F7: #${index})`);
|
|
707
|
+
break;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
681
710
|
}
|
|
682
711
|
}
|
|
683
712
|
/**
|
|
@@ -731,6 +760,7 @@ var ConnectionManager = class {
|
|
|
731
760
|
return {
|
|
732
761
|
providers: providerCount,
|
|
733
762
|
consumers: this.consumers.size,
|
|
763
|
+
extensions: this.chromeExtensions.size,
|
|
734
764
|
total: this.allConnections.size
|
|
735
765
|
};
|
|
736
766
|
}
|
|
@@ -740,6 +770,48 @@ var ConnectionManager = class {
|
|
|
740
770
|
getConnectedPageUrls() {
|
|
741
771
|
return Array.from(this.providers.keys());
|
|
742
772
|
}
|
|
773
|
+
/**
|
|
774
|
+
* 添加 Chrome 扩展连接
|
|
775
|
+
*/
|
|
776
|
+
addChromeExtension(ws, extensionId) {
|
|
777
|
+
const managedWs = this.addConnection(ws, "chrome_extension" /* CHROME_EXTENSION */);
|
|
778
|
+
const index = this.nextExtensionIndex++;
|
|
779
|
+
this.extensionIndexMap.set(extensionId, index);
|
|
780
|
+
this.chromeExtensions.set(extensionId, managedWs);
|
|
781
|
+
this.logger.info(`Chrome \u6269\u5C55\u8FDE\u63A5: ${extensionId} (\u5206\u914D\u5E8F\u53F7: #${index})`);
|
|
782
|
+
return managedWs;
|
|
783
|
+
}
|
|
784
|
+
/**
|
|
785
|
+
* 根据序号查找扩展
|
|
786
|
+
*/
|
|
787
|
+
findExtensionByIndex(index) {
|
|
788
|
+
for (const [extensionId, extIndex] of this.extensionIndexMap) {
|
|
789
|
+
if (extIndex === index) {
|
|
790
|
+
return this.chromeExtensions.get(extensionId);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
return void 0;
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* 获取第一个扩展
|
|
797
|
+
*/
|
|
798
|
+
getFirstExtension() {
|
|
799
|
+
return this.chromeExtensions.values().next().value;
|
|
800
|
+
}
|
|
801
|
+
/**
|
|
802
|
+
* 获取所有扩展信息
|
|
803
|
+
*/
|
|
804
|
+
getAllExtensions() {
|
|
805
|
+
const result = [];
|
|
806
|
+
for (const [extensionId, ws] of this.chromeExtensions) {
|
|
807
|
+
const index = this.extensionIndexMap.get(extensionId);
|
|
808
|
+
if (index !== void 0) {
|
|
809
|
+
result.push({ index, extensionId, ws });
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
result.sort((a, b) => a.index - b.index);
|
|
813
|
+
return result;
|
|
814
|
+
}
|
|
743
815
|
/**
|
|
744
816
|
* 关闭所有连接
|
|
745
817
|
*/
|
|
@@ -751,6 +823,8 @@ var ConnectionManager = class {
|
|
|
751
823
|
}
|
|
752
824
|
this.providers.clear();
|
|
753
825
|
this.consumers.clear();
|
|
826
|
+
this.chromeExtensions.clear();
|
|
827
|
+
this.extensionIndexMap.clear();
|
|
754
828
|
this.allConnections.clear();
|
|
755
829
|
}
|
|
756
830
|
};
|
|
@@ -868,6 +942,24 @@ var RequestHandler = class {
|
|
|
868
942
|
this.logger.error(`\u53D1\u9001\u9519\u8BEF\u54CD\u5E94\u5931\u8D25: ${requestId}`, err);
|
|
869
943
|
}
|
|
870
944
|
}
|
|
945
|
+
/**
|
|
946
|
+
* 注册待处理请求(供外部使用,如 OPEN_PAGE)
|
|
947
|
+
* @param requestId 请求 ID
|
|
948
|
+
* @param consumer 发起请求的 Consumer
|
|
949
|
+
* @param sendCallback 发送请求的回调函数
|
|
950
|
+
*/
|
|
951
|
+
registerPendingRequest(requestId, consumer, sendCallback) {
|
|
952
|
+
const timer = setTimeout(() => {
|
|
953
|
+
this.handleTimeout(requestId);
|
|
954
|
+
}, REQUEST_TIMEOUT);
|
|
955
|
+
this.pendingRequests.set(requestId, {
|
|
956
|
+
id: requestId,
|
|
957
|
+
consumer,
|
|
958
|
+
timer,
|
|
959
|
+
timestamp: Date.now()
|
|
960
|
+
});
|
|
961
|
+
sendCallback();
|
|
962
|
+
}
|
|
871
963
|
/**
|
|
872
964
|
* 清理特定连接的所有待处理请求
|
|
873
965
|
*/
|
|
@@ -922,6 +1014,13 @@ function getVersion() {
|
|
|
922
1014
|
return cachedVersion;
|
|
923
1015
|
}
|
|
924
1016
|
}
|
|
1017
|
+
var DEV_VERSION = "9.9.9";
|
|
1018
|
+
function isVersionMatch(version1, version2) {
|
|
1019
|
+
if (version1 === DEV_VERSION || version2 === DEV_VERSION) {
|
|
1020
|
+
return true;
|
|
1021
|
+
}
|
|
1022
|
+
return version1 === version2;
|
|
1023
|
+
}
|
|
925
1024
|
|
|
926
1025
|
// src/server/websocket-server.ts
|
|
927
1026
|
var MGServer = class {
|
|
@@ -1040,12 +1139,22 @@ var MGServer = class {
|
|
|
1040
1139
|
*/
|
|
1041
1140
|
handleRegister(ws, message) {
|
|
1042
1141
|
const { connectionType, pageUrl, pageId } = message.data;
|
|
1043
|
-
const
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1142
|
+
const extensionId = message.data.extensionId;
|
|
1143
|
+
let managedWs;
|
|
1144
|
+
let extensionIndex;
|
|
1145
|
+
if (connectionType === "chrome_extension" /* CHROME_EXTENSION */ && extensionId) {
|
|
1146
|
+
managedWs = this.connectionManager.addChromeExtension(ws, extensionId);
|
|
1147
|
+
const exts = this.connectionManager.getAllExtensions();
|
|
1148
|
+
const ext = exts.find((e) => e.extensionId === extensionId);
|
|
1149
|
+
extensionIndex = ext?.index;
|
|
1150
|
+
} else {
|
|
1151
|
+
managedWs = this.connectionManager.addConnection(
|
|
1152
|
+
ws,
|
|
1153
|
+
connectionType,
|
|
1154
|
+
pageUrl,
|
|
1155
|
+
pageId
|
|
1156
|
+
);
|
|
1157
|
+
}
|
|
1049
1158
|
const ack = {
|
|
1050
1159
|
id: message.id || "",
|
|
1051
1160
|
type: "register_ack" /* REGISTER_ACK */,
|
|
@@ -1053,7 +1162,8 @@ var MGServer = class {
|
|
|
1053
1162
|
data: {
|
|
1054
1163
|
connectionId: managedWs.connectionId,
|
|
1055
1164
|
pageUrl,
|
|
1056
|
-
serverVersion: getVersion()
|
|
1165
|
+
serverVersion: getVersion(),
|
|
1166
|
+
...extensionIndex !== void 0 && { extensionIndex }
|
|
1057
1167
|
}
|
|
1058
1168
|
};
|
|
1059
1169
|
ws.send(JSON.stringify(ack));
|
|
@@ -1070,6 +1180,15 @@ var MGServer = class {
|
|
|
1070
1180
|
case "get_server_status" /* GET_SERVER_STATUS */:
|
|
1071
1181
|
this.handleGetServerStatus(ws, message);
|
|
1072
1182
|
break;
|
|
1183
|
+
case "list_extensions" /* LIST_EXTENSIONS */:
|
|
1184
|
+
this.handleListExtensions(ws, message);
|
|
1185
|
+
break;
|
|
1186
|
+
case "open_page" /* OPEN_PAGE */:
|
|
1187
|
+
this.handleOpenPage(ws, message);
|
|
1188
|
+
break;
|
|
1189
|
+
case "navigate_to_node" /* NAVIGATE_TO_NODE */:
|
|
1190
|
+
this.handleNavigateToNode(ws, message);
|
|
1191
|
+
break;
|
|
1073
1192
|
case "response" /* RESPONSE */:
|
|
1074
1193
|
case "error" /* ERROR */:
|
|
1075
1194
|
this.requestHandler.handleResponse(message);
|
|
@@ -1102,6 +1221,13 @@ var MGServer = class {
|
|
|
1102
1221
|
connectedAt: info.connectedAt.toISOString(),
|
|
1103
1222
|
lastActiveAt: info.lastActiveAt.toISOString()
|
|
1104
1223
|
}));
|
|
1224
|
+
const extensions = this.connectionManager.getAllExtensions();
|
|
1225
|
+
const connectedExtensions = extensions.map((ext) => ({
|
|
1226
|
+
index: ext.index,
|
|
1227
|
+
extensionId: ext.extensionId,
|
|
1228
|
+
connectedAt: ext.ws.connectionInfo.connectedAt.toISOString(),
|
|
1229
|
+
lastActiveAt: ext.ws.connectionInfo.lastActiveAt.toISOString()
|
|
1230
|
+
}));
|
|
1105
1231
|
const uptimeMs = this.startedAt ? Date.now() - this.startedAt.getTime() : 0;
|
|
1106
1232
|
const statusData = {
|
|
1107
1233
|
running: this.isRunning,
|
|
@@ -1111,7 +1237,8 @@ var MGServer = class {
|
|
|
1111
1237
|
uptime: formatDuration(uptimeMs),
|
|
1112
1238
|
version: getVersion(),
|
|
1113
1239
|
stats,
|
|
1114
|
-
connectedPages
|
|
1240
|
+
connectedPages,
|
|
1241
|
+
connectedExtensions
|
|
1115
1242
|
};
|
|
1116
1243
|
const response = {
|
|
1117
1244
|
id: message.id || "",
|
|
@@ -1122,6 +1249,156 @@ var MGServer = class {
|
|
|
1122
1249
|
ws.send(JSON.stringify(response));
|
|
1123
1250
|
this.logger.info("\u8FD4\u56DE Server \u72B6\u6001\u4FE1\u606F");
|
|
1124
1251
|
}
|
|
1252
|
+
/**
|
|
1253
|
+
* 处理扩展列表查询
|
|
1254
|
+
*/
|
|
1255
|
+
handleListExtensions(ws, message) {
|
|
1256
|
+
const extensions = this.connectionManager.getAllExtensions();
|
|
1257
|
+
const extensionList = extensions.map((ext) => ({
|
|
1258
|
+
index: ext.index,
|
|
1259
|
+
extensionId: ext.extensionId,
|
|
1260
|
+
connectedAt: ext.ws.connectionInfo.connectedAt.toISOString(),
|
|
1261
|
+
lastActiveAt: ext.ws.connectionInfo.lastActiveAt.toISOString()
|
|
1262
|
+
}));
|
|
1263
|
+
const response = {
|
|
1264
|
+
id: message.id || "",
|
|
1265
|
+
type: "response" /* RESPONSE */,
|
|
1266
|
+
success: true,
|
|
1267
|
+
data: {
|
|
1268
|
+
extensions: extensionList,
|
|
1269
|
+
totalCount: extensionList.length
|
|
1270
|
+
}
|
|
1271
|
+
};
|
|
1272
|
+
ws.send(JSON.stringify(response));
|
|
1273
|
+
this.logger.info(`\u8FD4\u56DE\u6269\u5C55\u5217\u8868\uFF0C\u5171 ${extensionList.length} \u4E2A\u6269\u5C55`);
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* 处理打开页面请求
|
|
1277
|
+
*/
|
|
1278
|
+
handleOpenPage(ws, message) {
|
|
1279
|
+
const params = message.params;
|
|
1280
|
+
const requestId = message.id || "";
|
|
1281
|
+
if (!params?.url) {
|
|
1282
|
+
const errorResponse = {
|
|
1283
|
+
id: requestId,
|
|
1284
|
+
type: "error" /* ERROR */,
|
|
1285
|
+
success: false,
|
|
1286
|
+
error: {
|
|
1287
|
+
code: "E011" /* INVALID_PARAMS */,
|
|
1288
|
+
name: "INVALID_PARAMS",
|
|
1289
|
+
message: "\u7F3A\u5C11 url \u53C2\u6570"
|
|
1290
|
+
}
|
|
1291
|
+
};
|
|
1292
|
+
ws.send(JSON.stringify(errorResponse));
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
const targetIndex = params.extensionIndex || 1;
|
|
1296
|
+
const targetExtension = this.connectionManager.findExtensionByIndex(targetIndex);
|
|
1297
|
+
if (!targetExtension) {
|
|
1298
|
+
const errorResponse = {
|
|
1299
|
+
id: requestId,
|
|
1300
|
+
type: "error" /* ERROR */,
|
|
1301
|
+
success: false,
|
|
1302
|
+
error: {
|
|
1303
|
+
code: "E018" /* NO_EXTENSION_CONNECTED */,
|
|
1304
|
+
name: "NO_EXTENSION_CONNECTED",
|
|
1305
|
+
message: targetIndex === 1 ? "\u6CA1\u6709 Chrome \u6269\u5C55\u8FDE\u63A5\u5230 Server\u3002\u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u6253\u5F00\u5E76\u5B89\u88C5\u4E86 MG Plugin" : `\u672A\u627E\u5230\u5E8F\u53F7\u4E3A #${targetIndex} \u7684\u6269\u5C55\u5B9E\u4F8B`
|
|
1306
|
+
}
|
|
1307
|
+
};
|
|
1308
|
+
ws.send(JSON.stringify(errorResponse));
|
|
1309
|
+
this.logger.warn(`\u672A\u627E\u5230\u6269\u5C55\u5B9E\u4F8B #${targetIndex}`);
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const forwardMessage = {
|
|
1313
|
+
id: requestId,
|
|
1314
|
+
type: "open_page" /* OPEN_PAGE */,
|
|
1315
|
+
params: { url: params.url }
|
|
1316
|
+
};
|
|
1317
|
+
this.requestHandler.registerPendingRequest(requestId, ws, () => {
|
|
1318
|
+
try {
|
|
1319
|
+
targetExtension.send(JSON.stringify(forwardMessage));
|
|
1320
|
+
this.logger.info(`\u8F6C\u53D1 OPEN_PAGE \u8BF7\u6C42\u5230\u6269\u5C55 #${targetIndex}: ${params.url}`);
|
|
1321
|
+
} catch (error) {
|
|
1322
|
+
this.logger.error(`\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25: ${error}`);
|
|
1323
|
+
const errorResponse = {
|
|
1324
|
+
id: requestId,
|
|
1325
|
+
type: "error" /* ERROR */,
|
|
1326
|
+
success: false,
|
|
1327
|
+
error: {
|
|
1328
|
+
code: "E001" /* CONNECTION_FAILED */,
|
|
1329
|
+
name: "CONNECTION_FAILED",
|
|
1330
|
+
message: "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25"
|
|
1331
|
+
}
|
|
1332
|
+
};
|
|
1333
|
+
ws.send(JSON.stringify(errorResponse));
|
|
1334
|
+
}
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* 处理节点导航请求
|
|
1339
|
+
*/
|
|
1340
|
+
handleNavigateToNode(ws, message) {
|
|
1341
|
+
const params = message.params;
|
|
1342
|
+
const requestId = message.id || "";
|
|
1343
|
+
if (!params?.pageUrl || !params?.nodeId) {
|
|
1344
|
+
const errorResponse = {
|
|
1345
|
+
id: requestId,
|
|
1346
|
+
type: "error" /* ERROR */,
|
|
1347
|
+
success: false,
|
|
1348
|
+
error: {
|
|
1349
|
+
code: "E011" /* INVALID_PARAMS */,
|
|
1350
|
+
name: "INVALID_PARAMS",
|
|
1351
|
+
message: "\u7F3A\u5C11 pageUrl \u6216 nodeId \u53C2\u6570"
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
ws.send(JSON.stringify(errorResponse));
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
const targetIndex = params.extensionIndex || 1;
|
|
1358
|
+
const targetExtension = this.connectionManager.findExtensionByIndex(targetIndex);
|
|
1359
|
+
if (!targetExtension) {
|
|
1360
|
+
const errorResponse = {
|
|
1361
|
+
id: requestId,
|
|
1362
|
+
type: "error" /* ERROR */,
|
|
1363
|
+
success: false,
|
|
1364
|
+
error: {
|
|
1365
|
+
code: "E018" /* NO_EXTENSION_CONNECTED */,
|
|
1366
|
+
name: "NO_EXTENSION_CONNECTED",
|
|
1367
|
+
message: targetIndex === 1 ? "\u6CA1\u6709 Chrome \u6269\u5C55\u8FDE\u63A5\u5230 Server\u3002\u8BF7\u786E\u4FDD\u6D4F\u89C8\u5668\u5DF2\u6253\u5F00\u5E76\u5B89\u88C5\u4E86 MG Plugin" : `\u672A\u627E\u5230\u5E8F\u53F7\u4E3A #${targetIndex} \u7684\u6269\u5C55\u5B9E\u4F8B`
|
|
1368
|
+
}
|
|
1369
|
+
};
|
|
1370
|
+
ws.send(JSON.stringify(errorResponse));
|
|
1371
|
+
this.logger.warn(`\u672A\u627E\u5230\u6269\u5C55\u5B9E\u4F8B #${targetIndex}`);
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
const forwardMessage = {
|
|
1375
|
+
id: requestId,
|
|
1376
|
+
type: "navigate_to_node" /* NAVIGATE_TO_NODE */,
|
|
1377
|
+
params: {
|
|
1378
|
+
pageUrl: params.pageUrl,
|
|
1379
|
+
nodeId: params.nodeId
|
|
1380
|
+
}
|
|
1381
|
+
};
|
|
1382
|
+
this.requestHandler.registerPendingRequest(requestId, ws, () => {
|
|
1383
|
+
try {
|
|
1384
|
+
targetExtension.send(JSON.stringify(forwardMessage));
|
|
1385
|
+
this.logger.info(`\u8F6C\u53D1 NAVIGATE_TO_NODE \u8BF7\u6C42\u5230\u6269\u5C55 #${targetIndex}: ${params.pageUrl} -> ${params.nodeId}`);
|
|
1386
|
+
} catch (error) {
|
|
1387
|
+
this.logger.error(`\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25: ${error}`);
|
|
1388
|
+
const errorResponse = {
|
|
1389
|
+
id: requestId,
|
|
1390
|
+
type: "error" /* ERROR */,
|
|
1391
|
+
success: false,
|
|
1392
|
+
error: {
|
|
1393
|
+
code: "E001" /* CONNECTION_FAILED */,
|
|
1394
|
+
name: "CONNECTION_FAILED",
|
|
1395
|
+
message: "\u8F6C\u53D1\u8BF7\u6C42\u5931\u8D25"
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
ws.send(JSON.stringify(errorResponse));
|
|
1399
|
+
}
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1125
1402
|
/**
|
|
1126
1403
|
* 停止服务器
|
|
1127
1404
|
*/
|
|
@@ -1168,6 +1445,247 @@ var MGServer = class {
|
|
|
1168
1445
|
function createServer(options) {
|
|
1169
1446
|
return new MGServer(options);
|
|
1170
1447
|
}
|
|
1448
|
+
|
|
1449
|
+
// src/cli/client.ts
|
|
1450
|
+
import WebSocket2 from "ws";
|
|
1451
|
+
|
|
1452
|
+
// src/server/daemon.ts
|
|
1453
|
+
import { spawn } from "child_process";
|
|
1454
|
+
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
1455
|
+
import { dirname as dirname4, join as join3 } from "path";
|
|
1456
|
+
function isServerRunning() {
|
|
1457
|
+
const info = readServerInfo();
|
|
1458
|
+
if (!info) {
|
|
1459
|
+
return { running: false, info: null };
|
|
1460
|
+
}
|
|
1461
|
+
if (!isProcessRunning(info.pid)) {
|
|
1462
|
+
deleteServerInfo();
|
|
1463
|
+
return { running: false, info: null };
|
|
1464
|
+
}
|
|
1465
|
+
return { running: true, info };
|
|
1466
|
+
}
|
|
1467
|
+
async function startServerDaemon(port) {
|
|
1468
|
+
const { running, info } = isServerRunning();
|
|
1469
|
+
if (running && info) {
|
|
1470
|
+
throw new MGError(
|
|
1471
|
+
"E016" /* SERVER_ALREADY_RUNNING */,
|
|
1472
|
+
`Server \u5DF2\u5728\u8FD0\u884C\u4E2D (PID: ${info.pid}, \u7AEF\u53E3: ${info.port})`
|
|
1473
|
+
);
|
|
1474
|
+
}
|
|
1475
|
+
ensureConfigDir();
|
|
1476
|
+
const currentFile = fileURLToPath2(import.meta.url);
|
|
1477
|
+
const currentDir = dirname4(currentFile);
|
|
1478
|
+
const serverScript = join3(currentDir, "daemon-runner.js");
|
|
1479
|
+
const args = ["--foreground"];
|
|
1480
|
+
if (port) {
|
|
1481
|
+
args.push("--port", String(port));
|
|
1482
|
+
}
|
|
1483
|
+
const child = spawn(process.execPath, [serverScript, ...args], {
|
|
1484
|
+
detached: true,
|
|
1485
|
+
stdio: "ignore",
|
|
1486
|
+
env: {
|
|
1487
|
+
...process.env,
|
|
1488
|
+
MG_DAEMON: "1"
|
|
1489
|
+
}
|
|
1490
|
+
});
|
|
1491
|
+
child.unref();
|
|
1492
|
+
const startTime = Date.now();
|
|
1493
|
+
while (Date.now() - startTime < SERVER_START_TIMEOUT) {
|
|
1494
|
+
await new Promise((resolve2) => setTimeout(resolve2, 200));
|
|
1495
|
+
const { running: running2, info: info2 } = isServerRunning();
|
|
1496
|
+
if (running2 && info2) {
|
|
1497
|
+
return info2;
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
throw new MGError("E015" /* SERVER_START_FAILED */, "Server \u542F\u52A8\u8D85\u65F6");
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
// src/cli/client.ts
|
|
1504
|
+
var MGClient = class {
|
|
1505
|
+
ws = null;
|
|
1506
|
+
options;
|
|
1507
|
+
constructor(options = {}) {
|
|
1508
|
+
this.options = options;
|
|
1509
|
+
}
|
|
1510
|
+
/**
|
|
1511
|
+
* 连接到 Server
|
|
1512
|
+
*/
|
|
1513
|
+
async connect() {
|
|
1514
|
+
const serverInfo = readServerInfo();
|
|
1515
|
+
if (serverInfo) {
|
|
1516
|
+
if (isProcessRunning(serverInfo.pid)) {
|
|
1517
|
+
const currentVersion = getVersion();
|
|
1518
|
+
if (!isVersionMatch(currentVersion, serverInfo.version)) {
|
|
1519
|
+
console.warn(`\u26A0\uFE0F \u7248\u672C\u4E0D\u5339\u914D: CLI ${currentVersion} vs Server ${serverInfo.version}`);
|
|
1520
|
+
console.warn("\u63D0\u793A: \u5982\u9700\u5BF9\u9F50\u7248\u672C\uFF0C\u8BF7\u624B\u52A8\u8FD0\u884C `npx -y @hangox/mg-cli@latest server restart`");
|
|
1521
|
+
}
|
|
1522
|
+
try {
|
|
1523
|
+
await this.tryConnect(serverInfo.port);
|
|
1524
|
+
return;
|
|
1525
|
+
} catch {
|
|
1526
|
+
}
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
|
|
1530
|
+
try {
|
|
1531
|
+
await this.tryConnect(port);
|
|
1532
|
+
return;
|
|
1533
|
+
} catch {
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
if (!this.options.noAutoStart) {
|
|
1537
|
+
console.log("Server \u672A\u8FD0\u884C\uFF0C\u6B63\u5728\u81EA\u52A8\u542F\u52A8...");
|
|
1538
|
+
try {
|
|
1539
|
+
const info = await startServerDaemon();
|
|
1540
|
+
console.log(`Server \u5DF2\u542F\u52A8\uFF0C\u7AEF\u53E3: ${info.port}`);
|
|
1541
|
+
await this.waitForServer(info.port);
|
|
1542
|
+
return;
|
|
1543
|
+
} catch (error) {
|
|
1544
|
+
throw new MGError(
|
|
1545
|
+
"E015" /* SERVER_START_FAILED */,
|
|
1546
|
+
`\u81EA\u52A8\u542F\u52A8 Server \u5931\u8D25: ${error instanceof Error ? error.message : error}`
|
|
1547
|
+
);
|
|
1548
|
+
}
|
|
1549
|
+
}
|
|
1550
|
+
throw new MGError("E001" /* CONNECTION_FAILED */, ErrorMessages["E001" /* CONNECTION_FAILED */]);
|
|
1551
|
+
}
|
|
1552
|
+
/**
|
|
1553
|
+
* 尝试连接指定端口
|
|
1554
|
+
*/
|
|
1555
|
+
tryConnect(port) {
|
|
1556
|
+
return new Promise((resolve2, reject) => {
|
|
1557
|
+
const ws = new WebSocket2(`ws://localhost:${port}`);
|
|
1558
|
+
const timer = setTimeout(() => {
|
|
1559
|
+
ws.close();
|
|
1560
|
+
reject(new Error("\u8FDE\u63A5\u8D85\u65F6"));
|
|
1561
|
+
}, PORT_SCAN_TIMEOUT);
|
|
1562
|
+
ws.on("open", () => {
|
|
1563
|
+
clearTimeout(timer);
|
|
1564
|
+
this.ws = ws;
|
|
1565
|
+
this.register();
|
|
1566
|
+
resolve2();
|
|
1567
|
+
});
|
|
1568
|
+
ws.on("error", (error) => {
|
|
1569
|
+
clearTimeout(timer);
|
|
1570
|
+
reject(error);
|
|
1571
|
+
});
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
/**
|
|
1575
|
+
* 等待 Server 就绪
|
|
1576
|
+
*/
|
|
1577
|
+
async waitForServer(port) {
|
|
1578
|
+
const startTime = Date.now();
|
|
1579
|
+
const interval = 500;
|
|
1580
|
+
while (Date.now() - startTime < SERVER_START_TIMEOUT) {
|
|
1581
|
+
try {
|
|
1582
|
+
await this.tryConnect(port);
|
|
1583
|
+
return;
|
|
1584
|
+
} catch {
|
|
1585
|
+
await new Promise((r) => setTimeout(r, interval));
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
throw new Error("\u7B49\u5F85 Server \u542F\u52A8\u8D85\u65F6");
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* 注册为 Consumer
|
|
1592
|
+
*/
|
|
1593
|
+
register() {
|
|
1594
|
+
if (!this.ws) return;
|
|
1595
|
+
const message = {
|
|
1596
|
+
type: "register" /* REGISTER */,
|
|
1597
|
+
data: {
|
|
1598
|
+
connectionType: "consumer" /* CONSUMER */
|
|
1599
|
+
},
|
|
1600
|
+
timestamp: Date.now()
|
|
1601
|
+
};
|
|
1602
|
+
this.ws.send(JSON.stringify(message));
|
|
1603
|
+
}
|
|
1604
|
+
/**
|
|
1605
|
+
* 发送请求并等待响应
|
|
1606
|
+
*/
|
|
1607
|
+
async request(type, params, pageUrl) {
|
|
1608
|
+
if (!this.ws) {
|
|
1609
|
+
throw new MGError("E001" /* CONNECTION_FAILED */, "\u672A\u8FDE\u63A5\u5230 Server");
|
|
1610
|
+
}
|
|
1611
|
+
const requestId = generateId();
|
|
1612
|
+
const message = {
|
|
1613
|
+
id: requestId,
|
|
1614
|
+
type,
|
|
1615
|
+
params,
|
|
1616
|
+
pageUrl,
|
|
1617
|
+
timestamp: Date.now()
|
|
1618
|
+
};
|
|
1619
|
+
return new Promise((resolve2, reject) => {
|
|
1620
|
+
const timer = setTimeout(() => {
|
|
1621
|
+
reject(new MGError("E012" /* REQUEST_TIMEOUT */, ErrorMessages["E012" /* REQUEST_TIMEOUT */]));
|
|
1622
|
+
}, REQUEST_TIMEOUT);
|
|
1623
|
+
const messageHandler = (data) => {
|
|
1624
|
+
try {
|
|
1625
|
+
const response = JSON.parse(data.toString());
|
|
1626
|
+
if (response.id === requestId) {
|
|
1627
|
+
clearTimeout(timer);
|
|
1628
|
+
this.ws?.off("message", messageHandler);
|
|
1629
|
+
if (response.success) {
|
|
1630
|
+
resolve2(response.data);
|
|
1631
|
+
} else {
|
|
1632
|
+
const error = response.error;
|
|
1633
|
+
reject(
|
|
1634
|
+
new MGError(
|
|
1635
|
+
error?.code || "E099" /* UNKNOWN_ERROR */,
|
|
1636
|
+
error?.message || "\u672A\u77E5\u9519\u8BEF"
|
|
1637
|
+
)
|
|
1638
|
+
);
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
} catch {
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
this.ws.on("message", messageHandler);
|
|
1645
|
+
this.ws.send(JSON.stringify(message));
|
|
1646
|
+
});
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* 带重试的请求
|
|
1650
|
+
*/
|
|
1651
|
+
async requestWithRetry(type, params, pageUrl) {
|
|
1652
|
+
if (this.options.noRetry) {
|
|
1653
|
+
return this.request(type, params, pageUrl);
|
|
1654
|
+
}
|
|
1655
|
+
let lastError = null;
|
|
1656
|
+
for (let attempt = 0; attempt <= MAX_RETRY_COUNT; attempt++) {
|
|
1657
|
+
try {
|
|
1658
|
+
return await this.request(type, params, pageUrl);
|
|
1659
|
+
} catch (error) {
|
|
1660
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
1661
|
+
if (error instanceof MGError) {
|
|
1662
|
+
const retryable = ["E017" /* CONNECTION_LOST */, "E012" /* REQUEST_TIMEOUT */];
|
|
1663
|
+
if (!retryable.includes(error.code)) {
|
|
1664
|
+
throw error;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
if (attempt < MAX_RETRY_COUNT) {
|
|
1668
|
+
const delay = RETRY_INTERVALS[attempt] || RETRY_INTERVALS[RETRY_INTERVALS.length - 1];
|
|
1669
|
+
await new Promise((r) => setTimeout(r, delay));
|
|
1670
|
+
try {
|
|
1671
|
+
await this.connect();
|
|
1672
|
+
} catch {
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
throw lastError || new MGError("E099" /* UNKNOWN_ERROR */, "\u8BF7\u6C42\u5931\u8D25");
|
|
1678
|
+
}
|
|
1679
|
+
/**
|
|
1680
|
+
* 关闭连接
|
|
1681
|
+
*/
|
|
1682
|
+
close() {
|
|
1683
|
+
if (this.ws) {
|
|
1684
|
+
this.ws.close();
|
|
1685
|
+
this.ws = null;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
};
|
|
1171
1689
|
export {
|
|
1172
1690
|
CONFIG_DIR,
|
|
1173
1691
|
ConnectionManager,
|
|
@@ -1187,6 +1705,7 @@ export {
|
|
|
1187
1705
|
Logger,
|
|
1188
1706
|
MAX_PORT_ATTEMPTS,
|
|
1189
1707
|
MAX_RETRY_COUNT,
|
|
1708
|
+
MGClient,
|
|
1190
1709
|
MGError,
|
|
1191
1710
|
MGServer,
|
|
1192
1711
|
MessageType,
|