@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
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
export interface HostMapping {
|
|
2
|
+
domain: string;
|
|
3
|
+
ip: string;
|
|
4
|
+
}
|
|
5
|
+
/** Whistle 原始请求项(/cgi-bin/get-data 返回) */
|
|
6
|
+
export interface WhistleRawRequest {
|
|
7
|
+
id: string;
|
|
8
|
+
url: string;
|
|
9
|
+
startTime: number;
|
|
10
|
+
endTime: number;
|
|
11
|
+
dnsTime?: number;
|
|
12
|
+
requestTime?: number;
|
|
13
|
+
responseTime?: number;
|
|
14
|
+
req: {
|
|
15
|
+
method: string;
|
|
16
|
+
httpVersion?: string;
|
|
17
|
+
ip?: string;
|
|
18
|
+
headers?: Record<string, string>;
|
|
19
|
+
body?: string;
|
|
20
|
+
base64?: string;
|
|
21
|
+
};
|
|
22
|
+
res: {
|
|
23
|
+
statusCode?: number;
|
|
24
|
+
statusMessage?: string;
|
|
25
|
+
headers?: Record<string, string>;
|
|
26
|
+
body?: string;
|
|
27
|
+
base64?: string;
|
|
28
|
+
};
|
|
29
|
+
clientId?: string;
|
|
30
|
+
}
|
|
31
|
+
/** 精简后的捕获请求(推送给前端 / AI 分析用) */
|
|
32
|
+
export interface CapturedRequest {
|
|
33
|
+
id: string;
|
|
34
|
+
url: string;
|
|
35
|
+
method: string;
|
|
36
|
+
statusCode: number | null;
|
|
37
|
+
reqHeaders: Record<string, string>;
|
|
38
|
+
resHeaders: Record<string, string>;
|
|
39
|
+
reqBody: string | null;
|
|
40
|
+
resBody: string | null;
|
|
41
|
+
clientIp: string | null;
|
|
42
|
+
startTime: number;
|
|
43
|
+
endTime: number;
|
|
44
|
+
duration: number;
|
|
45
|
+
dnsTime?: number;
|
|
46
|
+
requestTime?: number;
|
|
47
|
+
responseTime?: number;
|
|
48
|
+
}
|
|
49
|
+
/** 请求抓包选项 */
|
|
50
|
+
export interface CaptureOptions {
|
|
51
|
+
/** 起始请求ID(从此ID之后开始获取) */
|
|
52
|
+
sinceId?: string;
|
|
53
|
+
/** 获取数量(默认100,最大100) */
|
|
54
|
+
count?: number;
|
|
55
|
+
/** 按客户端IP过滤 */
|
|
56
|
+
clientIp?: string;
|
|
57
|
+
/** 按URL关键词过滤 */
|
|
58
|
+
urlKeyword?: string;
|
|
59
|
+
}
|
|
60
|
+
export declare class WhistleRuleManager {
|
|
61
|
+
private readonly apiBase;
|
|
62
|
+
private readonly clientId;
|
|
63
|
+
/** 活跃的抓包会话:sessionId -> CaptureSession */
|
|
64
|
+
private captureSessions;
|
|
65
|
+
constructor();
|
|
66
|
+
private formatRulesText;
|
|
67
|
+
createRuleGroup(ruleGroupName: string, hostMappings: HostMapping[], customApiBase?: string): Promise<boolean>;
|
|
68
|
+
removeRuleGroup(ruleGroupName: string, customApiBase?: string): Promise<boolean>;
|
|
69
|
+
generateRuleGroupName(environmentId: string): string;
|
|
70
|
+
/**
|
|
71
|
+
* 获取 Whistle 当前最新的请求ID
|
|
72
|
+
* 调试开始前调用,记录起始位置,后续增量获取从此ID之后开始
|
|
73
|
+
*/
|
|
74
|
+
getLastDataId(customApiBase?: string): Promise<string | null>;
|
|
75
|
+
/**
|
|
76
|
+
* 增量获取请求列表
|
|
77
|
+
* @param options 抓包选项(sinceId、count、clientIp、urlKeyword)
|
|
78
|
+
* @returns 捕获请求列表 + 本批最后一条ID
|
|
79
|
+
*/
|
|
80
|
+
fetchRequestList(options?: CaptureOptions, customApiBase?: string): Promise<{
|
|
81
|
+
requests: CapturedRequest[];
|
|
82
|
+
lastId: string | null;
|
|
83
|
+
}>;
|
|
84
|
+
/**
|
|
85
|
+
* 按ID获取请求详情(含完整 req/res body)
|
|
86
|
+
* 适用于需要获取完整 body 的场景(/cgi-bin/get-data 默认不返回 body)
|
|
87
|
+
*/
|
|
88
|
+
fetchRequestDetail(ids: string[], customApiBase?: string): Promise<CapturedRequest[]>;
|
|
89
|
+
/**
|
|
90
|
+
* 开始抓包会话
|
|
91
|
+
* 记录当前最新请求ID,后续增量获取从此ID之后开始
|
|
92
|
+
* @param sessionId 调试会话ID
|
|
93
|
+
* @param clientIp 可选,设备IP(用于过滤只看该设备的请求)
|
|
94
|
+
* @param pollInterval 轮询间隔(毫秒),默认3000
|
|
95
|
+
*/
|
|
96
|
+
startCapture(sessionId: string, clientIp?: string, pollInterval?: number, proxyAccount?: string): Promise<boolean>;
|
|
97
|
+
/**
|
|
98
|
+
* 停止抓包会话,返回所有采集到的请求
|
|
99
|
+
* @param sessionId 调试会话ID
|
|
100
|
+
* @returns 采集到的请求列表
|
|
101
|
+
*/
|
|
102
|
+
stopCapture(sessionId: string): Promise<CapturedRequest[]>;
|
|
103
|
+
/**
|
|
104
|
+
* 启动定时轮询采集(调试期间持续采集请求)
|
|
105
|
+
* @param sessionId 调试会话ID
|
|
106
|
+
* @param onNewRequests 每次轮询到新请求时的回调
|
|
107
|
+
*/
|
|
108
|
+
startPolling(sessionId: string, onNewRequests?: (requests: CapturedRequest[]) => void): void;
|
|
109
|
+
/**
|
|
110
|
+
* 停止定时轮询
|
|
111
|
+
*/
|
|
112
|
+
stopPolling(sessionId: string): void;
|
|
113
|
+
/**
|
|
114
|
+
* 获取当前抓包会话已采集的请求数量
|
|
115
|
+
*/
|
|
116
|
+
getCapturedCount(sessionId: string): number;
|
|
117
|
+
/**
|
|
118
|
+
* 获取当前抓包会话已采集的所有请求
|
|
119
|
+
*/
|
|
120
|
+
getCapturedRequests(sessionId: string): CapturedRequest[];
|
|
121
|
+
/**
|
|
122
|
+
* 将 Whistle 原始请求转换为精简的 CapturedRequest
|
|
123
|
+
*/
|
|
124
|
+
private convertRawRequest;
|
|
125
|
+
}
|
|
126
|
+
export declare const whistleManager: WhistleRuleManager;
|
|
127
|
+
//# sourceMappingURL=whistle-manager.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whistle-manager.d.ts","sourceRoot":"","sources":["../../src/proxy/whistle-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import { createLogger } from '../core/logger.js';
|
|
2
|
+
const logger = createLogger('WhistleManager');
|
|
3
|
+
export class WhistleRuleManager {
|
|
4
|
+
apiBase;
|
|
5
|
+
clientId;
|
|
6
|
+
/** 活跃的抓包会话:sessionId -> CaptureSession */
|
|
7
|
+
captureSessions = new Map();
|
|
8
|
+
constructor() {
|
|
9
|
+
this.apiBase = process.env.WHISTLE_API_BASE || 'http://127.0.0.1:8899';
|
|
10
|
+
this.clientId = process.env.WHISTLE_CLIENT_ID || 'guada-ai-framework';
|
|
11
|
+
}
|
|
12
|
+
// ==================== 规则管理(已有功能) ====================
|
|
13
|
+
formatRulesText(hostMappings) {
|
|
14
|
+
return hostMappings.map(m => m.ip + ' ' + m.domain).join('\n');
|
|
15
|
+
}
|
|
16
|
+
async createRuleGroup(ruleGroupName, hostMappings, customApiBase) {
|
|
17
|
+
try {
|
|
18
|
+
const apiBase = customApiBase || this.apiBase;
|
|
19
|
+
const addParams = new URLSearchParams();
|
|
20
|
+
addParams.append('clientId', this.clientId);
|
|
21
|
+
addParams.append('name', ruleGroupName);
|
|
22
|
+
addParams.append('addToTop', '');
|
|
23
|
+
const addResponse = await fetch(apiBase + '/cgi-bin/rules/add', {
|
|
24
|
+
method: 'POST',
|
|
25
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
26
|
+
body: addParams.toString(),
|
|
27
|
+
});
|
|
28
|
+
const addResult = await addResponse.json();
|
|
29
|
+
if (addResult.ec !== 0) {
|
|
30
|
+
logger.error('Failed to create rule group: ' + JSON.stringify(addResult));
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
logger.info('Rule group [' + ruleGroupName + '] created');
|
|
34
|
+
const rulesText = this.formatRulesText(hostMappings);
|
|
35
|
+
const selectParams = new URLSearchParams();
|
|
36
|
+
selectParams.append('clientId', this.clientId);
|
|
37
|
+
selectParams.append('key', 'w-reactkey-' + Date.now());
|
|
38
|
+
selectParams.append('name', ruleGroupName);
|
|
39
|
+
selectParams.append('value', rulesText);
|
|
40
|
+
selectParams.append('hide', 'false');
|
|
41
|
+
selectParams.append('active', 'true');
|
|
42
|
+
selectParams.append('changed', 'true');
|
|
43
|
+
const selectResponse = await fetch(apiBase + '/cgi-bin/rules/select', {
|
|
44
|
+
method: 'POST',
|
|
45
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
46
|
+
body: selectParams.toString(),
|
|
47
|
+
});
|
|
48
|
+
const selectResult = await selectResponse.json();
|
|
49
|
+
if (selectResult.ec !== 0) {
|
|
50
|
+
logger.error('Failed to write rule content: ' + JSON.stringify(selectResult));
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
logger.info('Rule group [' + ruleGroupName + '] wrote ' + hostMappings.length + ' host mappings');
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logger.error('Error creating rule group [' + ruleGroupName + ']: ' + (error instanceof Error ? error.message : String(error)));
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
async removeRuleGroup(ruleGroupName, customApiBase) {
|
|
62
|
+
try {
|
|
63
|
+
const apiBase = customApiBase || this.apiBase;
|
|
64
|
+
const params = new URLSearchParams();
|
|
65
|
+
params.append('clientId', this.clientId);
|
|
66
|
+
params.append('list[]', ruleGroupName);
|
|
67
|
+
const response = await fetch(apiBase + '/cgi-bin/rules/remove', {
|
|
68
|
+
method: 'POST',
|
|
69
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
70
|
+
body: params.toString(),
|
|
71
|
+
});
|
|
72
|
+
const result = await response.json();
|
|
73
|
+
if (result.ec !== 0) {
|
|
74
|
+
logger.error('Failed to remove rule group: ' + JSON.stringify(result));
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
logger.info('Rule group [' + ruleGroupName + '] removed');
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
logger.error('Error removing rule group [' + ruleGroupName + ']: ' + (error instanceof Error ? error.message : String(error)));
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
generateRuleGroupName(environmentId) {
|
|
86
|
+
return 'guada-env-' + environmentId;
|
|
87
|
+
}
|
|
88
|
+
// ==================== 请求抓包(新增功能) ====================
|
|
89
|
+
/**
|
|
90
|
+
* 获取 Whistle 当前最新的请求ID
|
|
91
|
+
* 调试开始前调用,记录起始位置,后续增量获取从此ID之后开始
|
|
92
|
+
*/
|
|
93
|
+
async getLastDataId(customApiBase) {
|
|
94
|
+
try {
|
|
95
|
+
const apiBase = customApiBase || this.apiBase;
|
|
96
|
+
const url = apiBase + '/cgi-bin/get-data?count=1';
|
|
97
|
+
const response = await fetch(url);
|
|
98
|
+
const result = await response.json();
|
|
99
|
+
if (result.ec !== 0 || !result.data) {
|
|
100
|
+
logger.warn('Failed to get last data ID: ec=' + result.ec);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const lastId = result.data.endId || result.data.lastId || null;
|
|
104
|
+
logger.info('Last data ID: ' + lastId);
|
|
105
|
+
return lastId;
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
logger.error('Error getting last data ID: ' + (error instanceof Error ? error.message : String(error)));
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* 增量获取请求列表
|
|
114
|
+
* @param options 抓包选项(sinceId、count、clientIp、urlKeyword)
|
|
115
|
+
* @returns 捕获请求列表 + 本批最后一条ID
|
|
116
|
+
*/
|
|
117
|
+
async fetchRequestList(options = {}, customApiBase) {
|
|
118
|
+
try {
|
|
119
|
+
const count = Math.min(options.count || 100, 100);
|
|
120
|
+
const params = new URLSearchParams();
|
|
121
|
+
params.append('count', String(count));
|
|
122
|
+
if (options.sinceId) {
|
|
123
|
+
params.append('startTime', options.sinceId);
|
|
124
|
+
}
|
|
125
|
+
if (options.clientIp) {
|
|
126
|
+
params.append('ip', options.clientIp);
|
|
127
|
+
}
|
|
128
|
+
if (options.urlKeyword) {
|
|
129
|
+
params.append('url', options.urlKeyword);
|
|
130
|
+
}
|
|
131
|
+
const apiBase = customApiBase || this.apiBase;
|
|
132
|
+
const url = apiBase + '/cgi-bin/get-data?' + params.toString();
|
|
133
|
+
const response = await fetch(url);
|
|
134
|
+
const result = await response.json();
|
|
135
|
+
if (result.ec !== 0 || !result.data || !result.data.data) {
|
|
136
|
+
logger.warn('Failed to fetch request list: ec=' + result.ec);
|
|
137
|
+
return { requests: [], lastId: null };
|
|
138
|
+
}
|
|
139
|
+
const rawData = result.data.data;
|
|
140
|
+
const requests = [];
|
|
141
|
+
for (const id of (result.data.newIds || [])) {
|
|
142
|
+
const raw = rawData[id];
|
|
143
|
+
if (raw) {
|
|
144
|
+
requests.push(this.convertRawRequest(raw));
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// 按startTime排序(ID本身就是时间戳格式,所以按ID排序即可)
|
|
148
|
+
requests.sort((a, b) => a.startTime - b.startTime);
|
|
149
|
+
const lastId = result.data.lastId || result.data.endId || null;
|
|
150
|
+
return { requests, lastId };
|
|
151
|
+
}
|
|
152
|
+
catch (error) {
|
|
153
|
+
logger.error('Error fetching request list: ' + (error instanceof Error ? error.message : String(error)));
|
|
154
|
+
return { requests: [], lastId: null };
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* 按ID获取请求详情(含完整 req/res body)
|
|
159
|
+
* 适用于需要获取完整 body 的场景(/cgi-bin/get-data 默认不返回 body)
|
|
160
|
+
*/
|
|
161
|
+
async fetchRequestDetail(ids, customApiBase) {
|
|
162
|
+
if (ids.length === 0)
|
|
163
|
+
return [];
|
|
164
|
+
try {
|
|
165
|
+
const params = new URLSearchParams();
|
|
166
|
+
// get-session 要求 reqList 和 resList 为 JSON 数组字符串
|
|
167
|
+
params.append('reqList', JSON.stringify(ids));
|
|
168
|
+
params.append('resList', JSON.stringify(ids));
|
|
169
|
+
const apiBase = customApiBase || this.apiBase;
|
|
170
|
+
const response = await fetch(apiBase + '/cgi-bin/get-session?' + params.toString());
|
|
171
|
+
const result = await response.json();
|
|
172
|
+
// get-session 直接返回数据对象(无 ec 字段),空对象表示无数据
|
|
173
|
+
if (!result || typeof result !== 'object' || Object.keys(result).length === 0) {
|
|
174
|
+
logger.warn('Failed to fetch request detail: empty response');
|
|
175
|
+
return [];
|
|
176
|
+
}
|
|
177
|
+
const requests = [];
|
|
178
|
+
for (const id of ids) {
|
|
179
|
+
const raw = result[id];
|
|
180
|
+
if (raw) {
|
|
181
|
+
requests.push(this.convertRawRequest(raw));
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return requests;
|
|
185
|
+
}
|
|
186
|
+
catch (error) {
|
|
187
|
+
logger.error('Error fetching request detail: ' + (error instanceof Error ? error.message : String(error)));
|
|
188
|
+
return [];
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 开始抓包会话
|
|
193
|
+
* 记录当前最新请求ID,后续增量获取从此ID之后开始
|
|
194
|
+
* @param sessionId 调试会话ID
|
|
195
|
+
* @param clientIp 可选,设备IP(用于过滤只看该设备的请求)
|
|
196
|
+
* @param pollInterval 轮询间隔(毫秒),默认3000
|
|
197
|
+
*/
|
|
198
|
+
async startCapture(sessionId, clientIp, pollInterval = 3000, proxyAccount) {
|
|
199
|
+
// 如果已有抓包会话,先停止
|
|
200
|
+
if (this.captureSessions.has(sessionId)) {
|
|
201
|
+
this.stopCapture(sessionId);
|
|
202
|
+
}
|
|
203
|
+
// 根据 proxyAccount 决定使用哪个 Whistle 实例
|
|
204
|
+
const captureApiBase = proxyAccount ? `http://proxy-pc.jd.com/account/${proxyAccount}` : this.apiBase;
|
|
205
|
+
// 获取当前最新请求ID作为起始点
|
|
206
|
+
const lastId = await this.getLastDataId(captureApiBase);
|
|
207
|
+
if (lastId === null) {
|
|
208
|
+
logger.warn('[Capture] Failed to get start data ID, using empty string as fallback');
|
|
209
|
+
}
|
|
210
|
+
const captureSession = {
|
|
211
|
+
startDataId: lastId || '',
|
|
212
|
+
sessionId,
|
|
213
|
+
clientIp,
|
|
214
|
+
capturedRequests: [],
|
|
215
|
+
pollTimer: null,
|
|
216
|
+
pollInterval,
|
|
217
|
+
apiBase: captureApiBase,
|
|
218
|
+
};
|
|
219
|
+
this.captureSessions.set(sessionId, captureSession);
|
|
220
|
+
logger.info('[Capture] Started capture session: sessionId=' + sessionId + ', startDataId=' + lastId + ', clientIp=' + (clientIp || 'all'));
|
|
221
|
+
return true;
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* 停止抓包会话,返回所有采集到的请求
|
|
225
|
+
* @param sessionId 调试会话ID
|
|
226
|
+
* @returns 采集到的请求列表
|
|
227
|
+
*/
|
|
228
|
+
async stopCapture(sessionId) {
|
|
229
|
+
const capture = this.captureSessions.get(sessionId);
|
|
230
|
+
if (!capture) {
|
|
231
|
+
logger.warn('[Capture] No capture session found: sessionId=' + sessionId);
|
|
232
|
+
return [];
|
|
233
|
+
}
|
|
234
|
+
// 停止轮询
|
|
235
|
+
if (capture.pollTimer) {
|
|
236
|
+
clearInterval(capture.pollTimer);
|
|
237
|
+
capture.pollTimer = null;
|
|
238
|
+
}
|
|
239
|
+
// 最后一次拉取增量数据
|
|
240
|
+
try {
|
|
241
|
+
const lastId = capture.capturedRequests.length > 0
|
|
242
|
+
? capture.capturedRequests[capture.capturedRequests.length - 1].id
|
|
243
|
+
: capture.startDataId;
|
|
244
|
+
const { requests } = await this.fetchRequestList({
|
|
245
|
+
sinceId: lastId,
|
|
246
|
+
clientIp: capture.clientIp,
|
|
247
|
+
count: 100,
|
|
248
|
+
}, capture.apiBase);
|
|
249
|
+
if (requests.length > 0) {
|
|
250
|
+
capture.capturedRequests.push(...requests);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
catch (error) {
|
|
254
|
+
logger.warn('[Capture] Final fetch failed: ' + (error instanceof Error ? error.message : String(error)));
|
|
255
|
+
}
|
|
256
|
+
// 清理
|
|
257
|
+
this.captureSessions.delete(sessionId);
|
|
258
|
+
logger.info('[Capture] Stopped capture session: sessionId=' + sessionId + ', totalRequests=' + capture.capturedRequests.length);
|
|
259
|
+
return capture.capturedRequests;
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* 启动定时轮询采集(调试期间持续采集请求)
|
|
263
|
+
* @param sessionId 调试会话ID
|
|
264
|
+
* @param onNewRequests 每次轮询到新请求时的回调
|
|
265
|
+
*/
|
|
266
|
+
startPolling(sessionId, onNewRequests) {
|
|
267
|
+
const capture = this.captureSessions.get(sessionId);
|
|
268
|
+
if (!capture) {
|
|
269
|
+
logger.warn('[Capture] Cannot start polling, no capture session: sessionId=' + sessionId);
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
if (capture.pollTimer) {
|
|
273
|
+
clearInterval(capture.pollTimer);
|
|
274
|
+
}
|
|
275
|
+
capture.pollTimer = setInterval(async () => {
|
|
276
|
+
try {
|
|
277
|
+
const lastId = capture.capturedRequests.length > 0
|
|
278
|
+
? capture.capturedRequests[capture.capturedRequests.length - 1].id
|
|
279
|
+
: capture.startDataId;
|
|
280
|
+
const { requests } = await this.fetchRequestList({
|
|
281
|
+
sinceId: lastId,
|
|
282
|
+
clientIp: capture.clientIp,
|
|
283
|
+
count: 100,
|
|
284
|
+
}, capture.apiBase);
|
|
285
|
+
if (requests.length > 0) {
|
|
286
|
+
capture.capturedRequests.push(...requests);
|
|
287
|
+
if (onNewRequests) {
|
|
288
|
+
onNewRequests(requests);
|
|
289
|
+
}
|
|
290
|
+
logger.debug('[Capture] Polling got ' + requests.length + ' new requests for session ' + sessionId);
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
catch (error) {
|
|
294
|
+
logger.warn('[Capture] Polling error: ' + (error instanceof Error ? error.message : String(error)));
|
|
295
|
+
}
|
|
296
|
+
}, capture.pollInterval);
|
|
297
|
+
logger.info('[Capture] Polling started: sessionId=' + sessionId + ', interval=' + capture.pollInterval + 'ms');
|
|
298
|
+
}
|
|
299
|
+
/**
|
|
300
|
+
* 停止定时轮询
|
|
301
|
+
*/
|
|
302
|
+
stopPolling(sessionId) {
|
|
303
|
+
const capture = this.captureSessions.get(sessionId);
|
|
304
|
+
if (capture && capture.pollTimer) {
|
|
305
|
+
clearInterval(capture.pollTimer);
|
|
306
|
+
capture.pollTimer = null;
|
|
307
|
+
logger.info('[Capture] Polling stopped: sessionId=' + sessionId);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* 获取当前抓包会话已采集的请求数量
|
|
312
|
+
*/
|
|
313
|
+
getCapturedCount(sessionId) {
|
|
314
|
+
const capture = this.captureSessions.get(sessionId);
|
|
315
|
+
return capture ? capture.capturedRequests.length : 0;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* 获取当前抓包会话已采集的所有请求
|
|
319
|
+
*/
|
|
320
|
+
getCapturedRequests(sessionId) {
|
|
321
|
+
const capture = this.captureSessions.get(sessionId);
|
|
322
|
+
return capture ? [...capture.capturedRequests] : [];
|
|
323
|
+
}
|
|
324
|
+
// ==================== 内部工具方法 ====================
|
|
325
|
+
/**
|
|
326
|
+
* 将 Whistle 原始请求转换为精简的 CapturedRequest
|
|
327
|
+
*/
|
|
328
|
+
convertRawRequest(raw) {
|
|
329
|
+
// base64 body 解码
|
|
330
|
+
let reqBody = null;
|
|
331
|
+
if (raw.req.base64) {
|
|
332
|
+
try {
|
|
333
|
+
reqBody = Buffer.from(raw.req.base64, 'base64').toString('utf-8');
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
reqBody = '[binary data]';
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
else if (raw.req.body) {
|
|
340
|
+
reqBody = raw.req.body;
|
|
341
|
+
}
|
|
342
|
+
let resBody = null;
|
|
343
|
+
if (raw.res.base64) {
|
|
344
|
+
try {
|
|
345
|
+
resBody = Buffer.from(raw.res.base64, 'base64').toString('utf-8');
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
resBody = '[binary data]';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
else if (raw.res.body) {
|
|
352
|
+
resBody = raw.res.body;
|
|
353
|
+
}
|
|
354
|
+
return {
|
|
355
|
+
id: raw.id,
|
|
356
|
+
url: raw.url,
|
|
357
|
+
method: raw.req.method,
|
|
358
|
+
statusCode: raw.res.statusCode ?? null,
|
|
359
|
+
reqHeaders: raw.req.headers || {},
|
|
360
|
+
resHeaders: raw.res.headers || {},
|
|
361
|
+
reqBody,
|
|
362
|
+
resBody,
|
|
363
|
+
clientIp: raw.req.ip || null,
|
|
364
|
+
startTime: raw.startTime,
|
|
365
|
+
endTime: raw.endTime,
|
|
366
|
+
duration: raw.endTime - raw.startTime,
|
|
367
|
+
dnsTime: raw.dnsTime,
|
|
368
|
+
requestTime: raw.requestTime,
|
|
369
|
+
responseTime: raw.responseTime,
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
export const whistleManager = new WhistleRuleManager();
|
|
374
|
+
//# sourceMappingURL=whistle-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"whistle-manager.js","sourceRoot":"","sources":["../../src/proxy/whistle-manager.ts"],"names":[],"mappings":"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"}
|
package/dist/scrcpy/server.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/scrcpy/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAaH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAWxD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../src/scrcpy/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgDG;AAaH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAWxD,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,kBAAkB;IACjC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAgGD,qBAAa,YAAY;IACvB,OAAO,CAAC,UAAU,CAAa;IAC/B,OAAO,CAAC,GAAG,CAAkB;IAC7B,OAAO,CAAC,MAAM,CAAe;IAC7B,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,eAAe,CAAuB;IAC9C,OAAO,CAAC,kBAAkB,CAA+B;IACzD,OAAO,CAAC,kBAAkB,CAAM;IAChC,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,OAAO,CAAS;IACxB,iCAAiC;IACjC,OAAO,CAAC,QAAQ,CAAiD;gBAErD,MAAM,EAAE,YAAY;YAmBlB,iBAAiB;YA6CjB,YAAY;YA2BZ,cAAc;YAuBd,MAAM;YAqCN,gBAAgB;YA2BhB,WAAW;IAwEzB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,oBAAoB;IA4B5B,OAAO,CAAC,sBAAsB;YAwBhB,iBAAiB;IAa/B,wDAAwD;YAC1C,cAAc;IAqB5B,OAAO,CAAC,oBAAoB;IAyd5B,OAAO,CAAC,qBAAqB;IAYvB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAoBtB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CA6B7B"}
|
package/dist/scrcpy/server.js
CHANGED
|
@@ -108,6 +108,27 @@ function buildVideoFrameBuffer(payload, isKeyFrame, isConfiguration, timestampMs
|
|
|
108
108
|
buf.set(payload, 9);
|
|
109
109
|
return buf;
|
|
110
110
|
}
|
|
111
|
+
function isH264IdrFrame(payload) {
|
|
112
|
+
for (let i = 0; i + 4 < payload.byteLength; i++) {
|
|
113
|
+
let nalOffset = -1;
|
|
114
|
+
if (payload[i] === 0 && payload[i + 1] === 0 && payload[i + 2] === 1) {
|
|
115
|
+
nalOffset = i + 3;
|
|
116
|
+
}
|
|
117
|
+
else if (payload[i] === 0 &&
|
|
118
|
+
payload[i + 1] === 0 &&
|
|
119
|
+
payload[i + 2] === 0 &&
|
|
120
|
+
payload[i + 3] === 1) {
|
|
121
|
+
nalOffset = i + 4;
|
|
122
|
+
}
|
|
123
|
+
if (nalOffset >= 0 && nalOffset < payload.byteLength) {
|
|
124
|
+
const nalType = payload[nalOffset] & 0x1f;
|
|
125
|
+
if (nalType === 5)
|
|
126
|
+
return true;
|
|
127
|
+
i = nalOffset;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
111
132
|
export class ScrcpyServer {
|
|
112
133
|
httpServer;
|
|
113
134
|
wss;
|
|
@@ -216,6 +237,23 @@ export class ScrcpyServer {
|
|
|
216
237
|
this.currentDeviceId = devices[0].serial;
|
|
217
238
|
return new Adb(await withTimeout(client.createTransport(devices[0]), this.config.adbConnectTimeoutMs, `Timed out connecting to ${devices[0].serial} via ADB`));
|
|
218
239
|
}
|
|
240
|
+
async wakeDeviceScreen(deviceId) {
|
|
241
|
+
const targetDeviceId = deviceId || this.currentDeviceId;
|
|
242
|
+
const serialArg = targetDeviceId ? `-s ${JSON.stringify(targetDeviceId)} ` : '';
|
|
243
|
+
const commands = [
|
|
244
|
+
'input keyevent KEYCODE_WAKEUP',
|
|
245
|
+
'wm dismiss-keyguard',
|
|
246
|
+
'input keyevent 82',
|
|
247
|
+
];
|
|
248
|
+
for (const command of commands) {
|
|
249
|
+
try {
|
|
250
|
+
await withTimeout(promiseExec(`adb ${serialArg}shell ${JSON.stringify(command)}`), 3000, `Timed out waking Android screen with: ${command}`);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
logger.warn(`wake screen command failed (${command}): ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}
|
|
219
257
|
// ============================================================
|
|
220
258
|
// Scrcpy lifecycle
|
|
221
259
|
// ============================================================
|
|
@@ -238,7 +276,7 @@ export class ScrcpyServer {
|
|
|
238
276
|
sendFrameMeta: true,
|
|
239
277
|
videoBitRate: options.videoBitRate ?? this.config.videoBitRate,
|
|
240
278
|
videoCodec: 'h264',
|
|
241
|
-
maxFps:
|
|
279
|
+
maxFps: 30,
|
|
242
280
|
});
|
|
243
281
|
onProgress?.('starting-service');
|
|
244
282
|
const startPromise = AdbScrcpyClient.start(adb, DefaultServerPath, scrcpyOptions);
|
|
@@ -414,6 +452,7 @@ export class ScrcpyServer {
|
|
|
414
452
|
wsSend(ws, 'error', { message: 'No device found' });
|
|
415
453
|
return;
|
|
416
454
|
}
|
|
455
|
+
await this.wakeDeviceScreen(this.currentDeviceId || requestedDeviceId);
|
|
417
456
|
state.scrcpyClient = await this.startScrcpy(state.adb, options || {}, emitStatus);
|
|
418
457
|
logger.info(`Scrcpy started for device=${this.currentDeviceId},client=${state.id}`);
|
|
419
458
|
const videoStreamRaw = state.scrcpyClient?.videoStream;
|
|
@@ -469,34 +508,18 @@ export class ScrcpyServer {
|
|
|
469
508
|
const reader = stream.getReader();
|
|
470
509
|
state.streamReader = reader;
|
|
471
510
|
const processStream = async () => {
|
|
472
|
-
// 跳过开头的黑帧:直到收到一个数据量足够的关键帧才开始转发
|
|
473
|
-
// 黑屏帧通常极小(<1KB),正常关键帧在 1080p 下至少 30KB+
|
|
474
|
-
let gotValidKeyFrame = false;
|
|
475
511
|
try {
|
|
476
512
|
while (true) {
|
|
477
513
|
const { done, value } = await reader.read();
|
|
478
514
|
if (done)
|
|
479
515
|
break;
|
|
516
|
+
const data = value.data;
|
|
480
517
|
const frameType = value.type || 'data';
|
|
481
|
-
const isKey = !!(value.keyframe ?? value.keyFrame);
|
|
518
|
+
const isKey = !!(value.keyframe ?? value.keyFrame) || isH264IdrFrame(data);
|
|
482
519
|
const isConfig = frameType === 'configuration';
|
|
483
|
-
// 还没拿到有效关键帧时,只保留 config 帧,跳过 P 帧和小关键帧
|
|
484
|
-
if (!gotValidKeyFrame) {
|
|
485
|
-
if (isConfig) {
|
|
486
|
-
// config 帧总是转发(SPS/PPS),jmuxer 需要它
|
|
487
|
-
}
|
|
488
|
-
else if (isKey && value.data.byteLength > 10000) {
|
|
489
|
-
// 数据量足够的关键帧,说明屏幕已亮
|
|
490
|
-
gotValidKeyFrame = true;
|
|
491
|
-
}
|
|
492
|
-
else {
|
|
493
|
-
// P 帧或黑屏关键帧,跳过
|
|
494
|
-
continue;
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
520
|
if (ws.readyState !== WebSocket.OPEN)
|
|
498
521
|
break;
|
|
499
|
-
ws.send(buildVideoFrameBuffer(
|
|
522
|
+
ws.send(buildVideoFrameBuffer(data, isKey, isConfig, Date.now()), { binary: true });
|
|
500
523
|
}
|
|
501
524
|
}
|
|
502
525
|
catch (error) {
|