@aiscene/aiserver 1.6.4 → 1.6.6
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/dist/api/callback.d.ts +5 -0
- package/dist/api/callback.d.ts.map +1 -1
- package/dist/api/callback.js +79 -0
- package/dist/api/callback.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +1 -0
- package/dist/config/index.js.map +1 -1
- package/dist/config/schema.d.ts +1 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/core/types.d.ts +7 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/debug/types.d.ts +33 -0
- package/dist/debug/types.d.ts.map +1 -1
- package/dist/debug/websocket-server.d.ts +10 -0
- package/dist/debug/websocket-server.d.ts.map +1 -1
- package/dist/debug/websocket-server.js +332 -1
- package/dist/debug/websocket-server.js.map +1 -1
- package/dist/executor/android-executor.d.ts +20 -0
- package/dist/executor/android-executor.d.ts.map +1 -1
- package/dist/executor/android-executor.js +114 -0
- package/dist/executor/android-executor.js.map +1 -1
- package/dist/proxy/whistle-manager.d.ts +127 -0
- package/dist/proxy/whistle-manager.d.ts.map +1 -0
- package/dist/proxy/whistle-manager.js +374 -0
- package/dist/proxy/whistle-manager.js.map +1 -0
- package/dist/scrcpy/server.d.ts +1 -0
- package/dist/scrcpy/server.d.ts.map +1 -1
- package/dist/scrcpy/server.js +43 -20
- package/dist/scrcpy/server.js.map +1 -1
- package/dist/task/scheduler.d.ts +1 -0
- package/dist/task/scheduler.d.ts.map +1 -1
- package/dist/task/scheduler.js +48 -1
- package/dist/task/scheduler.js.map +1 -1
- package/package.json +1 -1
|
@@ -2,7 +2,9 @@ import { WebSocketServer } from 'ws';
|
|
|
2
2
|
import { fork } from 'child_process';
|
|
3
3
|
import { URL } from 'url';
|
|
4
4
|
import os from 'os';
|
|
5
|
-
import { uploadReportToServer } from '../api/callback.js';
|
|
5
|
+
import { uploadReportToServer, uploadCapturePackageFile } from '../api/callback.js';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import path from 'path';
|
|
6
8
|
import { createLogger } from '../core/logger.js';
|
|
7
9
|
import { eventBus } from '../core/event-bus.js';
|
|
8
10
|
import { sessionManager } from './session-manager.js';
|
|
@@ -13,6 +15,7 @@ import { webBrowserPool } from './web-browser-pool.js';
|
|
|
13
15
|
import { ExecutorFactory } from '../executor/executor-factory.js';
|
|
14
16
|
import { instrumentCode } from '../executor/code-instrument.js';
|
|
15
17
|
import { playwrightSwipe } from '../core/native-swipe.js';
|
|
18
|
+
import { whistleManager } from '../proxy/whistle-manager.js';
|
|
16
19
|
const logger = createLogger('DebugWebSocket');
|
|
17
20
|
/** 获取本机可用的非内部 IPv4 地址,供客户端连接投屏 WebSocket */
|
|
18
21
|
function getLocalIP() {
|
|
@@ -661,6 +664,12 @@ export class DebugWebSocketServer {
|
|
|
661
664
|
executionId: sessionId,
|
|
662
665
|
mobileMode: request.mobileMode,
|
|
663
666
|
deviceName: request.deviceName,
|
|
667
|
+
// 环境代理配置
|
|
668
|
+
environmentId: request.environmentId,
|
|
669
|
+
proxyPort: request.proxyPort || (request.hostMappings && request.hostMappings.length > 0 ? 8899 : undefined),
|
|
670
|
+
hostMappings: request.hostMappings,
|
|
671
|
+
// 公司代理平台账号
|
|
672
|
+
proxyAccount: request.proxyAccount,
|
|
664
673
|
// 添加登录配置
|
|
665
674
|
...(request.loginConfig && {
|
|
666
675
|
loginUrl: request.loginConfig.loginUrl,
|
|
@@ -673,6 +682,39 @@ export class DebugWebSocketServer {
|
|
|
673
682
|
agreementSelector: request.loginConfig.agreementSelector,
|
|
674
683
|
}),
|
|
675
684
|
};
|
|
685
|
+
// Setup proxy rules: 如果有 proxyAccount,使用公司代理平台;否则走本地 Whistle
|
|
686
|
+
const whistleApiBase = config.proxyAccount ? `http://proxy-pc.jd.com/account/${config.proxyAccount}` : undefined;
|
|
687
|
+
if (config.hostMappings && config.hostMappings.length > 0) {
|
|
688
|
+
try {
|
|
689
|
+
const envId = config.environmentId || sessionId;
|
|
690
|
+
const ruleGroupName = whistleManager.generateRuleGroupName(envId);
|
|
691
|
+
const success = await whistleManager.createRuleGroup(ruleGroupName, config.hostMappings, whistleApiBase);
|
|
692
|
+
if (success) {
|
|
693
|
+
const sess = sessionManager.get(sessionId);
|
|
694
|
+
if (sess) {
|
|
695
|
+
sess.whistleRuleGroupName = ruleGroupName;
|
|
696
|
+
sess.whistleApiBase = whistleApiBase;
|
|
697
|
+
}
|
|
698
|
+
if (!config.proxyAccount && !config.proxyPort)
|
|
699
|
+
config.proxyPort = 8899;
|
|
700
|
+
logger.info('[Debug] Whistle proxy setup: ruleGroup=' + ruleGroupName + ', proxyPort=' + config.proxyPort + ', proxyAccount=' + (config.proxyAccount || 'N/A'));
|
|
701
|
+
// 开始请求抓包(传入 proxyAccount,自动选择正确的 Whistle 实例)
|
|
702
|
+
try {
|
|
703
|
+
await whistleManager.startCapture(sessionId, undefined, 3000, config.proxyAccount);
|
|
704
|
+
logger.info('[Debug] Whistle request capture started: sessionId=' + sessionId);
|
|
705
|
+
}
|
|
706
|
+
catch (captureErr) {
|
|
707
|
+
logger.warn('[Debug] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
else {
|
|
711
|
+
logger.warn('[Debug] Whistle proxy setup failed, continuing without proxy');
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
catch (err) {
|
|
715
|
+
logger.warn('[Debug] Whistle proxy setup error: ' + (err instanceof Error ? err.message : String(err)));
|
|
716
|
+
}
|
|
717
|
+
}
|
|
676
718
|
// 使用 AbortSignal 监听停止请求
|
|
677
719
|
const executePromise = executor.execute(config);
|
|
678
720
|
const abortPromise = new Promise((resolve) => {
|
|
@@ -698,6 +740,42 @@ export class DebugWebSocketServer {
|
|
|
698
740
|
// 清理 executor 引用
|
|
699
741
|
if (session)
|
|
700
742
|
session.executor = undefined;
|
|
743
|
+
// 停止请求抓包,采集调试期间的所有请求
|
|
744
|
+
let capturedRequests = [];
|
|
745
|
+
let captureDataUrl;
|
|
746
|
+
try {
|
|
747
|
+
capturedRequests = await whistleManager.stopCapture(sessionId);
|
|
748
|
+
if (capturedRequests.length > 0) {
|
|
749
|
+
logger.info('[Debug] Captured ' + capturedRequests.length + ' requests during debug session');
|
|
750
|
+
// 将抓包数据保存到文件并上传到 OSS
|
|
751
|
+
try {
|
|
752
|
+
const uploadResult = await this.saveAndUploadCaptureData(capturedRequests, sessionId);
|
|
753
|
+
if (uploadResult) {
|
|
754
|
+
captureDataUrl = uploadResult;
|
|
755
|
+
logger.info('[Debug] Capture data uploaded: ' + captureDataUrl);
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
catch (uploadErr) {
|
|
759
|
+
logger.warn('[Debug] Failed to upload capture data: ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
catch (captureErr) {
|
|
764
|
+
logger.warn('[Debug] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
765
|
+
}
|
|
766
|
+
// Teardown Whistle proxy rules if they were set
|
|
767
|
+
const currentSess = sessionManager.get(sessionId);
|
|
768
|
+
const whistleRuleName = currentSess ? currentSess.whistleRuleGroupName : undefined;
|
|
769
|
+
if (whistleRuleName) {
|
|
770
|
+
whistleManager.removeRuleGroup(whistleRuleName, whistleApiBase).catch((err) => {
|
|
771
|
+
logger.warn('[Debug] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
772
|
+
});
|
|
773
|
+
if (currentSess) {
|
|
774
|
+
currentSess.whistleRuleGroupName = undefined;
|
|
775
|
+
currentSess.whistleApiBase = undefined;
|
|
776
|
+
}
|
|
777
|
+
logger.info('[Debug] Whistle proxy teardown: ruleGroup=' + whistleRuleName);
|
|
778
|
+
}
|
|
701
779
|
sessionManager.updateStatus(sessionId, result.success ? 'completed' : 'failed');
|
|
702
780
|
// 调试:打印 result 内容
|
|
703
781
|
logger.info(`[executeRunCodeInProcess] result: success=${result.success}, reportFile=${result.reportFile || ''}, errorMessage=${result.errorMessage}`);
|
|
@@ -714,13 +792,17 @@ export class DebugWebSocketServer {
|
|
|
714
792
|
reportUrl,
|
|
715
793
|
error: result.errorMessage,
|
|
716
794
|
platform: request.platform || 'android',
|
|
795
|
+
captureDataUrl,
|
|
717
796
|
}, request.deviceId);
|
|
718
797
|
// 同时发一份 debug_completed,让前端调试面板能正确更新状态
|
|
798
|
+
// capturedRequests 只发摘要给前端展示,完整数据通过 captureDataUrl (OSS URL) 获取
|
|
719
799
|
this.sendMessage(ws, {
|
|
720
800
|
type: 'debug_completed',
|
|
721
801
|
sessionId,
|
|
722
802
|
success: result.success,
|
|
723
803
|
reportUrl,
|
|
804
|
+
capturedRequests: capturedRequests.length > 0 ? this.simplifyCapturedRequests(capturedRequests) : undefined,
|
|
805
|
+
captureDataUrl,
|
|
724
806
|
}, request.deviceId);
|
|
725
807
|
}
|
|
726
808
|
/** 自然语言模式:Fork worker 进程执行 aiAction */
|
|
@@ -741,6 +823,12 @@ export class DebugWebSocketServer {
|
|
|
741
823
|
taskId: sessionId,
|
|
742
824
|
nodeId: this.config.task.nodeId,
|
|
743
825
|
modelConfig: fullModelConfig,
|
|
826
|
+
// 环境代理配置
|
|
827
|
+
environmentId: request.environmentId,
|
|
828
|
+
proxyPort: request.proxyPort || (request.hostMappings && request.hostMappings.length > 0 ? 8899 : undefined),
|
|
829
|
+
hostMappings: request.hostMappings,
|
|
830
|
+
// 公司代理平台账号
|
|
831
|
+
proxyAccount: request.proxyAccount,
|
|
744
832
|
// 添加登录配置
|
|
745
833
|
...(request.loginConfig && {
|
|
746
834
|
loginUrl: request.loginConfig.loginUrl,
|
|
@@ -753,6 +841,39 @@ export class DebugWebSocketServer {
|
|
|
753
841
|
agreementSelector: request.loginConfig.agreementSelector,
|
|
754
842
|
}),
|
|
755
843
|
};
|
|
844
|
+
// Setup proxy rules: 如果有 proxyAccount,使用公司代理平台;否则走本地 Whistle
|
|
845
|
+
const workerWhistleApiBase = execConfig.proxyAccount ? `http://proxy-pc.jd.com/account/${execConfig.proxyAccount}` : undefined;
|
|
846
|
+
if (execConfig.hostMappings && execConfig.hostMappings.length > 0) {
|
|
847
|
+
try {
|
|
848
|
+
const envId = execConfig.environmentId || sessionId;
|
|
849
|
+
const ruleGroupName = whistleManager.generateRuleGroupName(envId);
|
|
850
|
+
const success = await whistleManager.createRuleGroup(ruleGroupName, execConfig.hostMappings, workerWhistleApiBase);
|
|
851
|
+
if (success) {
|
|
852
|
+
const sess = sessionManager.get(sessionId);
|
|
853
|
+
if (sess) {
|
|
854
|
+
sess.whistleRuleGroupName = ruleGroupName;
|
|
855
|
+
sess.whistleApiBase = workerWhistleApiBase;
|
|
856
|
+
}
|
|
857
|
+
if (!execConfig.proxyAccount && !execConfig.proxyPort)
|
|
858
|
+
execConfig.proxyPort = 8899;
|
|
859
|
+
logger.info('[Debug-Worker] Whistle proxy setup: ruleGroup=' + ruleGroupName + ', proxyPort=' + execConfig.proxyPort + ', proxyAccount=' + (execConfig.proxyAccount || 'N/A'));
|
|
860
|
+
// 开始请求抓包(传入 proxyAccount,自动选择正确的 Whistle 实例)
|
|
861
|
+
try {
|
|
862
|
+
await whistleManager.startCapture(sessionId, undefined, 3000, execConfig.proxyAccount);
|
|
863
|
+
logger.info('[Debug-Worker] Whistle request capture started: sessionId=' + sessionId);
|
|
864
|
+
}
|
|
865
|
+
catch (captureErr) {
|
|
866
|
+
logger.warn('[Debug-Worker] Failed to start request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
logger.warn('[Debug-Worker] Whistle proxy setup failed, continuing without proxy');
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
catch (err) {
|
|
874
|
+
logger.warn('[Debug-Worker] Whistle proxy setup error: ' + (err instanceof Error ? err.message : String(err)));
|
|
875
|
+
}
|
|
876
|
+
}
|
|
756
877
|
// Fork worker process for long-running debug
|
|
757
878
|
const workerPath = new URL('../executor/worker-entry.js', import.meta.url).pathname;
|
|
758
879
|
// 优先使用 process.env 中已被 applyModelConfig() 设置的值(前端传入的模型配置),
|
|
@@ -799,10 +920,51 @@ export class DebugWebSocketServer {
|
|
|
799
920
|
return;
|
|
800
921
|
resultHandled = true;
|
|
801
922
|
const success = workerResult.success !== false;
|
|
923
|
+
// 停止请求抓包,采集调试期间的所有请求
|
|
924
|
+
let workerCapturedRequests = [];
|
|
925
|
+
try {
|
|
926
|
+
workerCapturedRequests = await whistleManager.stopCapture(sessionId);
|
|
927
|
+
if (workerCapturedRequests.length > 0) {
|
|
928
|
+
logger.info('[Debug-Worker] Captured ' + workerCapturedRequests.length + ' requests during debug session');
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
catch (captureErr) {
|
|
932
|
+
logger.warn('[Debug-Worker] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
933
|
+
}
|
|
934
|
+
// Teardown Whistle proxy rules if they were set
|
|
935
|
+
const curSess = sessionManager.get(sessionId);
|
|
936
|
+
const wRuleName = curSess ? curSess.whistleRuleGroupName : undefined;
|
|
937
|
+
if (wRuleName) {
|
|
938
|
+
try {
|
|
939
|
+
await whistleManager.removeRuleGroup(wRuleName, workerWhistleApiBase);
|
|
940
|
+
if (curSess) {
|
|
941
|
+
curSess.whistleRuleGroupName = undefined;
|
|
942
|
+
curSess.whistleApiBase = undefined;
|
|
943
|
+
}
|
|
944
|
+
logger.info('[Debug-Worker] Whistle proxy teardown: ruleGroup=' + wRuleName);
|
|
945
|
+
}
|
|
946
|
+
catch (err) {
|
|
947
|
+
logger.warn('[Debug-Worker] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
948
|
+
}
|
|
949
|
+
}
|
|
802
950
|
sessionManager.updateStatus(sessionId, success ? 'completed' : 'failed');
|
|
803
951
|
const reportFile = workerResult.reportFile || '';
|
|
804
952
|
const reportUrl = await this.safeUploadReport(reportFile, sessionId);
|
|
805
953
|
logger.info(`[Debug] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
954
|
+
// 将 worker 抓包数据保存到文件并上传到 OSS
|
|
955
|
+
let workerCaptureDataUrl;
|
|
956
|
+
if (workerCapturedRequests.length > 0) {
|
|
957
|
+
try {
|
|
958
|
+
const uploadResult = await this.saveAndUploadCaptureData(workerCapturedRequests, sessionId);
|
|
959
|
+
if (uploadResult) {
|
|
960
|
+
workerCaptureDataUrl = uploadResult;
|
|
961
|
+
logger.info('[Debug-Worker] Capture data uploaded: ' + workerCaptureDataUrl);
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
catch (uploadErr) {
|
|
965
|
+
logger.warn('[Debug-Worker] Failed to upload capture data: ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
966
|
+
}
|
|
967
|
+
}
|
|
806
968
|
this.sendMessage(ws, {
|
|
807
969
|
type: 'debug_completed',
|
|
808
970
|
sessionId,
|
|
@@ -810,6 +972,8 @@ export class DebugWebSocketServer {
|
|
|
810
972
|
dump: '',
|
|
811
973
|
reportUrl,
|
|
812
974
|
error: workerResult.errorMessage,
|
|
975
|
+
capturedRequests: workerCapturedRequests.length > 0 ? this.simplifyCapturedRequests(workerCapturedRequests) : undefined,
|
|
976
|
+
captureDataUrl: workerCaptureDataUrl,
|
|
813
977
|
}, request.deviceId);
|
|
814
978
|
};
|
|
815
979
|
childProcess.on('message', (message) => {
|
|
@@ -893,6 +1057,12 @@ export class DebugWebSocketServer {
|
|
|
893
1057
|
modelConfig: fullModelConfig,
|
|
894
1058
|
executionId: sessionId,
|
|
895
1059
|
skipAppRestart: request.skipAppRestart,
|
|
1060
|
+
// 环境代理配置
|
|
1061
|
+
environmentId: request.environmentId,
|
|
1062
|
+
proxyPort: request.proxyPort,
|
|
1063
|
+
hostMappings: request.hostMappings,
|
|
1064
|
+
// 公司代理平台账号
|
|
1065
|
+
proxyAccount: request.proxyAccount,
|
|
896
1066
|
};
|
|
897
1067
|
// 使用 AbortSignal 监听停止请求
|
|
898
1068
|
const executePromise = executor.execute(config);
|
|
@@ -1685,6 +1855,30 @@ export class DebugWebSocketServer {
|
|
|
1685
1855
|
return;
|
|
1686
1856
|
}
|
|
1687
1857
|
await this.closeWebSession(request.sessionId);
|
|
1858
|
+
// 停止请求抓包
|
|
1859
|
+
try {
|
|
1860
|
+
await whistleManager.stopCapture(request.sessionId);
|
|
1861
|
+
}
|
|
1862
|
+
catch (captureErr) {
|
|
1863
|
+
logger.warn('[CloseWebSession] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1864
|
+
}
|
|
1865
|
+
// Teardown Whistle proxy rules if they were set
|
|
1866
|
+
const closeSess = sessionManager.get(request.sessionId);
|
|
1867
|
+
const closeWhistleRule = closeSess ? closeSess.whistleRuleGroupName : undefined;
|
|
1868
|
+
if (closeWhistleRule) {
|
|
1869
|
+
try {
|
|
1870
|
+
const closeWhistleApiBase = closeSess ? closeSess.whistleApiBase : undefined;
|
|
1871
|
+
await whistleManager.removeRuleGroup(closeWhistleRule, closeWhistleApiBase);
|
|
1872
|
+
if (closeSess) {
|
|
1873
|
+
closeSess.whistleRuleGroupName = undefined;
|
|
1874
|
+
closeSess.whistleApiBase = undefined;
|
|
1875
|
+
}
|
|
1876
|
+
logger.info('[CloseWebSession] Whistle proxy teardown: ruleGroup=' + closeWhistleRule);
|
|
1877
|
+
}
|
|
1878
|
+
catch (err) {
|
|
1879
|
+
logger.warn('[CloseWebSession] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1688
1882
|
sessionManager.updateStatus(request.sessionId, 'stopped');
|
|
1689
1883
|
this.sendMessage(ws, { type: 'web_session_closed', sessionId: request.sessionId });
|
|
1690
1884
|
}
|
|
@@ -1749,15 +1943,59 @@ export class DebugWebSocketServer {
|
|
|
1749
1943
|
}
|
|
1750
1944
|
// 关闭 web 浏览器(如果存在)
|
|
1751
1945
|
await this.closeWebSession(request.sessionId);
|
|
1946
|
+
// 停止请求抓包,采集调试期间的所有请求
|
|
1947
|
+
let stopCapturedRequests = [];
|
|
1948
|
+
try {
|
|
1949
|
+
stopCapturedRequests = await whistleManager.stopCapture(request.sessionId);
|
|
1950
|
+
if (stopCapturedRequests.length > 0) {
|
|
1951
|
+
logger.info('[StopDebug] Captured ' + stopCapturedRequests.length + ' requests during debug session');
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
catch (captureErr) {
|
|
1955
|
+
logger.warn('[StopDebug] Failed to stop request capture: ' + (captureErr instanceof Error ? captureErr.message : String(captureErr)));
|
|
1956
|
+
}
|
|
1957
|
+
// Teardown Whistle proxy rules if they were set
|
|
1958
|
+
const stopSess = sessionManager.get(request.sessionId);
|
|
1959
|
+
const stopWhistleRule = stopSess ? stopSess.whistleRuleGroupName : undefined;
|
|
1960
|
+
if (stopWhistleRule) {
|
|
1961
|
+
try {
|
|
1962
|
+
const stopWhistleApiBase = stopSess ? stopSess.whistleApiBase : undefined;
|
|
1963
|
+
await whistleManager.removeRuleGroup(stopWhistleRule, stopWhistleApiBase);
|
|
1964
|
+
if (stopSess) {
|
|
1965
|
+
stopSess.whistleRuleGroupName = undefined;
|
|
1966
|
+
stopSess.whistleApiBase = undefined;
|
|
1967
|
+
}
|
|
1968
|
+
logger.info('[StopDebug] Whistle proxy teardown: ruleGroup=' + stopWhistleRule);
|
|
1969
|
+
}
|
|
1970
|
+
catch (err) {
|
|
1971
|
+
logger.warn('[StopDebug] Failed to teardown Whistle proxy: ' + (err instanceof Error ? err.message : String(err)));
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1752
1974
|
sessionManager.updateStatus(request.sessionId, 'stopped');
|
|
1753
1975
|
// 上传报告文件到服务端,拿到 reportUrl
|
|
1754
1976
|
const reportUrl = await this.safeUploadReport(reportFile, request.sessionId);
|
|
1755
1977
|
logger.info(`[StopDebug] report uploaded: reportUrl=${reportUrl || 'null'}, reportFile=${reportFile}`);
|
|
1978
|
+
// 将停止时的抓包数据保存到文件并上传到 OSS
|
|
1979
|
+
let stopCaptureDataUrl;
|
|
1980
|
+
if (stopCapturedRequests.length > 0) {
|
|
1981
|
+
try {
|
|
1982
|
+
const uploadResult = await this.saveAndUploadCaptureData(stopCapturedRequests, request.sessionId);
|
|
1983
|
+
if (uploadResult) {
|
|
1984
|
+
stopCaptureDataUrl = uploadResult;
|
|
1985
|
+
logger.info('[StopDebug] Capture data uploaded: ' + stopCaptureDataUrl);
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
catch (uploadErr) {
|
|
1989
|
+
logger.warn('[StopDebug] Failed to upload capture data: ' + (uploadErr instanceof Error ? uploadErr.message : String(uploadErr)));
|
|
1990
|
+
}
|
|
1991
|
+
}
|
|
1756
1992
|
// 发送带执行数据的停止消息
|
|
1757
1993
|
this.sendMessage(ws, {
|
|
1758
1994
|
type: 'debug_stopped',
|
|
1759
1995
|
sessionId: request.sessionId,
|
|
1760
1996
|
reportUrl,
|
|
1997
|
+
capturedRequests: stopCapturedRequests.length > 0 ? this.simplifyCapturedRequests(stopCapturedRequests) : undefined,
|
|
1998
|
+
captureDataUrl: stopCaptureDataUrl,
|
|
1761
1999
|
});
|
|
1762
2000
|
// 同时发送 action_result,让前端能获取到停止时的执行数据
|
|
1763
2001
|
this.sendMessage(ws, {
|
|
@@ -1774,6 +2012,8 @@ export class DebugWebSocketServer {
|
|
|
1774
2012
|
sessionId: request.sessionId,
|
|
1775
2013
|
success: false,
|
|
1776
2014
|
reportUrl,
|
|
2015
|
+
capturedRequests: stopCapturedRequests.length > 0 ? this.simplifyCapturedRequests(stopCapturedRequests) : undefined,
|
|
2016
|
+
captureDataUrl: stopCaptureDataUrl,
|
|
1777
2017
|
});
|
|
1778
2018
|
}
|
|
1779
2019
|
// ==================== Get Logs ====================
|
|
@@ -1937,6 +2177,97 @@ export class DebugWebSocketServer {
|
|
|
1937
2177
|
sendError(ws, error) {
|
|
1938
2178
|
this.sendMessage(ws, { type: 'error', error });
|
|
1939
2179
|
}
|
|
2180
|
+
/**
|
|
2181
|
+
* 精简 CapturedRequest 列表,去除大 body 只保留摘要信息,
|
|
2182
|
+
* 避免 WebSocket 消息过大(单条 body 上限 4KB,超出截断)
|
|
2183
|
+
*/
|
|
2184
|
+
/**
|
|
2185
|
+
* 将抓包数据保存到本地文件并上传到 OSS
|
|
2186
|
+
* 返回 OSS URL,失败则返回 undefined
|
|
2187
|
+
*/
|
|
2188
|
+
async saveAndUploadCaptureData(capturedRequests, sessionId) {
|
|
2189
|
+
if (!capturedRequests || capturedRequests.length === 0)
|
|
2190
|
+
return undefined;
|
|
2191
|
+
try {
|
|
2192
|
+
// 保存到本地临时文件
|
|
2193
|
+
const tmpDir = path.join(process.cwd(), 'tmp', 'captures');
|
|
2194
|
+
if (!fs.existsSync(tmpDir)) {
|
|
2195
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
2196
|
+
}
|
|
2197
|
+
const captureFile = path.join(tmpDir, `capture_${sessionId}_${Date.now()}.json`);
|
|
2198
|
+
fs.writeFileSync(captureFile, JSON.stringify(capturedRequests, null, 2));
|
|
2199
|
+
logger.info(`[CaptureUpload] Saved capture data to: ${captureFile} (${capturedRequests.length} requests)`);
|
|
2200
|
+
// 上传到 OSS
|
|
2201
|
+
const uploadUrl = this.config?.callback?.packageFileUploadUrl;
|
|
2202
|
+
if (!uploadUrl) {
|
|
2203
|
+
logger.warn('[CaptureUpload] packageFileUploadUrl not configured, skip upload');
|
|
2204
|
+
return undefined;
|
|
2205
|
+
}
|
|
2206
|
+
const captureDataUrl = await uploadCapturePackageFile(captureFile, sessionId, uploadUrl);
|
|
2207
|
+
if (captureDataUrl) {
|
|
2208
|
+
// 上传成功后删除本地临时文件
|
|
2209
|
+
try {
|
|
2210
|
+
fs.unlinkSync(captureFile);
|
|
2211
|
+
}
|
|
2212
|
+
catch { }
|
|
2213
|
+
return captureDataUrl;
|
|
2214
|
+
}
|
|
2215
|
+
return undefined;
|
|
2216
|
+
}
|
|
2217
|
+
catch (err) {
|
|
2218
|
+
logger.error('[CaptureUpload] Failed to save/upload capture data: ' + (err instanceof Error ? err.message : String(err)));
|
|
2219
|
+
return undefined;
|
|
2220
|
+
}
|
|
2221
|
+
}
|
|
2222
|
+
simplifyCapturedRequests(requests) {
|
|
2223
|
+
const MAX_BODY_LENGTH = 4096;
|
|
2224
|
+
return requests.map(req => {
|
|
2225
|
+
const simplified = {
|
|
2226
|
+
id: req.id,
|
|
2227
|
+
url: req.url,
|
|
2228
|
+
method: req.method,
|
|
2229
|
+
statusCode: req.statusCode,
|
|
2230
|
+
clientIp: req.clientIp,
|
|
2231
|
+
startTime: req.startTime,
|
|
2232
|
+
endTime: req.endTime,
|
|
2233
|
+
duration: req.duration,
|
|
2234
|
+
dnsTime: req.dnsTime,
|
|
2235
|
+
requestTime: req.requestTime,
|
|
2236
|
+
responseTime: req.responseTime,
|
|
2237
|
+
};
|
|
2238
|
+
// 请求头只保留 content-type 和 content-length
|
|
2239
|
+
if (req.reqHeaders) {
|
|
2240
|
+
const filteredReqHeaders = {};
|
|
2241
|
+
for (const [k, v] of Object.entries(req.reqHeaders)) {
|
|
2242
|
+
if (k.toLowerCase() === 'content-type' || k.toLowerCase() === 'content-length') {
|
|
2243
|
+
filteredReqHeaders[k] = v;
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
simplified.reqHeaders = filteredReqHeaders;
|
|
2247
|
+
}
|
|
2248
|
+
if (req.resHeaders) {
|
|
2249
|
+
const filteredResHeaders = {};
|
|
2250
|
+
for (const [k, v] of Object.entries(req.resHeaders)) {
|
|
2251
|
+
if (k.toLowerCase() === 'content-type' || k.toLowerCase() === 'content-length') {
|
|
2252
|
+
filteredResHeaders[k] = v;
|
|
2253
|
+
}
|
|
2254
|
+
}
|
|
2255
|
+
simplified.resHeaders = filteredResHeaders;
|
|
2256
|
+
}
|
|
2257
|
+
// body 截断
|
|
2258
|
+
if (req.reqBody) {
|
|
2259
|
+
simplified.reqBody = req.reqBody.length > MAX_BODY_LENGTH
|
|
2260
|
+
? req.reqBody.substring(0, MAX_BODY_LENGTH) + '...[truncated]'
|
|
2261
|
+
: req.reqBody;
|
|
2262
|
+
}
|
|
2263
|
+
if (req.resBody) {
|
|
2264
|
+
simplified.resBody = req.resBody.length > MAX_BODY_LENGTH
|
|
2265
|
+
? req.resBody.substring(0, MAX_BODY_LENGTH) + '...[truncated]'
|
|
2266
|
+
: req.resBody;
|
|
2267
|
+
}
|
|
2268
|
+
return simplified;
|
|
2269
|
+
});
|
|
2270
|
+
}
|
|
1940
2271
|
async cleanupSession(sessionId) {
|
|
1941
2272
|
// 关闭 web 浏览器(如果存在)
|
|
1942
2273
|
await this.closeWebSession(sessionId);
|