@jshookmcp/jshook 0.2.7 → 0.2.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/README.md +36 -5
- package/README.zh.md +36 -5
- package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-BNk-EoBt.mjs} +3 -3
- package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-Cq8q01kp.mjs} +5 -5
- package/dist/ConsoleMonitor-CPVQW1Y-.mjs +2201 -0
- package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-BNPxu0RH.mjs} +1 -1
- package/dist/DetailedDataManager-BQQcxh64.mjs +217 -0
- package/dist/EventBus-DgPmwpeu.mjs +141 -0
- package/dist/EvidenceGraphBridge-SFesNera.mjs +153 -0
- package/dist/{ExtensionManager-CZ6IveoV.mjs → ExtensionManager-CWYgw0YW.mjs} +13 -6
- package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-gzWtkKuf.mjs} +1 -1
- package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-B9gZCdFP.mjs} +3 -3
- package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-BLDH0dCv.mjs} +4 -4
- package/dist/HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs +639 -0
- package/dist/InstrumentationSession-CvPC7Jwy.mjs +244 -0
- package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CbVdCIJF.mjs} +3 -3
- package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-BsDZbLYm.mjs} +81 -78
- package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-Bcpml6II.mjs} +44 -18
- package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-dZtA1ZGn.mjs} +14 -53
- package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-B-FjA2mJ.mjs} +1 -1
- package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-D1lzJ_VG.mjs} +2 -2
- package/dist/PageController-Bqm2kZ_X.mjs +417 -0
- package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-BOhyVsjx.mjs} +4 -4
- package/dist/PrerequisiteError-Dl33Svkz.mjs +20 -0
- package/dist/ResponseBuilder-D3iFYx2N.mjs +143 -0
- package/dist/ReverseEvidenceGraph-Dlsk94LC.mjs +269 -0
- package/dist/ScriptManager-aHHq0X7U.mjs +3000 -0
- package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-CqdIFlQl.mjs} +2 -2
- package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-DhFaPvRO.mjs} +3 -3
- package/dist/ToolCatalog-C0JGZoOm.mjs +582 -0
- package/dist/ToolError-jh9whhMd.mjs +15 -0
- package/dist/ToolProbe-oC7aPrkv.mjs +45 -0
- package/dist/ToolRegistry-BjaF4oNz.mjs +131 -0
- package/dist/ToolRouter.policy-BWV67ZK-.mjs +304 -0
- package/dist/TraceRecorder-DgxyVbdQ.mjs +519 -0
- package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-CePkipZY.mjs} +1 -1
- package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-BvKs-gxc.mjs} +2 -2
- package/dist/WorkflowEngine-CuvkZtWu.mjs +598 -0
- package/dist/analysis-CL9uACt9.mjs +463 -0
- package/dist/antidebug-CqDTB_uk.mjs +1081 -0
- package/dist/artifactRetention-CFEprwPw.mjs +591 -0
- package/dist/artifacts-Bk2-_uPq.mjs +59 -0
- package/dist/betterSqlite3-0pqusHHH.mjs +74 -0
- package/dist/binary-instrument-CXfpx6fT.mjs +979 -0
- package/dist/bind-helpers-xFfRF-qm.mjs +22 -0
- package/dist/boringssl-inspector-BH2D3VKc.mjs +180 -0
- package/dist/browser-BpOr5PEx.mjs +4082 -0
- package/dist/concurrency-Bt0yv1kJ.mjs +41 -0
- package/dist/{constants-CCvsN80K.mjs → constants-B0OANIBL.mjs} +88 -46
- package/dist/coordination-qUbyF8KU.mjs +259 -0
- package/dist/debugger-gnKxRSN0.mjs +1271 -0
- package/dist/definitions-6M-eejaT.mjs +53 -0
- package/dist/definitions-B18eyf0B.mjs +18 -0
- package/dist/definitions-B3QdlrHv.mjs +34 -0
- package/dist/definitions-B4rAvHNZ.mjs +63 -0
- package/dist/definitions-BB_4jnmy.mjs +37 -0
- package/dist/definitions-BMfYXoNC.mjs +43 -0
- package/dist/definitions-Beid2EB3.mjs +27 -0
- package/dist/definitions-C1UvM5Iy.mjs +126 -0
- package/dist/definitions-CXEI7QC72.mjs +216 -0
- package/dist/definitions-C_4r7Fo-2.mjs +14 -0
- package/dist/definitions-CkFDALoa.mjs +26 -0
- package/dist/definitions-Cke7zEb8.mjs +94 -0
- package/dist/definitions-ClJLzsJQ.mjs +25 -0
- package/dist/definitions-Cq-zroAU.mjs +28 -0
- package/dist/definitions-Cy3Sl6gV.mjs +34 -0
- package/dist/definitions-D3VsGcvz.mjs +47 -0
- package/dist/definitions-DVGfrn7y.mjs +96 -0
- package/dist/definitions-LKpC3-nL.mjs +9 -0
- package/dist/definitions-bAhHQJq9.mjs +359 -0
- package/dist/encoding-Bvz5jLRv.mjs +1065 -0
- package/dist/evidence-graph-bridge-C_fv9PuC.mjs +135 -0
- package/dist/{factory-CibqTNC8.mjs → factory-DxlGh9Xf.mjs} +37 -52
- package/dist/graphql-DYWzJ29s.mjs +1026 -0
- package/dist/handlers-9sAbfIg-.mjs +2552 -0
- package/dist/handlers-Bl8zkwz1.mjs +2716 -0
- package/dist/handlers-C67ktuRN.mjs +710 -0
- package/dist/handlers-C87g8oCe.mjs +276 -0
- package/dist/handlers-CTsDAO6p.mjs +681 -0
- package/dist/handlers-Cgyg6c0U.mjs +645 -0
- package/dist/handlers-D6j6yka7.mjs +2124 -0
- package/dist/handlers-DdFzXLvF.mjs +446 -0
- package/dist/handlers-DeLOCd5m.mjs +799 -0
- package/dist/handlers-DlCJN4Td.mjs +757 -0
- package/dist/handlers-DxGIq15_2.mjs +917 -0
- package/dist/handlers-U6L4xhuF.mjs +585 -0
- package/dist/handlers-tB9Mp9ZK.mjs +84 -0
- package/dist/handlers-tiy7EIBp.mjs +572 -0
- package/dist/handlers.impl-DS0d9fUw.mjs +761 -0
- package/dist/hooks-CzCWByww.mjs +898 -0
- package/dist/index.mjs +384 -155
- package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
- package/dist/maintenance-P7ePRXQC.mjs +830 -0
- package/dist/manifest-2ToTpjv8.mjs +106 -0
- package/dist/manifest-3g71z6Bg.mjs +79 -0
- package/dist/manifest-82baTv4U.mjs +45 -0
- package/dist/manifest-B3QVVeBS.mjs +82 -0
- package/dist/manifest-BB2J8IMJ.mjs +149 -0
- package/dist/manifest-BKbgbSiY.mjs +60 -0
- package/dist/manifest-Bcf-TJzH.mjs +848 -0
- package/dist/manifest-BmtZzQiQ2.mjs +45 -0
- package/dist/manifest-Bnd7kqEY.mjs +55 -0
- package/dist/manifest-BqQX6OQC2.mjs +65 -0
- package/dist/manifest-BqrQ4Tpj.mjs +81 -0
- package/dist/manifest-Br4RPFt5.mjs +370 -0
- package/dist/manifest-C5qDjysN.mjs +107 -0
- package/dist/manifest-C9RT5nk32.mjs +34 -0
- package/dist/manifest-CAhOuvSl.mjs +204 -0
- package/dist/manifest-CBYWCUBJ.mjs +51 -0
- package/dist/manifest-CFADCRa1.mjs +37 -0
- package/dist/manifest-CQVhavRF.mjs +114 -0
- package/dist/manifest-CT7zZBV1.mjs +48 -0
- package/dist/manifest-CV12bcrF.mjs +121 -0
- package/dist/manifest-CXsRWjjI.mjs +224 -0
- package/dist/manifest-CZLUCfG02.mjs +95 -0
- package/dist/manifest-D6phHKFd.mjs +131 -0
- package/dist/manifest-DCyjf4n2.mjs +294 -0
- package/dist/manifest-DHsnKgP6.mjs +60 -0
- package/dist/manifest-Df_dliIe.mjs +55 -0
- package/dist/manifest-Dh8WBmEW.mjs +129 -0
- package/dist/manifest-DhKRAT8_.mjs +92 -0
- package/dist/manifest-DlpTj4ic2.mjs +193 -0
- package/dist/manifest-DrbmZcFl2.mjs +253 -0
- package/dist/manifest-DuwHjUa5.mjs +70 -0
- package/dist/manifest-DzwvxPJX.mjs +38 -0
- package/dist/manifest-NXctwWQq.mjs +68 -0
- package/dist/manifest-Sc_0JQ13.mjs +418 -0
- package/dist/manifest-gZ4s_UtG.mjs +96 -0
- package/dist/manifest-qSleDqdO.mjs +1023 -0
- package/dist/modules-C184v-S9.mjs +11365 -0
- package/dist/mojo-ipc-B_H61Afw.mjs +525 -0
- package/dist/network-671Cw6hV.mjs +3346 -0
- package/dist/{artifacts-BbdOMET5.mjs → outputPaths-B1uGmrWZ.mjs} +219 -212
- package/dist/parse-args-BlRjqlkL.mjs +39 -0
- package/dist/platform-WmNn8Sxb.mjs +2070 -0
- package/dist/process-QcbIy5Zq.mjs +1401 -0
- package/dist/proxy-DqNs0bAd.mjs +170 -0
- package/dist/registry-D-6e18lB.mjs +34 -0
- package/dist/response-BQVP-xUn.mjs +28 -0
- package/dist/server/plugin-api.mjs +2 -2
- package/dist/shared-state-board-DV-dpHFJ.mjs +586 -0
- package/dist/sourcemap-Dq8ez8vS.mjs +650 -0
- package/dist/ssrf-policy-ZaUfvhq7.mjs +166 -0
- package/dist/streaming-BUQ0VJsg.mjs +725 -0
- package/dist/tool-builder-DCbIC5Eo.mjs +186 -0
- package/dist/transform-CiYJfNX0.mjs +1007 -0
- package/dist/types-Bx92KJfT.mjs +4 -0
- package/dist/wasm-DQTnHDs4.mjs +531 -0
- package/dist/workflow-f3xJOcjx.mjs +725 -0
- package/package.json +48 -78
- package/dist/ExtensionManager-DqUSOamB.mjs +0 -2
- package/dist/ToolCatalog-CnwmMIw3.mjs +0 -61483
- package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-CDe5WPSV.mjs} +0 -0
- package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-Bo4T3bz8.mjs} +0 -0
- package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-CwVLVdDM.mjs} +0 -0
- package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-DVkj9kpI.mjs} +0 -0
- package/dist/{types-BBjOqye-.mjs → types-CPhOReNX.mjs} +1 -1
|
@@ -0,0 +1,2201 @@
|
|
|
1
|
+
import { t as __exportAll } from "./chunk-CjcI7cDX.mjs";
|
|
2
|
+
import { t as logger } from "./logger-Dh_xb7_2.mjs";
|
|
3
|
+
import { t as PrerequisiteError } from "./PrerequisiteError-Dl33Svkz.mjs";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
5
|
+
//#region src/modules/monitor/NetworkMonitor.interceptors.ts
|
|
6
|
+
function buildXHRInterceptorCode(maxRecords) {
|
|
7
|
+
return `
|
|
8
|
+
(function() {
|
|
9
|
+
if (window.__xhrInterceptorInstalled) {
|
|
10
|
+
console.log('[XHRInterceptor] Already installed');
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
13
|
+
window.__xhrInterceptorInstalled = true;
|
|
14
|
+
|
|
15
|
+
const originalXHR = window.__originalXMLHttpRequestForHook || window.XMLHttpRequest;
|
|
16
|
+
window.__originalXMLHttpRequestForHook = originalXHR;
|
|
17
|
+
if (!window.__xhrRequests) {
|
|
18
|
+
window.__xhrRequests = [];
|
|
19
|
+
}
|
|
20
|
+
const xhrRequests = window.__xhrRequests;
|
|
21
|
+
|
|
22
|
+
window.XMLHttpRequest = function() {
|
|
23
|
+
const xhr = new originalXHR();
|
|
24
|
+
const requestInfo = {
|
|
25
|
+
method: '',
|
|
26
|
+
url: '',
|
|
27
|
+
requestHeaders: {},
|
|
28
|
+
responseHeaders: {},
|
|
29
|
+
status: 0,
|
|
30
|
+
response: null,
|
|
31
|
+
timestamp: Date.now(),
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const originalOpen = xhr.open;
|
|
35
|
+
xhr.open = function(method, url, ...args) {
|
|
36
|
+
requestInfo.method = method;
|
|
37
|
+
requestInfo.url = url;
|
|
38
|
+
console.log('[XHRInterceptor] XHR opened:', method, url);
|
|
39
|
+
return originalOpen.call(xhr, method, url, ...args);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const originalSetRequestHeader = xhr.setRequestHeader;
|
|
43
|
+
xhr.setRequestHeader = function(header, value) {
|
|
44
|
+
requestInfo.requestHeaders[header] = value;
|
|
45
|
+
return originalSetRequestHeader.call(xhr, header, value);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const originalSend = xhr.send;
|
|
49
|
+
xhr.send = function(body) {
|
|
50
|
+
let bodySize = 0;
|
|
51
|
+
try {
|
|
52
|
+
if (typeof body === 'string') bodySize = body.length;
|
|
53
|
+
else if (body && typeof body.byteLength === 'number') bodySize = body.byteLength;
|
|
54
|
+
else if (body && typeof body.size === 'number') bodySize = body.size;
|
|
55
|
+
} catch {}
|
|
56
|
+
console.log('[XHRInterceptor] XHR sent:', requestInfo.url, 'BodySize:', bodySize);
|
|
57
|
+
|
|
58
|
+
xhr.addEventListener('load', function() {
|
|
59
|
+
requestInfo.status = xhr.status;
|
|
60
|
+
requestInfo.response = xhr.response;
|
|
61
|
+
requestInfo.responseHeaders = xhr.getAllResponseHeaders();
|
|
62
|
+
|
|
63
|
+
xhrRequests.push(requestInfo);
|
|
64
|
+
if (xhrRequests.length > ${maxRecords}) {
|
|
65
|
+
xhrRequests.splice(0, xhrRequests.length - ${maxRecords});
|
|
66
|
+
}
|
|
67
|
+
console.log('[XHRInterceptor] XHR completed:', requestInfo.url, 'Status:', xhr.status);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
return originalSend.call(xhr, body);
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
return xhr;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
window.__getXHRRequests = function() {
|
|
77
|
+
return window.__xhrRequests || [];
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
console.log('[XHRInterceptor] XHR interceptor installed');
|
|
81
|
+
})();
|
|
82
|
+
`;
|
|
83
|
+
}
|
|
84
|
+
function buildFetchInterceptorCode(maxRecords) {
|
|
85
|
+
return `
|
|
86
|
+
(function() {
|
|
87
|
+
if (window.__fetchInterceptorInstalled) {
|
|
88
|
+
console.log('[FetchInterceptor] Already installed');
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
window.__fetchInterceptorInstalled = true;
|
|
92
|
+
|
|
93
|
+
const originalFetch = window.__originalFetchForHook || window.fetch;
|
|
94
|
+
window.__originalFetchForHook = originalFetch;
|
|
95
|
+
if (!window.__fetchRequests) {
|
|
96
|
+
window.__fetchRequests = [];
|
|
97
|
+
}
|
|
98
|
+
const fetchRequests = window.__fetchRequests;
|
|
99
|
+
|
|
100
|
+
window.fetch = function(url, options = {}) {
|
|
101
|
+
const requestInfo = {
|
|
102
|
+
url: typeof url === 'string' ? url : url.url,
|
|
103
|
+
method: options.method || 'GET',
|
|
104
|
+
headers: options.headers || {},
|
|
105
|
+
body: options.body,
|
|
106
|
+
timestamp: Date.now(),
|
|
107
|
+
response: null,
|
|
108
|
+
status: 0,
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
console.log('[FetchInterceptor] Fetch called:', requestInfo.method, requestInfo.url);
|
|
112
|
+
|
|
113
|
+
return originalFetch.call(window, url, options).then(async (response) => {
|
|
114
|
+
requestInfo.status = response.status;
|
|
115
|
+
|
|
116
|
+
const clonedResponse = response.clone();
|
|
117
|
+
try {
|
|
118
|
+
requestInfo.response = await clonedResponse.text();
|
|
119
|
+
} catch (e) {
|
|
120
|
+
requestInfo.response = '[Unable to read response]';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
fetchRequests.push(requestInfo);
|
|
124
|
+
if (fetchRequests.length > ${maxRecords}) {
|
|
125
|
+
fetchRequests.splice(0, fetchRequests.length - ${maxRecords});
|
|
126
|
+
}
|
|
127
|
+
// Auto-persist compact summary to localStorage so data survives context compression
|
|
128
|
+
try {
|
|
129
|
+
const summary = { url: requestInfo.url, method: requestInfo.method, status: requestInfo.status, ts: requestInfo.timestamp };
|
|
130
|
+
const prev = JSON.parse(localStorage.getItem('__capturedAPIs') || '[]');
|
|
131
|
+
prev.push(summary);
|
|
132
|
+
if (prev.length > 500) prev.splice(0, prev.length - 500);
|
|
133
|
+
localStorage.setItem('__capturedAPIs', JSON.stringify(prev));
|
|
134
|
+
} catch(e) {
|
|
135
|
+
// best-effort persistence only; ignore quota or serialization failures
|
|
136
|
+
}
|
|
137
|
+
console.log('[FetchInterceptor] Fetch completed:', requestInfo.url, 'Status:', response.status);
|
|
138
|
+
|
|
139
|
+
return response;
|
|
140
|
+
}).catch((error) => {
|
|
141
|
+
console.error('[FetchInterceptor] Fetch failed:', requestInfo.url, error);
|
|
142
|
+
throw error;
|
|
143
|
+
});
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
window.__getFetchRequests = function() {
|
|
147
|
+
return window.__fetchRequests || [];
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
console.log('[FetchInterceptor] Fetch interceptor installed');
|
|
151
|
+
})();
|
|
152
|
+
`;
|
|
153
|
+
}
|
|
154
|
+
const CLEAR_INJECTED_BUFFERS_EXPRESSION = `
|
|
155
|
+
(() => {
|
|
156
|
+
const xhrStore = Array.isArray(window.__xhrRequests)
|
|
157
|
+
? window.__xhrRequests
|
|
158
|
+
: (typeof window.__getXHRRequests === 'function' ? window.__getXHRRequests() : null);
|
|
159
|
+
const fetchStore = Array.isArray(window.__fetchRequests)
|
|
160
|
+
? window.__fetchRequests
|
|
161
|
+
: (typeof window.__getFetchRequests === 'function' ? window.__getFetchRequests() : null);
|
|
162
|
+
|
|
163
|
+
const xhrCleared = Array.isArray(xhrStore) ? xhrStore.length : 0;
|
|
164
|
+
const fetchCleared = Array.isArray(fetchStore) ? fetchStore.length : 0;
|
|
165
|
+
|
|
166
|
+
if (Array.isArray(xhrStore)) xhrStore.length = 0;
|
|
167
|
+
if (Array.isArray(fetchStore)) fetchStore.length = 0;
|
|
168
|
+
|
|
169
|
+
return { xhrCleared, fetchCleared };
|
|
170
|
+
})()
|
|
171
|
+
`;
|
|
172
|
+
const RESET_INJECTED_INTERCEPTORS_EXPRESSION = `
|
|
173
|
+
(() => {
|
|
174
|
+
let xhrReset = false;
|
|
175
|
+
let fetchReset = false;
|
|
176
|
+
|
|
177
|
+
if (window.__originalXMLHttpRequestForHook) {
|
|
178
|
+
window.XMLHttpRequest = window.__originalXMLHttpRequestForHook;
|
|
179
|
+
xhrReset = true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (window.__originalFetchForHook) {
|
|
183
|
+
window.fetch = window.__originalFetchForHook;
|
|
184
|
+
fetchReset = true;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (Array.isArray(window.__xhrRequests)) window.__xhrRequests.length = 0;
|
|
188
|
+
if (Array.isArray(window.__fetchRequests)) window.__fetchRequests.length = 0;
|
|
189
|
+
|
|
190
|
+
window.__xhrInterceptorInstalled = false;
|
|
191
|
+
window.__fetchInterceptorInstalled = false;
|
|
192
|
+
delete window.__getXHRRequests;
|
|
193
|
+
delete window.__getFetchRequests;
|
|
194
|
+
|
|
195
|
+
return { xhrReset, fetchReset };
|
|
196
|
+
})()
|
|
197
|
+
`;
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/modules/monitor/NetworkMonitor.impl.ts
|
|
200
|
+
const isObjectRecord = (value) => typeof value === "object" && value !== null;
|
|
201
|
+
const isRequestWillBeSentPayload = (value) => {
|
|
202
|
+
if (!isObjectRecord(value) || typeof value.requestId !== "string") return false;
|
|
203
|
+
if (!isObjectRecord(value.request)) return false;
|
|
204
|
+
if (typeof value.request.url !== "string" || typeof value.request.method !== "string") return false;
|
|
205
|
+
if (value.request.postData !== void 0 && typeof value.request.postData !== "string") return false;
|
|
206
|
+
return typeof value.timestamp === "number";
|
|
207
|
+
};
|
|
208
|
+
const isResponseReceivedPayload = (value) => {
|
|
209
|
+
if (!isObjectRecord(value) || typeof value.requestId !== "string") return false;
|
|
210
|
+
if (!isObjectRecord(value.response)) return false;
|
|
211
|
+
if (typeof value.response.url !== "string" || typeof value.response.status !== "number" || typeof value.response.statusText !== "string" || typeof value.response.mimeType !== "string") return false;
|
|
212
|
+
return typeof value.timestamp === "number";
|
|
213
|
+
};
|
|
214
|
+
const isLoadingFinishedPayload = (value) => isObjectRecord(value) && typeof value.requestId === "string";
|
|
215
|
+
const isResponseBodyPayload = (value) => isObjectRecord(value) && typeof value.body === "string" && typeof value.base64Encoded === "boolean";
|
|
216
|
+
const asStringRecord = (value) => isObjectRecord(value) ? value : {};
|
|
217
|
+
const toRuntimeEvaluateValue = (value) => {
|
|
218
|
+
if (!isObjectRecord(value)) return;
|
|
219
|
+
const runtimeResult = value.result;
|
|
220
|
+
if (!isObjectRecord(runtimeResult)) return;
|
|
221
|
+
return runtimeResult.value;
|
|
222
|
+
};
|
|
223
|
+
const toFiniteNumber = (value, fallback = 0) => typeof value === "number" && Number.isFinite(value) ? value : fallback;
|
|
224
|
+
const toBoolean = (value, fallback) => typeof value === "boolean" ? value : fallback;
|
|
225
|
+
var NetworkMonitor = class {
|
|
226
|
+
networkEnabled = false;
|
|
227
|
+
requests = /* @__PURE__ */ new Map();
|
|
228
|
+
responses = /* @__PURE__ */ new Map();
|
|
229
|
+
MAX_NETWORK_RECORDS = 500;
|
|
230
|
+
MAX_INJECTED_RECORDS = 500;
|
|
231
|
+
JS_RESPONSE_CONCURRENCY = 6;
|
|
232
|
+
/** LRU cache for response bodies, auto-captured on loadingFinished. */
|
|
233
|
+
responseBodyCache = /* @__PURE__ */ new Map();
|
|
234
|
+
MAX_BODY_CACHE_ENTRIES = 200;
|
|
235
|
+
networkListeners = {};
|
|
236
|
+
constructor(cdpSession) {
|
|
237
|
+
this.cdpSession = cdpSession;
|
|
238
|
+
this.cdpSession.on("disconnected", () => {
|
|
239
|
+
logger.warn("NetworkMonitor: CDP session disconnected");
|
|
240
|
+
this.networkEnabled = false;
|
|
241
|
+
this.networkListeners = {};
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
async enable() {
|
|
245
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
246
|
+
if (this.networkEnabled) {
|
|
247
|
+
logger.warn("Network monitoring already enabled");
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
try {
|
|
251
|
+
await this.cdpSession.send("Network.enable", {
|
|
252
|
+
maxTotalBufferSize: 1e7,
|
|
253
|
+
maxResourceBufferSize: 5e6,
|
|
254
|
+
maxPostDataSize: 65536
|
|
255
|
+
});
|
|
256
|
+
logger.info("Network domain enabled");
|
|
257
|
+
this.networkListeners.requestWillBeSent = (params) => {
|
|
258
|
+
if (!isRequestWillBeSentPayload(params)) {
|
|
259
|
+
logger.debug("Skipping malformed Network.requestWillBeSent payload");
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const request = {
|
|
263
|
+
requestId: params.requestId,
|
|
264
|
+
url: params.request.url,
|
|
265
|
+
method: params.request.method,
|
|
266
|
+
headers: asStringRecord(params.request.headers),
|
|
267
|
+
postData: params.request.postData,
|
|
268
|
+
timestamp: params.timestamp,
|
|
269
|
+
type: params.type,
|
|
270
|
+
initiator: params.initiator
|
|
271
|
+
};
|
|
272
|
+
this.requests.set(params.requestId, request);
|
|
273
|
+
if (this.requests.size > this.MAX_NETWORK_RECORDS) {
|
|
274
|
+
const firstKey = this.requests.keys().next().value;
|
|
275
|
+
if (firstKey) this.requests.delete(firstKey);
|
|
276
|
+
}
|
|
277
|
+
logger.debug(`Network request captured: ${params.request.method} ${params.request.url}`);
|
|
278
|
+
};
|
|
279
|
+
this.networkListeners.responseReceived = (params) => {
|
|
280
|
+
if (!isResponseReceivedPayload(params)) {
|
|
281
|
+
logger.debug("Skipping malformed Network.responseReceived payload");
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const response = {
|
|
285
|
+
requestId: params.requestId,
|
|
286
|
+
url: params.response.url,
|
|
287
|
+
status: params.response.status,
|
|
288
|
+
statusText: params.response.statusText,
|
|
289
|
+
headers: asStringRecord(params.response.headers),
|
|
290
|
+
mimeType: params.response.mimeType,
|
|
291
|
+
timestamp: params.timestamp,
|
|
292
|
+
fromCache: params.response.fromDiskCache || params.response.fromServiceWorker,
|
|
293
|
+
timing: params.response.timing
|
|
294
|
+
};
|
|
295
|
+
this.responses.set(params.requestId, response);
|
|
296
|
+
if (this.responses.size > this.MAX_NETWORK_RECORDS) {
|
|
297
|
+
const firstKey = this.responses.keys().next().value;
|
|
298
|
+
if (firstKey) this.responses.delete(firstKey);
|
|
299
|
+
}
|
|
300
|
+
logger.debug(`Network response captured: ${params.response.status} ${params.response.url}`);
|
|
301
|
+
};
|
|
302
|
+
this.networkListeners.loadingFinished = (params) => {
|
|
303
|
+
if (!isLoadingFinishedPayload(params)) {
|
|
304
|
+
logger.debug("Skipping malformed Network.loadingFinished payload");
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
logger.debug(`Network loading finished: ${params.requestId}`);
|
|
308
|
+
this.captureResponseBody(params.requestId).catch((err) => {
|
|
309
|
+
logger.debug(`[BodyCache] Auto-capture failed for ${params.requestId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
310
|
+
});
|
|
311
|
+
};
|
|
312
|
+
this.cdpSession.on("Network.requestWillBeSent", this.networkListeners.requestWillBeSent);
|
|
313
|
+
this.cdpSession.on("Network.responseReceived", this.networkListeners.responseReceived);
|
|
314
|
+
this.cdpSession.on("Network.loadingFinished", this.networkListeners.loadingFinished);
|
|
315
|
+
this.networkEnabled = true;
|
|
316
|
+
logger.info(" Network monitoring enabled successfully", {
|
|
317
|
+
requestListeners: !!this.networkListeners.requestWillBeSent,
|
|
318
|
+
responseListeners: !!this.networkListeners.responseReceived,
|
|
319
|
+
loadingListeners: !!this.networkListeners.loadingFinished
|
|
320
|
+
});
|
|
321
|
+
} catch (error) {
|
|
322
|
+
logger.error(" Failed to enable network monitoring:", error);
|
|
323
|
+
this.networkEnabled = false;
|
|
324
|
+
throw error;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Auto-capture a response body into the LRU cache.
|
|
329
|
+
* Called from the loadingFinished listener so bodies are available
|
|
330
|
+
* even after Chrome's internal buffer is reclaimed.
|
|
331
|
+
*/
|
|
332
|
+
async captureResponseBody(requestId) {
|
|
333
|
+
if (this.responseBodyCache.has(requestId)) return;
|
|
334
|
+
const response = this.responses.get(requestId);
|
|
335
|
+
if (!response) return;
|
|
336
|
+
if (response.fromCache) return;
|
|
337
|
+
try {
|
|
338
|
+
const rawResult = await this.cdpSession.send("Network.getResponseBody", { requestId });
|
|
339
|
+
if (!isResponseBodyPayload(rawResult)) return;
|
|
340
|
+
if (rawResult.body.length > 1048576) {
|
|
341
|
+
logger.debug(`[BodyCache] Skipping oversized body for ${requestId} (${rawResult.body.length} chars)`);
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
344
|
+
if (this.responseBodyCache.size >= this.MAX_BODY_CACHE_ENTRIES) {
|
|
345
|
+
const oldestKey = this.responseBodyCache.keys().next().value;
|
|
346
|
+
if (oldestKey) this.responseBodyCache.delete(oldestKey);
|
|
347
|
+
}
|
|
348
|
+
this.responseBodyCache.set(requestId, {
|
|
349
|
+
body: rawResult.body,
|
|
350
|
+
base64Encoded: rawResult.base64Encoded
|
|
351
|
+
});
|
|
352
|
+
logger.debug(`[BodyCache] Cached body for ${requestId} (${rawResult.body.length} chars, url=${response.url})`);
|
|
353
|
+
} catch (err) {
|
|
354
|
+
logger.debug(`[BodyCache] Could not capture body for ${requestId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
async disable() {
|
|
358
|
+
if (!this.networkEnabled) return;
|
|
359
|
+
if (this.networkListeners.requestWillBeSent) this.cdpSession.off("Network.requestWillBeSent", this.networkListeners.requestWillBeSent);
|
|
360
|
+
if (this.networkListeners.responseReceived) this.cdpSession.off("Network.responseReceived", this.networkListeners.responseReceived);
|
|
361
|
+
if (this.networkListeners.loadingFinished) this.cdpSession.off("Network.loadingFinished", this.networkListeners.loadingFinished);
|
|
362
|
+
try {
|
|
363
|
+
await this.cdpSession.send("Network.disable");
|
|
364
|
+
} catch (error) {
|
|
365
|
+
logger.warn("Failed to disable Network domain:", error);
|
|
366
|
+
}
|
|
367
|
+
this.networkListeners = {};
|
|
368
|
+
this.networkEnabled = false;
|
|
369
|
+
logger.info("Network monitoring disabled");
|
|
370
|
+
}
|
|
371
|
+
isEnabled() {
|
|
372
|
+
return this.networkEnabled;
|
|
373
|
+
}
|
|
374
|
+
getStatus() {
|
|
375
|
+
return {
|
|
376
|
+
enabled: this.networkEnabled,
|
|
377
|
+
requestCount: this.requests.size,
|
|
378
|
+
responseCount: this.responses.size,
|
|
379
|
+
listenerCount: Object.keys(this.networkListeners).filter((key) => this.networkListeners[key] !== void 0).length,
|
|
380
|
+
cdpSessionActive: true
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
getRequests(filter) {
|
|
384
|
+
let requests = Array.from(this.requests.values());
|
|
385
|
+
if (filter?.url) requests = requests.filter((req) => req.url.includes(filter.url));
|
|
386
|
+
if (filter?.method) requests = requests.filter((req) => req.method === filter.method);
|
|
387
|
+
if (filter?.limit) requests = requests.slice(-filter.limit);
|
|
388
|
+
return requests;
|
|
389
|
+
}
|
|
390
|
+
getResponses(filter) {
|
|
391
|
+
let responses = Array.from(this.responses.values());
|
|
392
|
+
if (filter?.url) responses = responses.filter((res) => res.url.includes(filter.url));
|
|
393
|
+
if (filter?.status) responses = responses.filter((res) => res.status === filter.status);
|
|
394
|
+
if (filter?.limit) responses = responses.slice(-filter.limit);
|
|
395
|
+
return responses;
|
|
396
|
+
}
|
|
397
|
+
getActivity(requestId) {
|
|
398
|
+
return {
|
|
399
|
+
request: this.requests.get(requestId),
|
|
400
|
+
response: this.responses.get(requestId)
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
async getResponseBody(requestId) {
|
|
404
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
405
|
+
if (!this.networkEnabled) {
|
|
406
|
+
logger.error("Network monitoring is not enabled. Call enable() with enableNetwork: true first.");
|
|
407
|
+
return null;
|
|
408
|
+
}
|
|
409
|
+
const cached = this.responseBodyCache.get(requestId);
|
|
410
|
+
if (cached) {
|
|
411
|
+
this.responseBodyCache.delete(requestId);
|
|
412
|
+
this.responseBodyCache.set(requestId, cached);
|
|
413
|
+
logger.debug(`[BodyCache] Cache hit for ${requestId}`);
|
|
414
|
+
return cached;
|
|
415
|
+
}
|
|
416
|
+
const request = this.requests.get(requestId);
|
|
417
|
+
const response = this.responses.get(requestId);
|
|
418
|
+
if (!request) {
|
|
419
|
+
logger.error(`Request not found: ${requestId}. Make sure network monitoring was enabled before the request.`);
|
|
420
|
+
return null;
|
|
421
|
+
}
|
|
422
|
+
if (!response) {
|
|
423
|
+
logger.warn(`Response not yet received for request: ${requestId}. The request may still be pending.`);
|
|
424
|
+
return null;
|
|
425
|
+
}
|
|
426
|
+
try {
|
|
427
|
+
const rawResult = await this.cdpSession.send("Network.getResponseBody", { requestId });
|
|
428
|
+
if (!isResponseBodyPayload(rawResult)) {
|
|
429
|
+
logger.error(`Unexpected response body payload for ${requestId}`);
|
|
430
|
+
return null;
|
|
431
|
+
}
|
|
432
|
+
logger.info(`Response body retrieved for request: ${requestId}`, {
|
|
433
|
+
url: response.url,
|
|
434
|
+
status: response.status,
|
|
435
|
+
size: rawResult.body.length,
|
|
436
|
+
base64: rawResult.base64Encoded
|
|
437
|
+
});
|
|
438
|
+
return {
|
|
439
|
+
body: rawResult.body,
|
|
440
|
+
base64Encoded: rawResult.base64Encoded
|
|
441
|
+
};
|
|
442
|
+
} catch (error) {
|
|
443
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
444
|
+
logger.error(`Failed to get response body for ${requestId}:`, {
|
|
445
|
+
url: response.url,
|
|
446
|
+
status: response.status,
|
|
447
|
+
error: errorMessage,
|
|
448
|
+
hint: "The response body may not be available for this request type (e.g., cached, redirected, or failed requests)"
|
|
449
|
+
});
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
async getAllJavaScriptResponses() {
|
|
454
|
+
const candidates = Array.from(this.responses.entries()).filter(([, response]) => {
|
|
455
|
+
return response.mimeType.includes("javascript") || response.url.endsWith(".js") || response.url.includes(".js?");
|
|
456
|
+
});
|
|
457
|
+
const jsResponses = [];
|
|
458
|
+
for (let i = 0; i < candidates.length; i += this.JS_RESPONSE_CONCURRENCY) {
|
|
459
|
+
const batch = candidates.slice(i, i + this.JS_RESPONSE_CONCURRENCY);
|
|
460
|
+
const batchResults = await Promise.all(batch.map(async ([requestId, response]) => {
|
|
461
|
+
const bodyResult = await this.getResponseBody(requestId);
|
|
462
|
+
if (!bodyResult) return null;
|
|
463
|
+
const content = bodyResult.base64Encoded ? Buffer.from(bodyResult.body, "base64").toString("utf-8") : bodyResult.body;
|
|
464
|
+
return {
|
|
465
|
+
url: response.url,
|
|
466
|
+
content,
|
|
467
|
+
size: content.length,
|
|
468
|
+
requestId
|
|
469
|
+
};
|
|
470
|
+
}));
|
|
471
|
+
jsResponses.push(...batchResults.filter((value) => value !== null));
|
|
472
|
+
}
|
|
473
|
+
logger.info(`Collected ${jsResponses.length} JavaScript responses`);
|
|
474
|
+
return jsResponses;
|
|
475
|
+
}
|
|
476
|
+
clearRecords() {
|
|
477
|
+
this.requests.clear();
|
|
478
|
+
this.responses.clear();
|
|
479
|
+
this.responseBodyCache.clear();
|
|
480
|
+
logger.info("Network records cleared");
|
|
481
|
+
}
|
|
482
|
+
getStats() {
|
|
483
|
+
const byMethod = {};
|
|
484
|
+
const byStatus = {};
|
|
485
|
+
const byType = {};
|
|
486
|
+
for (const request of this.requests.values()) {
|
|
487
|
+
byMethod[request.method] = (byMethod[request.method] || 0) + 1;
|
|
488
|
+
if (request.type) byType[request.type] = (byType[request.type] || 0) + 1;
|
|
489
|
+
}
|
|
490
|
+
for (const response of this.responses.values()) byStatus[response.status] = (byStatus[response.status] || 0) + 1;
|
|
491
|
+
return {
|
|
492
|
+
totalRequests: this.requests.size,
|
|
493
|
+
totalResponses: this.responses.size,
|
|
494
|
+
byMethod,
|
|
495
|
+
byStatus,
|
|
496
|
+
byType
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
async injectXHRInterceptor(options) {
|
|
500
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
501
|
+
const interceptorCode = buildXHRInterceptorCode(this.MAX_INJECTED_RECORDS);
|
|
502
|
+
if (options?.persistent) {
|
|
503
|
+
await this.cdpSession.send("Page.addScriptToEvaluateOnNewDocument", { source: interceptorCode });
|
|
504
|
+
logger.info("XHR interceptor injected (persistent)");
|
|
505
|
+
} else {
|
|
506
|
+
await this.cdpSession.send("Runtime.evaluate", { expression: interceptorCode });
|
|
507
|
+
logger.info("XHR interceptor injected");
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async injectFetchInterceptor(options) {
|
|
511
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
512
|
+
const interceptorCode = buildFetchInterceptorCode(this.MAX_INJECTED_RECORDS);
|
|
513
|
+
if (options?.persistent) {
|
|
514
|
+
await this.cdpSession.send("Page.addScriptToEvaluateOnNewDocument", { source: interceptorCode });
|
|
515
|
+
logger.info("Fetch interceptor injected (persistent)");
|
|
516
|
+
} else {
|
|
517
|
+
await this.cdpSession.send("Runtime.evaluate", { expression: interceptorCode });
|
|
518
|
+
logger.info("Fetch interceptor injected");
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
async getXHRRequests() {
|
|
522
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
523
|
+
try {
|
|
524
|
+
const value = toRuntimeEvaluateValue(await this.cdpSession.send("Runtime.evaluate", {
|
|
525
|
+
expression: "window.__getXHRRequests ? window.__getXHRRequests() : []",
|
|
526
|
+
returnByValue: true
|
|
527
|
+
}));
|
|
528
|
+
if (!Array.isArray(value)) return [];
|
|
529
|
+
return value.filter((entry) => isObjectRecord(entry));
|
|
530
|
+
} catch (error) {
|
|
531
|
+
logger.error("Failed to get XHR requests:", error);
|
|
532
|
+
return [];
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
async getFetchRequests() {
|
|
536
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
537
|
+
try {
|
|
538
|
+
const value = toRuntimeEvaluateValue(await this.cdpSession.send("Runtime.evaluate", {
|
|
539
|
+
expression: "window.__getFetchRequests ? window.__getFetchRequests() : []",
|
|
540
|
+
returnByValue: true
|
|
541
|
+
}));
|
|
542
|
+
if (!Array.isArray(value)) return [];
|
|
543
|
+
return value.filter((entry) => isObjectRecord(entry));
|
|
544
|
+
} catch (error) {
|
|
545
|
+
logger.error("Failed to get Fetch requests:", error);
|
|
546
|
+
return [];
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
async clearInjectedBuffers() {
|
|
550
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
551
|
+
try {
|
|
552
|
+
const value = toRuntimeEvaluateValue(await this.cdpSession.send("Runtime.evaluate", {
|
|
553
|
+
expression: CLEAR_INJECTED_BUFFERS_EXPRESSION,
|
|
554
|
+
returnByValue: true
|
|
555
|
+
}));
|
|
556
|
+
if (!isObjectRecord(value)) return {
|
|
557
|
+
xhrCleared: 0,
|
|
558
|
+
fetchCleared: 0
|
|
559
|
+
};
|
|
560
|
+
return {
|
|
561
|
+
xhrCleared: toFiniteNumber(value.xhrCleared),
|
|
562
|
+
fetchCleared: toFiniteNumber(value.fetchCleared)
|
|
563
|
+
};
|
|
564
|
+
} catch (error) {
|
|
565
|
+
logger.error("Failed to clear injected network buffers:", error);
|
|
566
|
+
return {
|
|
567
|
+
xhrCleared: 0,
|
|
568
|
+
fetchCleared: 0
|
|
569
|
+
};
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
async resetInjectedInterceptors() {
|
|
573
|
+
if (!this.cdpSession) throw new Error("CDP session not initialized");
|
|
574
|
+
try {
|
|
575
|
+
const value = toRuntimeEvaluateValue(await this.cdpSession.send("Runtime.evaluate", {
|
|
576
|
+
expression: RESET_INJECTED_INTERCEPTORS_EXPRESSION,
|
|
577
|
+
returnByValue: true
|
|
578
|
+
}));
|
|
579
|
+
if (!isObjectRecord(value)) return {
|
|
580
|
+
xhrReset: false,
|
|
581
|
+
fetchReset: false
|
|
582
|
+
};
|
|
583
|
+
return {
|
|
584
|
+
xhrReset: toBoolean(value.xhrReset, false),
|
|
585
|
+
fetchReset: toBoolean(value.fetchReset, false)
|
|
586
|
+
};
|
|
587
|
+
} catch (error) {
|
|
588
|
+
logger.error("Failed to reset injected network interceptors:", error);
|
|
589
|
+
return {
|
|
590
|
+
xhrReset: false,
|
|
591
|
+
fetchReset: false
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
};
|
|
596
|
+
//#endregion
|
|
597
|
+
//#region src/modules/monitor/PlaywrightNetworkMonitor.ts
|
|
598
|
+
/**
|
|
599
|
+
* Lightweight network monitor for Playwright-based browsers (Camoufox/Firefox).
|
|
600
|
+
* Uses page.on('request'/'response') instead of CDP Network domain.
|
|
601
|
+
*/
|
|
602
|
+
var PlaywrightNetworkMonitor = class {
|
|
603
|
+
networkEnabled = false;
|
|
604
|
+
requests = /* @__PURE__ */ new Map();
|
|
605
|
+
responses = /* @__PURE__ */ new Map();
|
|
606
|
+
MAX_NETWORK_RECORDS = 500;
|
|
607
|
+
MAX_INJECTED_RECORDS = 500;
|
|
608
|
+
requestCounter = 0;
|
|
609
|
+
/** LRU cache for response bodies, auto-captured on response event. */
|
|
610
|
+
responseBodyCache = /* @__PURE__ */ new Map();
|
|
611
|
+
MAX_BODY_CACHE_ENTRIES = 200;
|
|
612
|
+
requestIdMap = /* @__PURE__ */ new WeakMap();
|
|
613
|
+
boundOnRequest = null;
|
|
614
|
+
boundOnResponse = null;
|
|
615
|
+
constructor(page) {
|
|
616
|
+
this.page = page;
|
|
617
|
+
}
|
|
618
|
+
setPage(page) {
|
|
619
|
+
if (this.page === page) return;
|
|
620
|
+
const previousPage = this.page;
|
|
621
|
+
const wasEnabled = this.networkEnabled;
|
|
622
|
+
const onRequest = this.boundOnRequest;
|
|
623
|
+
const onResponse = this.boundOnResponse;
|
|
624
|
+
if (wasEnabled && previousPage && onRequest) try {
|
|
625
|
+
previousPage.off("request", onRequest);
|
|
626
|
+
} catch {}
|
|
627
|
+
if (wasEnabled && previousPage && onResponse) try {
|
|
628
|
+
previousPage.off("response", onResponse);
|
|
629
|
+
} catch {}
|
|
630
|
+
this.page = page;
|
|
631
|
+
if (!wasEnabled || !this.page) {
|
|
632
|
+
if (!this.page) this.networkEnabled = false;
|
|
633
|
+
return;
|
|
634
|
+
}
|
|
635
|
+
if (onRequest) this.page.on("request", onRequest);
|
|
636
|
+
if (onResponse) this.page.on("response", onResponse);
|
|
637
|
+
}
|
|
638
|
+
getPageOrThrow() {
|
|
639
|
+
if (!this.page) throw new Error("Playwright page not initialized");
|
|
640
|
+
return this.page;
|
|
641
|
+
}
|
|
642
|
+
async evaluateInPage(pageFunction) {
|
|
643
|
+
const page = this.getPageOrThrow();
|
|
644
|
+
if (!page.evaluate) throw new Error("Playwright page.evaluate is not available");
|
|
645
|
+
return page.evaluate(pageFunction);
|
|
646
|
+
}
|
|
647
|
+
async evaluateOnNewDocumentInPage(pageFunction) {
|
|
648
|
+
const page = this.getPageOrThrow();
|
|
649
|
+
if (!page.evaluateOnNewDocument) throw new Error("Playwright page.evaluateOnNewDocument is not available");
|
|
650
|
+
return page.evaluateOnNewDocument(pageFunction);
|
|
651
|
+
}
|
|
652
|
+
isUnknownArray(value) {
|
|
653
|
+
return Array.isArray(value);
|
|
654
|
+
}
|
|
655
|
+
isClearedBuffersResult(value) {
|
|
656
|
+
if (!value || typeof value !== "object") return false;
|
|
657
|
+
const candidate = value;
|
|
658
|
+
return typeof candidate.xhrCleared === "number" && typeof candidate.fetchCleared === "number";
|
|
659
|
+
}
|
|
660
|
+
isResetInterceptorsResult(value) {
|
|
661
|
+
if (!value || typeof value !== "object") return false;
|
|
662
|
+
const candidate = value;
|
|
663
|
+
return typeof candidate.xhrReset === "boolean" && typeof candidate.fetchReset === "boolean";
|
|
664
|
+
}
|
|
665
|
+
isPlaywrightLikeRequest(value) {
|
|
666
|
+
if (!value || typeof value !== "object") return false;
|
|
667
|
+
const candidate = value;
|
|
668
|
+
return typeof candidate.url === "function" && typeof candidate.method === "function" && typeof candidate.headers === "function" && typeof candidate.postData === "function" && typeof candidate.resourceType === "function";
|
|
669
|
+
}
|
|
670
|
+
isPlaywrightLikeResponse(value) {
|
|
671
|
+
if (!value || typeof value !== "object") return false;
|
|
672
|
+
const candidate = value;
|
|
673
|
+
return typeof candidate.request === "function" && typeof candidate.url === "function" && typeof candidate.status === "function" && typeof candidate.statusText === "function" && typeof candidate.headers === "function";
|
|
674
|
+
}
|
|
675
|
+
async enable() {
|
|
676
|
+
if (this.networkEnabled) {
|
|
677
|
+
logger.warn("PlaywrightNetworkMonitor already enabled");
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
this.boundOnRequest = (req) => {
|
|
681
|
+
if (!this.isPlaywrightLikeRequest(req)) return;
|
|
682
|
+
const requestId = `pw-${++this.requestCounter}`;
|
|
683
|
+
this.requestIdMap.set(req, requestId);
|
|
684
|
+
const request = {
|
|
685
|
+
requestId,
|
|
686
|
+
url: req.url(),
|
|
687
|
+
method: req.method(),
|
|
688
|
+
headers: req.headers(),
|
|
689
|
+
postData: req.postData() ?? void 0,
|
|
690
|
+
timestamp: Date.now(),
|
|
691
|
+
type: req.resourceType()
|
|
692
|
+
};
|
|
693
|
+
this.requests.set(requestId, request);
|
|
694
|
+
if (this.requests.size > this.MAX_NETWORK_RECORDS) {
|
|
695
|
+
const firstKey = this.requests.keys().next().value;
|
|
696
|
+
if (firstKey) this.requests.delete(firstKey);
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
this.boundOnResponse = (res) => {
|
|
700
|
+
if (!this.isPlaywrightLikeResponse(res)) return;
|
|
701
|
+
const req = res.request();
|
|
702
|
+
const fallbackRequestId = `pw-res-${Date.now()}-${Math.random()}`;
|
|
703
|
+
const requestId = this.isPlaywrightLikeRequest(req) ? this.requestIdMap.get(req) ?? fallbackRequestId : fallbackRequestId;
|
|
704
|
+
const response = {
|
|
705
|
+
requestId,
|
|
706
|
+
url: res.url(),
|
|
707
|
+
status: res.status(),
|
|
708
|
+
statusText: res.statusText(),
|
|
709
|
+
headers: res.headers(),
|
|
710
|
+
mimeType: res.headers()["content-type"] ?? "unknown",
|
|
711
|
+
timestamp: Date.now()
|
|
712
|
+
};
|
|
713
|
+
this.responses.set(requestId, response);
|
|
714
|
+
if (this.responses.size > this.MAX_NETWORK_RECORDS) {
|
|
715
|
+
const firstKey = this.responses.keys().next().value;
|
|
716
|
+
if (firstKey) this.responses.delete(firstKey);
|
|
717
|
+
}
|
|
718
|
+
if (typeof res.body === "function") {
|
|
719
|
+
const captureId = requestId;
|
|
720
|
+
res.body().then((buf) => {
|
|
721
|
+
if (buf.length > 1048576) {
|
|
722
|
+
logger.debug(`[PW-BodyCache] Skipping oversized body for ${captureId} (${buf.length} bytes)`);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
725
|
+
if (this.responseBodyCache.size >= this.MAX_BODY_CACHE_ENTRIES) {
|
|
726
|
+
const oldestKey = this.responseBodyCache.keys().next().value;
|
|
727
|
+
if (oldestKey) this.responseBodyCache.delete(oldestKey);
|
|
728
|
+
}
|
|
729
|
+
if (/^(text\/|application\/(json|javascript|xml|x-www-form-urlencoded))/i.test(response.mimeType)) this.responseBodyCache.set(captureId, {
|
|
730
|
+
body: buf.toString("utf-8"),
|
|
731
|
+
base64Encoded: false
|
|
732
|
+
});
|
|
733
|
+
else this.responseBodyCache.set(captureId, {
|
|
734
|
+
body: buf.toString("base64"),
|
|
735
|
+
base64Encoded: true
|
|
736
|
+
});
|
|
737
|
+
logger.debug(`[PW-BodyCache] Cached body for ${captureId} (${buf.length} bytes)`);
|
|
738
|
+
}).catch((err) => {
|
|
739
|
+
logger.debug(`[PW-BodyCache] Could not capture body for ${captureId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
const page = this.getPageOrThrow();
|
|
744
|
+
page.on("request", this.boundOnRequest);
|
|
745
|
+
page.on("response", this.boundOnResponse);
|
|
746
|
+
this.networkEnabled = true;
|
|
747
|
+
logger.info("PlaywrightNetworkMonitor enabled");
|
|
748
|
+
}
|
|
749
|
+
async disable() {
|
|
750
|
+
const page = this.getPageOrThrow();
|
|
751
|
+
if (this.boundOnRequest) {
|
|
752
|
+
try {
|
|
753
|
+
page.off("request", this.boundOnRequest);
|
|
754
|
+
} catch {}
|
|
755
|
+
this.boundOnRequest = null;
|
|
756
|
+
}
|
|
757
|
+
if (this.boundOnResponse) {
|
|
758
|
+
try {
|
|
759
|
+
page.off("response", this.boundOnResponse);
|
|
760
|
+
} catch {}
|
|
761
|
+
this.boundOnResponse = null;
|
|
762
|
+
}
|
|
763
|
+
this.networkEnabled = false;
|
|
764
|
+
logger.info("PlaywrightNetworkMonitor disabled");
|
|
765
|
+
}
|
|
766
|
+
isEnabled() {
|
|
767
|
+
return this.networkEnabled;
|
|
768
|
+
}
|
|
769
|
+
getRequests(filter) {
|
|
770
|
+
let requests = Array.from(this.requests.values());
|
|
771
|
+
if (filter?.url) requests = requests.filter((r) => r.url.includes(filter.url));
|
|
772
|
+
if (filter?.method) requests = requests.filter((r) => r.method.toUpperCase() === filter.method.toUpperCase());
|
|
773
|
+
if (filter?.limit) requests = requests.slice(-filter.limit);
|
|
774
|
+
return requests;
|
|
775
|
+
}
|
|
776
|
+
getResponses(filter) {
|
|
777
|
+
let responses = Array.from(this.responses.values());
|
|
778
|
+
if (filter?.url) responses = responses.filter((r) => r.url.includes(filter.url));
|
|
779
|
+
if (filter?.status) responses = responses.filter((r) => r.status === filter.status);
|
|
780
|
+
if (filter?.limit) responses = responses.slice(-filter.limit);
|
|
781
|
+
return responses;
|
|
782
|
+
}
|
|
783
|
+
getStatus() {
|
|
784
|
+
return {
|
|
785
|
+
enabled: this.networkEnabled,
|
|
786
|
+
requestCount: this.requests.size,
|
|
787
|
+
responseCount: this.responses.size,
|
|
788
|
+
listenerCount: this.networkEnabled ? 2 : 0,
|
|
789
|
+
cdpSessionActive: false
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
getActivity(requestId) {
|
|
793
|
+
return {
|
|
794
|
+
request: this.requests.get(requestId),
|
|
795
|
+
response: this.responses.get(requestId)
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
clearRecords() {
|
|
799
|
+
this.requests.clear();
|
|
800
|
+
this.responses.clear();
|
|
801
|
+
this.responseBodyCache.clear();
|
|
802
|
+
}
|
|
803
|
+
getStats() {
|
|
804
|
+
const requests = Array.from(this.requests.values());
|
|
805
|
+
const responses = Array.from(this.responses.values());
|
|
806
|
+
const byMethod = {};
|
|
807
|
+
requests.forEach((r) => {
|
|
808
|
+
byMethod[r.method] = (byMethod[r.method] || 0) + 1;
|
|
809
|
+
});
|
|
810
|
+
const byStatus = {};
|
|
811
|
+
responses.forEach((r) => {
|
|
812
|
+
byStatus[r.status] = (byStatus[r.status] || 0) + 1;
|
|
813
|
+
});
|
|
814
|
+
const byType = {};
|
|
815
|
+
requests.forEach((r) => {
|
|
816
|
+
const type = r.type || "unknown";
|
|
817
|
+
byType[type] = (byType[type] || 0) + 1;
|
|
818
|
+
});
|
|
819
|
+
return {
|
|
820
|
+
totalRequests: requests.length,
|
|
821
|
+
totalResponses: responses.length,
|
|
822
|
+
byMethod,
|
|
823
|
+
byStatus,
|
|
824
|
+
byType
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
/** Response body retrieval from LRU cache. */
|
|
828
|
+
async getResponseBody(requestId) {
|
|
829
|
+
const cached = this.responseBodyCache.get(requestId);
|
|
830
|
+
if (cached) {
|
|
831
|
+
this.responseBodyCache.delete(requestId);
|
|
832
|
+
this.responseBodyCache.set(requestId, cached);
|
|
833
|
+
logger.debug(`[PW-BodyCache] Cache hit for ${requestId}`);
|
|
834
|
+
return cached;
|
|
835
|
+
}
|
|
836
|
+
logger.warn(`getResponseBody: no cached body for ${requestId} in Playwright mode`);
|
|
837
|
+
return null;
|
|
838
|
+
}
|
|
839
|
+
/** Inject a script via page.evaluate (Playwright equivalent of CDP Runtime.evaluate). */
|
|
840
|
+
async injectScript(script) {
|
|
841
|
+
await this.evaluateInPage(script);
|
|
842
|
+
}
|
|
843
|
+
async injectXHRInterceptor(options) {
|
|
844
|
+
const script = `
|
|
845
|
+
(function() {
|
|
846
|
+
if (window.__xhrInterceptorInjected) return;
|
|
847
|
+
window.__xhrInterceptorInjected = true;
|
|
848
|
+
const maxRecords = ${this.MAX_INJECTED_RECORDS};
|
|
849
|
+
const OrigXHR = window.__pwOriginalXMLHttpRequest || window.XMLHttpRequest;
|
|
850
|
+
window.__pwOriginalXMLHttpRequest = OrigXHR;
|
|
851
|
+
if (!window.__xhrRequests) window.__xhrRequests = [];
|
|
852
|
+
window.XMLHttpRequest = function() {
|
|
853
|
+
const xhr = new OrigXHR();
|
|
854
|
+
const origOpen = xhr.open.bind(xhr);
|
|
855
|
+
const origSend = xhr.send.bind(xhr);
|
|
856
|
+
xhr.open = function(method, url, ...rest) {
|
|
857
|
+
xhr.__hookMeta = { method, url, timestamp: Date.now() };
|
|
858
|
+
return origOpen(method, url, ...rest);
|
|
859
|
+
};
|
|
860
|
+
xhr.send = function(body) {
|
|
861
|
+
xhr.addEventListener('load', function() {
|
|
862
|
+
window.__xhrRequests.push({
|
|
863
|
+
...xhr.__hookMeta, body: body ? String(body).slice(0, 2048) : null,
|
|
864
|
+
status: xhr.status, response: xhr.responseText.slice(0, 2048),
|
|
865
|
+
});
|
|
866
|
+
if (window.__xhrRequests.length > maxRecords) {
|
|
867
|
+
window.__xhrRequests.splice(0, window.__xhrRequests.length - maxRecords);
|
|
868
|
+
}
|
|
869
|
+
});
|
|
870
|
+
return origSend(body);
|
|
871
|
+
};
|
|
872
|
+
return xhr;
|
|
873
|
+
};
|
|
874
|
+
console.log('[PlaywrightXHR] XHR interceptor injected');
|
|
875
|
+
})();
|
|
876
|
+
`;
|
|
877
|
+
if (options?.persistent) await this.evaluateOnNewDocumentInPage(script);
|
|
878
|
+
else await this.evaluateInPage(script);
|
|
879
|
+
}
|
|
880
|
+
async injectFetchInterceptor(options) {
|
|
881
|
+
const script = `
|
|
882
|
+
(function() {
|
|
883
|
+
if (window.__fetchInterceptorInjected) return;
|
|
884
|
+
window.__fetchInterceptorInjected = true;
|
|
885
|
+
const maxRecords = ${this.MAX_INJECTED_RECORDS};
|
|
886
|
+
const origFetch = window.__pwOriginalFetch || window.fetch;
|
|
887
|
+
window.__pwOriginalFetch = origFetch;
|
|
888
|
+
if (!window.__fetchRequests) window.__fetchRequests = [];
|
|
889
|
+
window.fetch = function(...args) {
|
|
890
|
+
const [url, opts] = args;
|
|
891
|
+
const entry = { url: String(url), method: opts?.method || 'GET', timestamp: Date.now() };
|
|
892
|
+
return origFetch.apply(this, args).then(res => {
|
|
893
|
+
entry.status = res.status;
|
|
894
|
+
window.__fetchRequests.push(entry);
|
|
895
|
+
if (window.__fetchRequests.length > maxRecords) {
|
|
896
|
+
window.__fetchRequests.splice(0, window.__fetchRequests.length - maxRecords);
|
|
897
|
+
}
|
|
898
|
+
// Auto-persist compact summary so data survives context compression
|
|
899
|
+
try {
|
|
900
|
+
const s = { url: entry.url, method: entry.method, status: entry.status, ts: entry.timestamp };
|
|
901
|
+
const prev = JSON.parse(localStorage.getItem('__capturedAPIs') || '[]');
|
|
902
|
+
prev.push(s);
|
|
903
|
+
if (prev.length > 500) prev.splice(0, prev.length - 500);
|
|
904
|
+
localStorage.setItem('__capturedAPIs', JSON.stringify(prev));
|
|
905
|
+
} catch(e) {}
|
|
906
|
+
return res;
|
|
907
|
+
});
|
|
908
|
+
};
|
|
909
|
+
console.log('[PlaywrightFetch] Fetch interceptor injected');
|
|
910
|
+
})();
|
|
911
|
+
`;
|
|
912
|
+
if (options?.persistent) await this.evaluateOnNewDocumentInPage(script);
|
|
913
|
+
else await this.evaluateInPage(script);
|
|
914
|
+
}
|
|
915
|
+
async getXHRRequests() {
|
|
916
|
+
try {
|
|
917
|
+
const result = await this.evaluateInPage(() => {
|
|
918
|
+
return window.__xhrRequests ?? [];
|
|
919
|
+
});
|
|
920
|
+
return this.isUnknownArray(result) ? result : [];
|
|
921
|
+
} catch (err) {
|
|
922
|
+
logger.warn(`[PW] Failed to get XHR requests: ${err instanceof Error ? err.message : String(err)}`);
|
|
923
|
+
return [];
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
async getFetchRequests() {
|
|
927
|
+
try {
|
|
928
|
+
const result = await this.evaluateInPage(() => {
|
|
929
|
+
return window.__fetchRequests ?? [];
|
|
930
|
+
});
|
|
931
|
+
return this.isUnknownArray(result) ? result : [];
|
|
932
|
+
} catch (err) {
|
|
933
|
+
logger.warn(`[PW] Failed to get fetch requests: ${err instanceof Error ? err.message : String(err)}`);
|
|
934
|
+
return [];
|
|
935
|
+
}
|
|
936
|
+
}
|
|
937
|
+
async clearInjectedBuffers() {
|
|
938
|
+
try {
|
|
939
|
+
const result = await this.evaluateInPage(() => {
|
|
940
|
+
const bridgeWindow = window;
|
|
941
|
+
const xhrRequests = bridgeWindow.__xhrRequests;
|
|
942
|
+
const fetchRequests = bridgeWindow.__fetchRequests;
|
|
943
|
+
const xhrCleared = Array.isArray(xhrRequests) ? xhrRequests.length : 0;
|
|
944
|
+
const fetchCleared = Array.isArray(fetchRequests) ? fetchRequests.length : 0;
|
|
945
|
+
if (Array.isArray(xhrRequests)) xhrRequests.length = 0;
|
|
946
|
+
if (Array.isArray(fetchRequests)) fetchRequests.length = 0;
|
|
947
|
+
return {
|
|
948
|
+
xhrCleared,
|
|
949
|
+
fetchCleared
|
|
950
|
+
};
|
|
951
|
+
});
|
|
952
|
+
return this.isClearedBuffersResult(result) ? result : {
|
|
953
|
+
xhrCleared: 0,
|
|
954
|
+
fetchCleared: 0
|
|
955
|
+
};
|
|
956
|
+
} catch (err) {
|
|
957
|
+
logger.warn(`[PW] Failed to clear injected buffers: ${err instanceof Error ? err.message : String(err)}`);
|
|
958
|
+
return {
|
|
959
|
+
xhrCleared: 0,
|
|
960
|
+
fetchCleared: 0
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
}
|
|
964
|
+
async resetInjectedInterceptors() {
|
|
965
|
+
try {
|
|
966
|
+
const result = await this.evaluateInPage(() => {
|
|
967
|
+
const bridgeWindow = window;
|
|
968
|
+
let xhrReset = false;
|
|
969
|
+
let fetchReset = false;
|
|
970
|
+
if (bridgeWindow.__pwOriginalXMLHttpRequest) {
|
|
971
|
+
bridgeWindow.XMLHttpRequest = bridgeWindow.__pwOriginalXMLHttpRequest;
|
|
972
|
+
xhrReset = true;
|
|
973
|
+
}
|
|
974
|
+
if (bridgeWindow.__pwOriginalFetch) {
|
|
975
|
+
bridgeWindow.fetch = bridgeWindow.__pwOriginalFetch;
|
|
976
|
+
fetchReset = true;
|
|
977
|
+
}
|
|
978
|
+
if (Array.isArray(bridgeWindow.__xhrRequests)) bridgeWindow.__xhrRequests.length = 0;
|
|
979
|
+
if (Array.isArray(bridgeWindow.__fetchRequests)) bridgeWindow.__fetchRequests.length = 0;
|
|
980
|
+
bridgeWindow.__xhrInterceptorInjected = false;
|
|
981
|
+
bridgeWindow.__fetchInterceptorInjected = false;
|
|
982
|
+
return {
|
|
983
|
+
xhrReset,
|
|
984
|
+
fetchReset
|
|
985
|
+
};
|
|
986
|
+
});
|
|
987
|
+
return this.isResetInterceptorsResult(result) ? result : {
|
|
988
|
+
xhrReset: false,
|
|
989
|
+
fetchReset: false
|
|
990
|
+
};
|
|
991
|
+
} catch (err) {
|
|
992
|
+
logger.warn(`[PW] Failed to reset interceptors: ${err instanceof Error ? err.message : String(err)}`);
|
|
993
|
+
return {
|
|
994
|
+
xhrReset: false,
|
|
995
|
+
fetchReset: false
|
|
996
|
+
};
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
async getAllJavaScriptResponses() {
|
|
1000
|
+
return Array.from(this.responses.values()).filter((r) => r.mimeType.includes("javascript"));
|
|
1001
|
+
}
|
|
1002
|
+
};
|
|
1003
|
+
//#endregion
|
|
1004
|
+
//#region src/modules/monitor/FetchInterceptor.ts
|
|
1005
|
+
var FetchInterceptor = class {
|
|
1006
|
+
rules = /* @__PURE__ */ new Map();
|
|
1007
|
+
enabled = false;
|
|
1008
|
+
eventHandler = null;
|
|
1009
|
+
compiledPatterns = /* @__PURE__ */ new Map();
|
|
1010
|
+
constructor(cdpSession) {
|
|
1011
|
+
this.cdpSession = cdpSession;
|
|
1012
|
+
}
|
|
1013
|
+
/**
|
|
1014
|
+
* Enable Fetch domain interception with the given rules.
|
|
1015
|
+
* If already enabled, merges new rules with existing ones.
|
|
1016
|
+
*/
|
|
1017
|
+
async enable(ruleInputs) {
|
|
1018
|
+
const newRules = [];
|
|
1019
|
+
for (const input of ruleInputs) {
|
|
1020
|
+
const rule = this.createRule(input);
|
|
1021
|
+
this.rules.set(rule.id, rule);
|
|
1022
|
+
this.compiledPatterns.set(rule.id, this.compilePattern(rule));
|
|
1023
|
+
newRules.push(rule);
|
|
1024
|
+
}
|
|
1025
|
+
await this.applyRules();
|
|
1026
|
+
if (!this.eventHandler) {
|
|
1027
|
+
this.eventHandler = (params) => {
|
|
1028
|
+
this.handleRequestPaused(params);
|
|
1029
|
+
};
|
|
1030
|
+
this.cdpSession.on("Fetch.requestPaused", this.eventHandler);
|
|
1031
|
+
}
|
|
1032
|
+
this.enabled = true;
|
|
1033
|
+
logger.info(`FetchInterceptor enabled with ${this.rules.size} rule(s)`);
|
|
1034
|
+
return newRules;
|
|
1035
|
+
}
|
|
1036
|
+
/**
|
|
1037
|
+
* Disable all interception, remove all rules, detach event handler.
|
|
1038
|
+
*/
|
|
1039
|
+
async disable() {
|
|
1040
|
+
const count = this.rules.size;
|
|
1041
|
+
if (this.eventHandler) {
|
|
1042
|
+
try {
|
|
1043
|
+
this.cdpSession.off("Fetch.requestPaused", this.eventHandler);
|
|
1044
|
+
} catch {}
|
|
1045
|
+
this.eventHandler = null;
|
|
1046
|
+
}
|
|
1047
|
+
try {
|
|
1048
|
+
await this.cdpSession.send("Fetch.disable");
|
|
1049
|
+
} catch (error) {
|
|
1050
|
+
logger.warn("Fetch.disable failed:", error);
|
|
1051
|
+
}
|
|
1052
|
+
this.rules.clear();
|
|
1053
|
+
this.compiledPatterns.clear();
|
|
1054
|
+
this.enabled = false;
|
|
1055
|
+
logger.info(`FetchInterceptor disabled, removed ${count} rule(s)`);
|
|
1056
|
+
return { removedRules: count };
|
|
1057
|
+
}
|
|
1058
|
+
/**
|
|
1059
|
+
* Remove a specific rule by ID. If no rules remain, disables Fetch domain.
|
|
1060
|
+
*/
|
|
1061
|
+
async removeRule(ruleId) {
|
|
1062
|
+
const removed = this.rules.delete(ruleId);
|
|
1063
|
+
this.compiledPatterns.delete(ruleId);
|
|
1064
|
+
if (removed) if (this.rules.size === 0) await this.disable();
|
|
1065
|
+
else await this.applyRules();
|
|
1066
|
+
return removed;
|
|
1067
|
+
}
|
|
1068
|
+
/**
|
|
1069
|
+
* List all active rules with hit statistics.
|
|
1070
|
+
*/
|
|
1071
|
+
listRules() {
|
|
1072
|
+
const rules = Array.from(this.rules.values());
|
|
1073
|
+
return {
|
|
1074
|
+
enabled: this.enabled,
|
|
1075
|
+
rules,
|
|
1076
|
+
totalHits: rules.reduce((sum, r) => sum + r.hitCount, 0)
|
|
1077
|
+
};
|
|
1078
|
+
}
|
|
1079
|
+
isEnabled() {
|
|
1080
|
+
return this.enabled;
|
|
1081
|
+
}
|
|
1082
|
+
createRule(input) {
|
|
1083
|
+
const headers = [];
|
|
1084
|
+
if (input.responseHeaders) for (const [name, value] of Object.entries(input.responseHeaders)) headers.push({
|
|
1085
|
+
name,
|
|
1086
|
+
value
|
|
1087
|
+
});
|
|
1088
|
+
return {
|
|
1089
|
+
id: randomUUID().slice(0, 8),
|
|
1090
|
+
urlPattern: input.urlPattern,
|
|
1091
|
+
urlPatternType: input.urlPatternType ?? "glob",
|
|
1092
|
+
stage: input.stage ?? "Response",
|
|
1093
|
+
responseCode: input.responseCode ?? 200,
|
|
1094
|
+
responseHeaders: headers,
|
|
1095
|
+
responseBody: input.responseBody ?? "",
|
|
1096
|
+
hitCount: 0,
|
|
1097
|
+
createdAt: Date.now()
|
|
1098
|
+
};
|
|
1099
|
+
}
|
|
1100
|
+
compilePattern(rule) {
|
|
1101
|
+
if (rule.urlPatternType === "regex") try {
|
|
1102
|
+
return new RegExp(rule.urlPattern, "i");
|
|
1103
|
+
} catch {
|
|
1104
|
+
return new RegExp(rule.urlPattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "i");
|
|
1105
|
+
}
|
|
1106
|
+
const escaped = rule.urlPattern.replace(/[.+?^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "⟨GLOBSTAR⟩").replace(/\*/g, "[^/]*").replace(/⟨GLOBSTAR⟩/g, ".*");
|
|
1107
|
+
return new RegExp(escaped, "i");
|
|
1108
|
+
}
|
|
1109
|
+
async applyRules() {
|
|
1110
|
+
const patterns = [];
|
|
1111
|
+
for (const rule of this.rules.values()) patterns.push({
|
|
1112
|
+
urlPattern: rule.urlPatternType === "glob" ? rule.urlPattern : "*",
|
|
1113
|
+
requestStage: rule.stage
|
|
1114
|
+
});
|
|
1115
|
+
if (patterns.length === 0) return;
|
|
1116
|
+
try {
|
|
1117
|
+
try {
|
|
1118
|
+
await this.cdpSession.send("Fetch.disable");
|
|
1119
|
+
} catch {}
|
|
1120
|
+
await this.cdpSession.send("Fetch.enable", {
|
|
1121
|
+
patterns: patterns.length > 0 ? patterns : [{
|
|
1122
|
+
urlPattern: "*",
|
|
1123
|
+
requestStage: "Response"
|
|
1124
|
+
}],
|
|
1125
|
+
handleAuthRequests: false
|
|
1126
|
+
});
|
|
1127
|
+
} catch (error) {
|
|
1128
|
+
logger.error("Failed to apply Fetch interception rules:", error);
|
|
1129
|
+
throw error;
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
async handleRequestPaused(params) {
|
|
1133
|
+
const requestUrl = params.request.url;
|
|
1134
|
+
for (const [ruleId, rule] of this.rules) {
|
|
1135
|
+
const pattern = this.compiledPatterns.get(ruleId);
|
|
1136
|
+
if (!pattern) continue;
|
|
1137
|
+
if (pattern.test(requestUrl)) {
|
|
1138
|
+
rule.hitCount++;
|
|
1139
|
+
logger.info(`[FetchInterceptor] Rule "${rule.urlPattern}" matched: ${requestUrl}`);
|
|
1140
|
+
try {
|
|
1141
|
+
const headers = [...rule.responseHeaders];
|
|
1142
|
+
if (!headers.some((h) => h.name.toLowerCase() === "content-type")) {
|
|
1143
|
+
const body = rule.responseBody;
|
|
1144
|
+
if (body.startsWith("{") || body.startsWith("[")) headers.push({
|
|
1145
|
+
name: "Content-Type",
|
|
1146
|
+
value: "application/json"
|
|
1147
|
+
});
|
|
1148
|
+
else headers.push({
|
|
1149
|
+
name: "Content-Type",
|
|
1150
|
+
value: "text/plain"
|
|
1151
|
+
});
|
|
1152
|
+
}
|
|
1153
|
+
if (!headers.some((h) => h.name.toLowerCase() === "access-control-allow-origin")) headers.push({
|
|
1154
|
+
name: "Access-Control-Allow-Origin",
|
|
1155
|
+
value: "*"
|
|
1156
|
+
});
|
|
1157
|
+
await this.cdpSession.send("Fetch.fulfillRequest", {
|
|
1158
|
+
requestId: params.requestId,
|
|
1159
|
+
responseCode: rule.responseCode,
|
|
1160
|
+
responseHeaders: headers,
|
|
1161
|
+
body: Buffer.from(rule.responseBody, "utf-8").toString("base64")
|
|
1162
|
+
});
|
|
1163
|
+
return;
|
|
1164
|
+
} catch (error) {
|
|
1165
|
+
logger.error(`[FetchInterceptor] fulfillRequest failed for ${requestUrl}:`, error);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
try {
|
|
1170
|
+
if (params.responseStatusCode !== void 0) await this.cdpSession.send("Fetch.continueResponse", { requestId: params.requestId });
|
|
1171
|
+
else await this.cdpSession.send("Fetch.continueRequest", { requestId: params.requestId });
|
|
1172
|
+
} catch (error) {
|
|
1173
|
+
logger.warn(`[FetchInterceptor] continue failed for ${requestUrl}:`, error);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
};
|
|
1177
|
+
//#endregion
|
|
1178
|
+
//#region src/modules/monitor/ConsoleMonitor.impl.core.logs.ts
|
|
1179
|
+
function asLogsCoreContext(ctx) {
|
|
1180
|
+
return ctx;
|
|
1181
|
+
}
|
|
1182
|
+
function getLogsCore(ctx, filter) {
|
|
1183
|
+
const coreCtx = asLogsCoreContext(ctx);
|
|
1184
|
+
if (coreCtx.contextSwitchPending) return [];
|
|
1185
|
+
let logs = coreCtx.messages;
|
|
1186
|
+
if (filter?.type) logs = logs.filter((msg) => msg.type === filter.type);
|
|
1187
|
+
const since = filter?.since;
|
|
1188
|
+
if (since !== void 0) logs = logs.filter((msg) => msg.timestamp >= since);
|
|
1189
|
+
if (filter?.limit) logs = logs.slice(-filter.limit);
|
|
1190
|
+
logger.debug(`getLogs: ${logs.length} messages`);
|
|
1191
|
+
return logs;
|
|
1192
|
+
}
|
|
1193
|
+
function clearLogsCore(ctx) {
|
|
1194
|
+
const coreCtx = asLogsCoreContext(ctx);
|
|
1195
|
+
coreCtx.messages = [];
|
|
1196
|
+
logger.info("Console logs cleared");
|
|
1197
|
+
}
|
|
1198
|
+
function getStatsCore(ctx) {
|
|
1199
|
+
const coreCtx = asLogsCoreContext(ctx);
|
|
1200
|
+
if (coreCtx.contextSwitchPending) return {
|
|
1201
|
+
totalMessages: 0,
|
|
1202
|
+
byType: {}
|
|
1203
|
+
};
|
|
1204
|
+
const byType = {};
|
|
1205
|
+
for (const msg of coreCtx.messages) byType[msg.type] = (byType[msg.type] || 0) + 1;
|
|
1206
|
+
return {
|
|
1207
|
+
totalMessages: coreCtx.messages.length,
|
|
1208
|
+
byType
|
|
1209
|
+
};
|
|
1210
|
+
}
|
|
1211
|
+
function getExceptionsCore(ctx, filter) {
|
|
1212
|
+
const coreCtx = asLogsCoreContext(ctx);
|
|
1213
|
+
if (coreCtx.contextSwitchPending) return [];
|
|
1214
|
+
let exceptions = coreCtx.exceptions;
|
|
1215
|
+
if (filter?.url) exceptions = exceptions.filter((ex) => ex.url?.includes(filter.url));
|
|
1216
|
+
const since = filter?.since;
|
|
1217
|
+
if (since !== void 0) exceptions = exceptions.filter((ex) => ex.timestamp >= since);
|
|
1218
|
+
if (filter?.limit) exceptions = exceptions.slice(-filter.limit);
|
|
1219
|
+
return exceptions;
|
|
1220
|
+
}
|
|
1221
|
+
function clearExceptionsCore(ctx) {
|
|
1222
|
+
const coreCtx = asLogsCoreContext(ctx);
|
|
1223
|
+
coreCtx.exceptions = [];
|
|
1224
|
+
logger.info("Exceptions cleared");
|
|
1225
|
+
}
|
|
1226
|
+
//#endregion
|
|
1227
|
+
//#region src/modules/monitor/ConsoleMonitor.impl.core.network.ts
|
|
1228
|
+
function asNetworkCoreContext(ctx) {
|
|
1229
|
+
return ctx;
|
|
1230
|
+
}
|
|
1231
|
+
function hasStaleContext(ctx) {
|
|
1232
|
+
return ctx.contextSwitchPending === true;
|
|
1233
|
+
}
|
|
1234
|
+
function isNetworkEnabledCore(ctx) {
|
|
1235
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1236
|
+
if (hasStaleContext(coreCtx)) return false;
|
|
1237
|
+
return (coreCtx.networkMonitor?.isEnabled() ?? false) || (coreCtx.playwrightNetworkMonitor?.isEnabled() ?? false);
|
|
1238
|
+
}
|
|
1239
|
+
function getNetworkStatusCore(ctx) {
|
|
1240
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1241
|
+
if (hasStaleContext(coreCtx)) return {
|
|
1242
|
+
enabled: false,
|
|
1243
|
+
requestCount: 0,
|
|
1244
|
+
responseCount: 0,
|
|
1245
|
+
listenerCount: 0,
|
|
1246
|
+
cdpSessionActive: false
|
|
1247
|
+
};
|
|
1248
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getStatus();
|
|
1249
|
+
if (!coreCtx.networkMonitor) return {
|
|
1250
|
+
enabled: false,
|
|
1251
|
+
requestCount: 0,
|
|
1252
|
+
responseCount: 0,
|
|
1253
|
+
listenerCount: 0,
|
|
1254
|
+
cdpSessionActive: coreCtx.cdpSession !== null
|
|
1255
|
+
};
|
|
1256
|
+
return coreCtx.networkMonitor.getStatus();
|
|
1257
|
+
}
|
|
1258
|
+
function getNetworkRequestsCore(ctx, filter) {
|
|
1259
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1260
|
+
if (hasStaleContext(coreCtx)) return [];
|
|
1261
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getRequests(filter);
|
|
1262
|
+
return coreCtx.networkMonitor?.getRequests(filter) ?? [];
|
|
1263
|
+
}
|
|
1264
|
+
function getNetworkResponsesCore(ctx, filter) {
|
|
1265
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1266
|
+
if (hasStaleContext(coreCtx)) return [];
|
|
1267
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getResponses(filter);
|
|
1268
|
+
return coreCtx.networkMonitor?.getResponses(filter) ?? [];
|
|
1269
|
+
}
|
|
1270
|
+
function getNetworkActivityCore(ctx, requestId) {
|
|
1271
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1272
|
+
if (hasStaleContext(coreCtx)) return {};
|
|
1273
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getActivity(requestId);
|
|
1274
|
+
return coreCtx.networkMonitor?.getActivity(requestId) ?? {};
|
|
1275
|
+
}
|
|
1276
|
+
async function getResponseBodyCore(ctx, requestId) {
|
|
1277
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1278
|
+
if (hasStaleContext(coreCtx)) return null;
|
|
1279
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getResponseBody(requestId);
|
|
1280
|
+
if (!coreCtx.networkMonitor) {
|
|
1281
|
+
logger.error("Network monitoring is not enabled. Call enable() with enableNetwork: true first.");
|
|
1282
|
+
return null;
|
|
1283
|
+
}
|
|
1284
|
+
return coreCtx.networkMonitor.getResponseBody(requestId);
|
|
1285
|
+
}
|
|
1286
|
+
async function getAllJavaScriptResponsesCore(ctx) {
|
|
1287
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1288
|
+
if (hasStaleContext(coreCtx)) return [];
|
|
1289
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getAllJavaScriptResponses();
|
|
1290
|
+
if (!coreCtx.networkMonitor) return [];
|
|
1291
|
+
return coreCtx.networkMonitor.getAllJavaScriptResponses();
|
|
1292
|
+
}
|
|
1293
|
+
function clearNetworkRecordsCore(ctx) {
|
|
1294
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1295
|
+
coreCtx.networkMonitor?.clearRecords();
|
|
1296
|
+
coreCtx.playwrightNetworkMonitor?.clearRecords();
|
|
1297
|
+
}
|
|
1298
|
+
async function clearInjectedBuffersCore(ctx) {
|
|
1299
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1300
|
+
if (coreCtx.playwrightNetworkMonitor) return {
|
|
1301
|
+
...await coreCtx.playwrightNetworkMonitor.clearInjectedBuffers(),
|
|
1302
|
+
dynamicScriptsCleared: 0
|
|
1303
|
+
};
|
|
1304
|
+
const networkResult = coreCtx.networkMonitor ? await coreCtx.networkMonitor.clearInjectedBuffers() : {
|
|
1305
|
+
xhrCleared: 0,
|
|
1306
|
+
fetchCleared: 0
|
|
1307
|
+
};
|
|
1308
|
+
const dynamicResult = await coreCtx.clearDynamicScriptBuffer();
|
|
1309
|
+
return {
|
|
1310
|
+
...networkResult,
|
|
1311
|
+
...dynamicResult
|
|
1312
|
+
};
|
|
1313
|
+
}
|
|
1314
|
+
async function resetInjectedInterceptorsCore(ctx) {
|
|
1315
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1316
|
+
if (coreCtx.playwrightNetworkMonitor) return {
|
|
1317
|
+
...await coreCtx.playwrightNetworkMonitor.resetInjectedInterceptors(),
|
|
1318
|
+
scriptMonitorReset: false
|
|
1319
|
+
};
|
|
1320
|
+
const networkResult = coreCtx.networkMonitor ? await coreCtx.networkMonitor.resetInjectedInterceptors() : {
|
|
1321
|
+
xhrReset: false,
|
|
1322
|
+
fetchReset: false
|
|
1323
|
+
};
|
|
1324
|
+
const scriptResult = await coreCtx.resetDynamicScriptMonitoring();
|
|
1325
|
+
return {
|
|
1326
|
+
...networkResult,
|
|
1327
|
+
...scriptResult
|
|
1328
|
+
};
|
|
1329
|
+
}
|
|
1330
|
+
function getNetworkStatsCore(ctx) {
|
|
1331
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1332
|
+
if (hasStaleContext(coreCtx)) return {
|
|
1333
|
+
totalRequests: 0,
|
|
1334
|
+
totalResponses: 0,
|
|
1335
|
+
byMethod: {},
|
|
1336
|
+
byStatus: {},
|
|
1337
|
+
byType: {}
|
|
1338
|
+
};
|
|
1339
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getStats();
|
|
1340
|
+
return coreCtx.networkMonitor?.getStats() ?? {
|
|
1341
|
+
totalRequests: 0,
|
|
1342
|
+
totalResponses: 0,
|
|
1343
|
+
byMethod: {},
|
|
1344
|
+
byStatus: {},
|
|
1345
|
+
byType: {}
|
|
1346
|
+
};
|
|
1347
|
+
}
|
|
1348
|
+
async function injectXHRInterceptorCore(ctx, options) {
|
|
1349
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1350
|
+
if (hasStaleContext(coreCtx)) throw new PrerequisiteError("Network monitoring is not enabled. Call enable() with enableNetwork: true first.");
|
|
1351
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.injectXHRInterceptor(options);
|
|
1352
|
+
if (!coreCtx.networkMonitor) throw new PrerequisiteError("Network monitoring is not enabled. Call enable() with enableNetwork: true first.");
|
|
1353
|
+
return coreCtx.networkMonitor.injectXHRInterceptor(options);
|
|
1354
|
+
}
|
|
1355
|
+
async function injectFetchInterceptorCore(ctx, options) {
|
|
1356
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1357
|
+
if (hasStaleContext(coreCtx)) throw new PrerequisiteError("Network monitoring is not enabled. Call enable() with enableNetwork: true first.");
|
|
1358
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.injectFetchInterceptor(options);
|
|
1359
|
+
if (!coreCtx.networkMonitor) throw new PrerequisiteError("Network monitoring is not enabled. Call enable() with enableNetwork: true first.");
|
|
1360
|
+
return coreCtx.networkMonitor.injectFetchInterceptor(options);
|
|
1361
|
+
}
|
|
1362
|
+
async function getXHRRequestsCore(ctx) {
|
|
1363
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1364
|
+
if (hasStaleContext(coreCtx)) return [];
|
|
1365
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getXHRRequests();
|
|
1366
|
+
if (!coreCtx.networkMonitor) return [];
|
|
1367
|
+
return coreCtx.networkMonitor.getXHRRequests();
|
|
1368
|
+
}
|
|
1369
|
+
async function getFetchRequestsCore(ctx) {
|
|
1370
|
+
const coreCtx = asNetworkCoreContext(ctx);
|
|
1371
|
+
if (hasStaleContext(coreCtx)) return [];
|
|
1372
|
+
if (coreCtx.playwrightNetworkMonitor) return coreCtx.playwrightNetworkMonitor.getFetchRequests();
|
|
1373
|
+
if (!coreCtx.networkMonitor) return [];
|
|
1374
|
+
return coreCtx.networkMonitor.getFetchRequests();
|
|
1375
|
+
}
|
|
1376
|
+
//#endregion
|
|
1377
|
+
//#region src/modules/monitor/ConsoleMonitor.impl.core.object-cache.ts
|
|
1378
|
+
async function inspectObjectCore(ctx, objectId) {
|
|
1379
|
+
const context = ctx;
|
|
1380
|
+
await context.ensureSession();
|
|
1381
|
+
if (!context.cdpSession) throw new Error("CDP session not available after reconnect attempt");
|
|
1382
|
+
if (context.objectCache.has(objectId)) {
|
|
1383
|
+
const cached = context.objectCache.get(objectId);
|
|
1384
|
+
if (cached !== void 0) return cached;
|
|
1385
|
+
}
|
|
1386
|
+
try {
|
|
1387
|
+
const result = await context.cdpSession.send("Runtime.getProperties", {
|
|
1388
|
+
objectId,
|
|
1389
|
+
ownProperties: true,
|
|
1390
|
+
accessorPropertiesOnly: false,
|
|
1391
|
+
generatePreview: true
|
|
1392
|
+
});
|
|
1393
|
+
const properties = {};
|
|
1394
|
+
for (const prop of result.result) {
|
|
1395
|
+
if (!prop.value) continue;
|
|
1396
|
+
const valueObj = prop.value;
|
|
1397
|
+
properties[prop.name] = {
|
|
1398
|
+
value: context.extractValue(valueObj),
|
|
1399
|
+
type: valueObj.type,
|
|
1400
|
+
objectId: valueObj.objectId,
|
|
1401
|
+
description: valueObj.description
|
|
1402
|
+
};
|
|
1403
|
+
}
|
|
1404
|
+
if (!context.objectCache.has(objectId)) while (context.objectCache.size >= context.MAX_OBJECT_CACHE_SIZE) {
|
|
1405
|
+
const oldestKey = context.objectCache.keys().next().value;
|
|
1406
|
+
if (oldestKey === void 0) break;
|
|
1407
|
+
context.objectCache.delete(oldestKey);
|
|
1408
|
+
}
|
|
1409
|
+
context.objectCache.set(objectId, properties);
|
|
1410
|
+
logger.info(`Object inspected: ${objectId}`, { propertyCount: Object.keys(properties).length });
|
|
1411
|
+
return properties;
|
|
1412
|
+
} catch (error) {
|
|
1413
|
+
logger.error("Failed to inspect object:", error);
|
|
1414
|
+
throw error;
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
function clearObjectCacheCore(ctx) {
|
|
1418
|
+
ctx.objectCache.clear();
|
|
1419
|
+
logger.info("Object cache cleared");
|
|
1420
|
+
}
|
|
1421
|
+
//#endregion
|
|
1422
|
+
//#region src/modules/monitor/ConsoleMonitor.impl.core.dynamic.ts
|
|
1423
|
+
function asDynamicCoreContext(ctx) {
|
|
1424
|
+
return ctx;
|
|
1425
|
+
}
|
|
1426
|
+
async function enableDynamicScriptMonitoringCore(ctx, options) {
|
|
1427
|
+
const coreCtx = asDynamicCoreContext(ctx);
|
|
1428
|
+
await coreCtx.ensureSession();
|
|
1429
|
+
if (!coreCtx.cdpSession) throw new PrerequisiteError("CDP session not available after reconnect attempt");
|
|
1430
|
+
const monitorCode = `
|
|
1431
|
+
(function() {
|
|
1432
|
+
if (window.__dynamicScriptMonitorInstalled) {
|
|
1433
|
+
console.log('[ScriptMonitor] Already installed');
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
window.__dynamicScriptMonitorInstalled = true;
|
|
1437
|
+
|
|
1438
|
+
const maxRecords = ${coreCtx.MAX_INJECTED_DYNAMIC_SCRIPTS};
|
|
1439
|
+
if (!window.__dynamicScripts) {
|
|
1440
|
+
window.__dynamicScripts = [];
|
|
1441
|
+
}
|
|
1442
|
+
const dynamicScripts = window.__dynamicScripts;
|
|
1443
|
+
const state = window.__dynamicScriptMonitorState || {};
|
|
1444
|
+
if (!state.originalCreateElement) state.originalCreateElement = document.createElement;
|
|
1445
|
+
if (!state.originalEval) state.originalEval = window.eval;
|
|
1446
|
+
if (!state.originalFunction) state.originalFunction = window.Function;
|
|
1447
|
+
window.__dynamicScriptMonitorState = state;
|
|
1448
|
+
|
|
1449
|
+
const observer = new MutationObserver((mutations) => {
|
|
1450
|
+
mutations.forEach((mutation) => {
|
|
1451
|
+
mutation.addedNodes.forEach((node) => {
|
|
1452
|
+
if (node.nodeName === 'SCRIPT') {
|
|
1453
|
+
const script = node;
|
|
1454
|
+
const info = {
|
|
1455
|
+
type: 'dynamic',
|
|
1456
|
+
src: script.src || '(inline)',
|
|
1457
|
+
content: script.src ? null : script.textContent,
|
|
1458
|
+
timestamp: Date.now(),
|
|
1459
|
+
async: script.async,
|
|
1460
|
+
defer: script.defer,
|
|
1461
|
+
};
|
|
1462
|
+
|
|
1463
|
+
dynamicScripts.push(info);
|
|
1464
|
+
if (dynamicScripts.length > maxRecords) {
|
|
1465
|
+
dynamicScripts.splice(0, dynamicScripts.length - maxRecords);
|
|
1466
|
+
}
|
|
1467
|
+
console.log('[ScriptMonitor] Dynamic script added:', info);
|
|
1468
|
+
}
|
|
1469
|
+
});
|
|
1470
|
+
});
|
|
1471
|
+
});
|
|
1472
|
+
|
|
1473
|
+
observer.observe(document.documentElement, {
|
|
1474
|
+
childList: true,
|
|
1475
|
+
subtree: true,
|
|
1476
|
+
});
|
|
1477
|
+
state.observer = observer;
|
|
1478
|
+
|
|
1479
|
+
const originalCreateElement = state.originalCreateElement;
|
|
1480
|
+
document.createElement = function(tagName) {
|
|
1481
|
+
const element = originalCreateElement.call(document, tagName);
|
|
1482
|
+
|
|
1483
|
+
if (tagName.toLowerCase() === 'script') {
|
|
1484
|
+
console.log('[ScriptMonitor] Script element created via createElement');
|
|
1485
|
+
|
|
1486
|
+
const originalSetAttribute = element.setAttribute;
|
|
1487
|
+
element.setAttribute = function(name, value) {
|
|
1488
|
+
if (name === 'src') {
|
|
1489
|
+
console.log('[ScriptMonitor] Script src set to:', value);
|
|
1490
|
+
}
|
|
1491
|
+
return originalSetAttribute.call(element, name, value);
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
return element;
|
|
1496
|
+
};
|
|
1497
|
+
|
|
1498
|
+
const originalEval = state.originalEval;
|
|
1499
|
+
window.eval = function(code) {
|
|
1500
|
+
console.log('[ScriptMonitor] eval() called with code:',
|
|
1501
|
+
typeof code === 'string' ? code.substring(0, 100) + '...' : code);
|
|
1502
|
+
return originalEval.call(window, code);
|
|
1503
|
+
};
|
|
1504
|
+
|
|
1505
|
+
const originalFunction = state.originalFunction;
|
|
1506
|
+
window.Function = function(...args) {
|
|
1507
|
+
console.log('[ScriptMonitor] Function() constructor called with args:', args);
|
|
1508
|
+
return originalFunction.apply(this, args);
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
window.__getDynamicScripts = function() {
|
|
1512
|
+
return dynamicScripts;
|
|
1513
|
+
};
|
|
1514
|
+
|
|
1515
|
+
console.log('[ScriptMonitor] Dynamic script monitoring enabled');
|
|
1516
|
+
})();
|
|
1517
|
+
`;
|
|
1518
|
+
if (options?.persistent) {
|
|
1519
|
+
await coreCtx.cdpSession.send("Page.addScriptToEvaluateOnNewDocument", { source: monitorCode });
|
|
1520
|
+
logger.info("Dynamic script monitoring enabled (persistent)");
|
|
1521
|
+
} else {
|
|
1522
|
+
await coreCtx.cdpSession.send("Runtime.evaluate", { expression: monitorCode });
|
|
1523
|
+
logger.info("Dynamic script monitoring enabled");
|
|
1524
|
+
}
|
|
1525
|
+
}
|
|
1526
|
+
async function clearDynamicScriptBufferCore(ctx) {
|
|
1527
|
+
const coreCtx = asDynamicCoreContext(ctx);
|
|
1528
|
+
if (!coreCtx.cdpSession) return { dynamicScriptsCleared: 0 };
|
|
1529
|
+
try {
|
|
1530
|
+
const value = (await coreCtx.cdpSession.send("Runtime.evaluate", {
|
|
1531
|
+
expression: `
|
|
1532
|
+
(() => {
|
|
1533
|
+
const store = Array.isArray(window.__dynamicScripts)
|
|
1534
|
+
? window.__dynamicScripts
|
|
1535
|
+
: (typeof window.__getDynamicScripts === 'function'
|
|
1536
|
+
? window.__getDynamicScripts()
|
|
1537
|
+
: null);
|
|
1538
|
+
const dynamicScriptsCleared = Array.isArray(store) ? store.length : 0;
|
|
1539
|
+
if (Array.isArray(store)) {
|
|
1540
|
+
store.length = 0;
|
|
1541
|
+
}
|
|
1542
|
+
return { dynamicScriptsCleared };
|
|
1543
|
+
})()
|
|
1544
|
+
`,
|
|
1545
|
+
returnByValue: true
|
|
1546
|
+
})).result?.value;
|
|
1547
|
+
if (typeof value === "object" && value !== null && "dynamicScriptsCleared" in value && typeof value.dynamicScriptsCleared === "number") return value;
|
|
1548
|
+
return { dynamicScriptsCleared: 0 };
|
|
1549
|
+
} catch (error) {
|
|
1550
|
+
logger.error("Failed to clear dynamic script buffer:", error);
|
|
1551
|
+
return { dynamicScriptsCleared: 0 };
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
async function resetDynamicScriptMonitoringCore(ctx) {
|
|
1555
|
+
const coreCtx = asDynamicCoreContext(ctx);
|
|
1556
|
+
if (!coreCtx.cdpSession) return { scriptMonitorReset: false };
|
|
1557
|
+
try {
|
|
1558
|
+
const value = (await coreCtx.cdpSession.send("Runtime.evaluate", {
|
|
1559
|
+
expression: `
|
|
1560
|
+
(() => {
|
|
1561
|
+
const state = window.__dynamicScriptMonitorState;
|
|
1562
|
+
let scriptMonitorReset = false;
|
|
1563
|
+
|
|
1564
|
+
try {
|
|
1565
|
+
if (state && state.observer && typeof state.observer.disconnect === 'function') {
|
|
1566
|
+
state.observer.disconnect();
|
|
1567
|
+
state.observer = null;
|
|
1568
|
+
scriptMonitorReset = true;
|
|
1569
|
+
}
|
|
1570
|
+
} catch (_) {}
|
|
1571
|
+
|
|
1572
|
+
try {
|
|
1573
|
+
if (state && state.originalCreateElement) {
|
|
1574
|
+
document.createElement = state.originalCreateElement;
|
|
1575
|
+
scriptMonitorReset = true;
|
|
1576
|
+
}
|
|
1577
|
+
} catch (_) {}
|
|
1578
|
+
|
|
1579
|
+
try {
|
|
1580
|
+
if (state && state.originalEval) {
|
|
1581
|
+
window.eval = state.originalEval;
|
|
1582
|
+
scriptMonitorReset = true;
|
|
1583
|
+
}
|
|
1584
|
+
} catch (_) {}
|
|
1585
|
+
|
|
1586
|
+
try {
|
|
1587
|
+
if (state && state.originalFunction) {
|
|
1588
|
+
window.Function = state.originalFunction;
|
|
1589
|
+
scriptMonitorReset = true;
|
|
1590
|
+
}
|
|
1591
|
+
} catch (_) {}
|
|
1592
|
+
|
|
1593
|
+
if (Array.isArray(window.__dynamicScripts)) {
|
|
1594
|
+
window.__dynamicScripts.length = 0;
|
|
1595
|
+
}
|
|
1596
|
+
delete window.__getDynamicScripts;
|
|
1597
|
+
window.__dynamicScriptMonitorInstalled = false;
|
|
1598
|
+
|
|
1599
|
+
return { scriptMonitorReset };
|
|
1600
|
+
})()
|
|
1601
|
+
`,
|
|
1602
|
+
returnByValue: true
|
|
1603
|
+
})).result?.value;
|
|
1604
|
+
if (typeof value === "object" && value !== null && "scriptMonitorReset" in value && typeof value.scriptMonitorReset === "boolean") return value;
|
|
1605
|
+
return { scriptMonitorReset: false };
|
|
1606
|
+
} catch (error) {
|
|
1607
|
+
logger.error("Failed to reset dynamic script monitoring:", error);
|
|
1608
|
+
return { scriptMonitorReset: false };
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
async function getDynamicScriptsCore(ctx) {
|
|
1612
|
+
const coreCtx = asDynamicCoreContext(ctx);
|
|
1613
|
+
if (!coreCtx.cdpSession) throw new PrerequisiteError("CDP session not initialized");
|
|
1614
|
+
try {
|
|
1615
|
+
const value = (await coreCtx.cdpSession.send("Runtime.evaluate", {
|
|
1616
|
+
expression: "window.__getDynamicScripts ? window.__getDynamicScripts() : []",
|
|
1617
|
+
returnByValue: true
|
|
1618
|
+
})).result?.value;
|
|
1619
|
+
return Array.isArray(value) ? value : [];
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
logger.error("Failed to get dynamic scripts:", error);
|
|
1622
|
+
return [];
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
async function injectFunctionTracerCore(ctx, functionName, options) {
|
|
1626
|
+
const coreCtx = asDynamicCoreContext(ctx);
|
|
1627
|
+
if (!coreCtx.cdpSession) throw new PrerequisiteError("CDP session not initialized");
|
|
1628
|
+
const tracerCode = `
|
|
1629
|
+
(function() {
|
|
1630
|
+
const originalFunc = window.${functionName};
|
|
1631
|
+
if (typeof originalFunc !== 'function') {
|
|
1632
|
+
console.error('[Tracer] ${functionName} is not a function');
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
window.${functionName} = new Proxy(originalFunc, {
|
|
1637
|
+
apply: function(target, thisArg, args) {
|
|
1638
|
+
console.log('[Tracer] ${functionName} called with args:', args);
|
|
1639
|
+
const startTime = performance.now();
|
|
1640
|
+
|
|
1641
|
+
try {
|
|
1642
|
+
const result = target.apply(thisArg, args);
|
|
1643
|
+
const endTime = performance.now();
|
|
1644
|
+
console.log('[Tracer] ${functionName} returned:', result, 'Time:', (endTime - startTime).toFixed(2), 'ms');
|
|
1645
|
+
return result;
|
|
1646
|
+
} catch (error) {
|
|
1647
|
+
console.error('[Tracer] ${functionName} threw error:', error);
|
|
1648
|
+
throw error;
|
|
1649
|
+
}
|
|
1650
|
+
}
|
|
1651
|
+
});
|
|
1652
|
+
|
|
1653
|
+
console.log('[Tracer] ${functionName} is now being traced');
|
|
1654
|
+
})();
|
|
1655
|
+
`;
|
|
1656
|
+
if (options?.persistent) {
|
|
1657
|
+
await coreCtx.cdpSession.send("Page.addScriptToEvaluateOnNewDocument", { source: tracerCode });
|
|
1658
|
+
logger.info(`Function tracer injected for: ${functionName} (persistent)`);
|
|
1659
|
+
} else {
|
|
1660
|
+
await coreCtx.cdpSession.send("Runtime.evaluate", { expression: tracerCode });
|
|
1661
|
+
logger.info(`Function tracer injected for: ${functionName}`);
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
async function injectPropertyWatcherCore(ctx, objectPath, propertyName, options) {
|
|
1665
|
+
const coreCtx = asDynamicCoreContext(ctx);
|
|
1666
|
+
if (!coreCtx.cdpSession) throw new PrerequisiteError("CDP session not initialized");
|
|
1667
|
+
const watcherCode = `
|
|
1668
|
+
(function() {
|
|
1669
|
+
const obj = ${objectPath};
|
|
1670
|
+
if (!obj) {
|
|
1671
|
+
console.error('[Watcher] Object not found: ${objectPath}');
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
let value = obj.${propertyName};
|
|
1676
|
+
|
|
1677
|
+
Object.defineProperty(obj, '${propertyName}', {
|
|
1678
|
+
get: function() {
|
|
1679
|
+
console.log('[Watcher] ${objectPath}.${propertyName} accessed, value:', value);
|
|
1680
|
+
return value;
|
|
1681
|
+
},
|
|
1682
|
+
set: function(newValue) {
|
|
1683
|
+
console.log('[Watcher] ${objectPath}.${propertyName} changed from', value, 'to', newValue);
|
|
1684
|
+
value = newValue;
|
|
1685
|
+
},
|
|
1686
|
+
enumerable: true,
|
|
1687
|
+
configurable: true
|
|
1688
|
+
});
|
|
1689
|
+
|
|
1690
|
+
console.log('[Watcher] Property watcher installed for ${objectPath}.${propertyName}');
|
|
1691
|
+
})();
|
|
1692
|
+
`;
|
|
1693
|
+
if (options?.persistent) {
|
|
1694
|
+
await coreCtx.cdpSession.send("Page.addScriptToEvaluateOnNewDocument", { source: watcherCode });
|
|
1695
|
+
logger.info(`Property watcher injected for: ${objectPath}.${propertyName} (persistent)`);
|
|
1696
|
+
} else {
|
|
1697
|
+
await coreCtx.cdpSession.send("Runtime.evaluate", { expression: watcherCode });
|
|
1698
|
+
logger.info(`Property watcher injected for: ${objectPath}.${propertyName}`);
|
|
1699
|
+
}
|
|
1700
|
+
}
|
|
1701
|
+
//#endregion
|
|
1702
|
+
//#region src/modules/monitor/ConsoleMonitor.impl.core.session.ts
|
|
1703
|
+
function asSessionCtx(ctx) {
|
|
1704
|
+
return ctx;
|
|
1705
|
+
}
|
|
1706
|
+
async function doEnableCdpCore(ctx, session, managed, options) {
|
|
1707
|
+
const state = asSessionCtx(ctx);
|
|
1708
|
+
state.cdpSession = session;
|
|
1709
|
+
state.usingManagedTargetSession = managed;
|
|
1710
|
+
state.lastEnableOptions = { ...options };
|
|
1711
|
+
state.cdpSession.on("disconnected", () => {
|
|
1712
|
+
logger.warn("ConsoleMonitor CDP session disconnected");
|
|
1713
|
+
state.cdpSession = null;
|
|
1714
|
+
state.networkMonitor = null;
|
|
1715
|
+
state.usingManagedTargetSession = false;
|
|
1716
|
+
});
|
|
1717
|
+
await cdpSendWithTimeout(state.cdpSession, "Runtime.enable", {}, 5e3);
|
|
1718
|
+
await cdpSendWithTimeout(state.cdpSession, "Console.enable", {}, 5e3);
|
|
1719
|
+
state.cdpSession.on("Runtime.consoleAPICalled", (params) => {
|
|
1720
|
+
const stackTrace = params.stackTrace?.callFrames?.map((frame) => ({
|
|
1721
|
+
functionName: frame.functionName || "(anonymous)",
|
|
1722
|
+
url: frame.url,
|
|
1723
|
+
lineNumber: frame.lineNumber,
|
|
1724
|
+
columnNumber: frame.columnNumber
|
|
1725
|
+
})) || [];
|
|
1726
|
+
const message = {
|
|
1727
|
+
type: params.type,
|
|
1728
|
+
text: params.args.map((arg) => state.formatRemoteObject(arg)).join(" "),
|
|
1729
|
+
args: params.args.map((arg) => state.extractValue(arg)),
|
|
1730
|
+
timestamp: params.timestamp,
|
|
1731
|
+
stackTrace,
|
|
1732
|
+
url: stackTrace[0]?.url,
|
|
1733
|
+
lineNumber: stackTrace[0]?.lineNumber,
|
|
1734
|
+
columnNumber: stackTrace[0]?.columnNumber
|
|
1735
|
+
};
|
|
1736
|
+
state.messages.push(message);
|
|
1737
|
+
if (state.messages.length > state.MAX_MESSAGES) state.messages = state.messages.slice(-Math.floor(state.MAX_MESSAGES / 2));
|
|
1738
|
+
logger.debug(`Console ${params.type}: ${message.text}`);
|
|
1739
|
+
});
|
|
1740
|
+
state.cdpSession.on("Console.messageAdded", (params) => {
|
|
1741
|
+
const msg = params.message;
|
|
1742
|
+
const message = {
|
|
1743
|
+
type: msg.level || "log",
|
|
1744
|
+
text: msg.text,
|
|
1745
|
+
timestamp: Date.now(),
|
|
1746
|
+
url: msg.url,
|
|
1747
|
+
lineNumber: msg.line,
|
|
1748
|
+
columnNumber: msg.column
|
|
1749
|
+
};
|
|
1750
|
+
state.messages.push(message);
|
|
1751
|
+
if (state.messages.length > state.MAX_MESSAGES) state.messages = state.messages.slice(-Math.floor(state.MAX_MESSAGES / 2));
|
|
1752
|
+
});
|
|
1753
|
+
if (options?.enableExceptions !== false) state.cdpSession.on("Runtime.exceptionThrown", (params) => {
|
|
1754
|
+
const exception = params.exceptionDetails;
|
|
1755
|
+
const stackTrace = exception.stackTrace?.callFrames?.map((frame) => ({
|
|
1756
|
+
functionName: frame.functionName || "(anonymous)",
|
|
1757
|
+
url: frame.url,
|
|
1758
|
+
lineNumber: frame.lineNumber,
|
|
1759
|
+
columnNumber: frame.columnNumber
|
|
1760
|
+
})) || [];
|
|
1761
|
+
const exceptionInfo = {
|
|
1762
|
+
text: exception.exception?.description || exception.text,
|
|
1763
|
+
exceptionId: exception.exceptionId,
|
|
1764
|
+
timestamp: Date.now(),
|
|
1765
|
+
stackTrace,
|
|
1766
|
+
url: exception.url,
|
|
1767
|
+
lineNumber: exception.lineNumber,
|
|
1768
|
+
columnNumber: exception.columnNumber,
|
|
1769
|
+
scriptId: exception.scriptId
|
|
1770
|
+
};
|
|
1771
|
+
state.exceptions.push(exceptionInfo);
|
|
1772
|
+
if (state.exceptions.length > state.MAX_EXCEPTIONS) state.exceptions = state.exceptions.slice(-Math.floor(state.MAX_EXCEPTIONS / 2));
|
|
1773
|
+
logger.error(`Exception thrown: ${exceptionInfo.text}`, {
|
|
1774
|
+
url: exceptionInfo.url,
|
|
1775
|
+
line: exceptionInfo.lineNumber
|
|
1776
|
+
});
|
|
1777
|
+
});
|
|
1778
|
+
if (options?.enableNetwork) {
|
|
1779
|
+
state.networkMonitor = new NetworkMonitor(state.cdpSession);
|
|
1780
|
+
await state.networkMonitor.enable();
|
|
1781
|
+
}
|
|
1782
|
+
logger.info("ConsoleMonitor enabled", {
|
|
1783
|
+
network: options?.enableNetwork || false,
|
|
1784
|
+
exceptions: options?.enableExceptions !== false
|
|
1785
|
+
});
|
|
1786
|
+
}
|
|
1787
|
+
async function enablePlaywrightCore(ctx, options) {
|
|
1788
|
+
const state = asSessionCtx(ctx);
|
|
1789
|
+
if (state.playwrightConsoleHandler) {
|
|
1790
|
+
if (options?.enableNetwork && !state.playwrightNetworkMonitor) {
|
|
1791
|
+
state.playwrightNetworkMonitor = new PlaywrightNetworkMonitor(state.playwrightPage);
|
|
1792
|
+
await state.playwrightNetworkMonitor.enable();
|
|
1793
|
+
logger.info("Network monitoring added to existing ConsoleMonitor Playwright session");
|
|
1794
|
+
}
|
|
1795
|
+
return;
|
|
1796
|
+
}
|
|
1797
|
+
const page = state.playwrightPage;
|
|
1798
|
+
state.playwrightConsoleHandler = (msg) => {
|
|
1799
|
+
const message = {
|
|
1800
|
+
type: msg.type() || "log",
|
|
1801
|
+
text: msg.text(),
|
|
1802
|
+
timestamp: Date.now()
|
|
1803
|
+
};
|
|
1804
|
+
state.messages.push(message);
|
|
1805
|
+
if (state.messages.length > state.MAX_MESSAGES) state.messages = state.messages.slice(-Math.floor(state.MAX_MESSAGES / 2));
|
|
1806
|
+
};
|
|
1807
|
+
page.on("console", state.playwrightConsoleHandler);
|
|
1808
|
+
if (options?.enableExceptions !== false) {
|
|
1809
|
+
state.playwrightErrorHandler = (error) => {
|
|
1810
|
+
const exceptionInfo = {
|
|
1811
|
+
text: error.message,
|
|
1812
|
+
exceptionId: Date.now(),
|
|
1813
|
+
timestamp: Date.now()
|
|
1814
|
+
};
|
|
1815
|
+
state.exceptions.push(exceptionInfo);
|
|
1816
|
+
if (state.exceptions.length > state.MAX_EXCEPTIONS) state.exceptions = state.exceptions.slice(-Math.floor(state.MAX_EXCEPTIONS / 2));
|
|
1817
|
+
};
|
|
1818
|
+
page.on("pageerror", state.playwrightErrorHandler);
|
|
1819
|
+
}
|
|
1820
|
+
if (options?.enableNetwork) {
|
|
1821
|
+
state.playwrightNetworkMonitor = new PlaywrightNetworkMonitor(state.playwrightPage);
|
|
1822
|
+
await state.playwrightNetworkMonitor.enable();
|
|
1823
|
+
}
|
|
1824
|
+
logger.info("ConsoleMonitor enabled (Playwright/camoufox mode)", { network: options?.enableNetwork || false });
|
|
1825
|
+
}
|
|
1826
|
+
async function disableCore(ctx) {
|
|
1827
|
+
const state = asSessionCtx(ctx);
|
|
1828
|
+
if (state.playwrightPage) {
|
|
1829
|
+
const page = state.playwrightPage;
|
|
1830
|
+
if (state.playwrightConsoleHandler) {
|
|
1831
|
+
try {
|
|
1832
|
+
page.off("console", state.playwrightConsoleHandler);
|
|
1833
|
+
} catch {}
|
|
1834
|
+
state.playwrightConsoleHandler = null;
|
|
1835
|
+
}
|
|
1836
|
+
if (state.playwrightErrorHandler) {
|
|
1837
|
+
try {
|
|
1838
|
+
page.off("pageerror", state.playwrightErrorHandler);
|
|
1839
|
+
} catch {}
|
|
1840
|
+
state.playwrightErrorHandler = null;
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
if (state.playwrightNetworkMonitor) {
|
|
1844
|
+
await state.playwrightNetworkMonitor.disable();
|
|
1845
|
+
state.playwrightNetworkMonitor = null;
|
|
1846
|
+
}
|
|
1847
|
+
if (state.cdpSession) {
|
|
1848
|
+
if (state.networkMonitor) {
|
|
1849
|
+
await state.networkMonitor.disable();
|
|
1850
|
+
state.networkMonitor = null;
|
|
1851
|
+
}
|
|
1852
|
+
try {
|
|
1853
|
+
await state.cdpSession.send("Console.disable");
|
|
1854
|
+
} catch (error) {
|
|
1855
|
+
logger.warn("Failed to disable Console domain:", error);
|
|
1856
|
+
}
|
|
1857
|
+
try {
|
|
1858
|
+
await state.cdpSession.send("Runtime.disable");
|
|
1859
|
+
} catch (error) {
|
|
1860
|
+
logger.warn("Failed to disable Runtime domain:", error);
|
|
1861
|
+
}
|
|
1862
|
+
if (!state.usingManagedTargetSession) try {
|
|
1863
|
+
await state.cdpSession.detach();
|
|
1864
|
+
} catch (error) {
|
|
1865
|
+
logger.warn("Failed to detach ConsoleMonitor CDP session:", error);
|
|
1866
|
+
}
|
|
1867
|
+
else logger.debug("ConsoleMonitor released managed target session without detaching target");
|
|
1868
|
+
state.cdpSession = null;
|
|
1869
|
+
state.usingManagedTargetSession = false;
|
|
1870
|
+
logger.info("ConsoleMonitor disabled");
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
async function cdpSendWithTimeout(session, method, params, timeoutMs = 3e4) {
|
|
1874
|
+
return Promise.race([session.send(method, params), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error(`CDP ${method} timed out after ${timeoutMs}ms`)), timeoutMs))]);
|
|
1875
|
+
}
|
|
1876
|
+
//#endregion
|
|
1877
|
+
//#region src/modules/monitor/ConsoleMonitor.impl.core.class.ts
|
|
1878
|
+
var ConsoleMonitor = class {
|
|
1879
|
+
cdpSession = null;
|
|
1880
|
+
networkMonitor = null;
|
|
1881
|
+
fetchInterceptor = null;
|
|
1882
|
+
playwrightNetworkMonitor = null;
|
|
1883
|
+
playwrightPage = null;
|
|
1884
|
+
usingManagedTargetSession = false;
|
|
1885
|
+
contextSwitchPending = false;
|
|
1886
|
+
playwrightConsoleHandler = null;
|
|
1887
|
+
playwrightErrorHandler = null;
|
|
1888
|
+
messages = [];
|
|
1889
|
+
MAX_MESSAGES = 1e3;
|
|
1890
|
+
exceptions = [];
|
|
1891
|
+
MAX_EXCEPTIONS = 500;
|
|
1892
|
+
MAX_INJECTED_DYNAMIC_SCRIPTS = 500;
|
|
1893
|
+
MAX_OBJECT_CACHE_SIZE = 1e3;
|
|
1894
|
+
objectCache = /* @__PURE__ */ new Map();
|
|
1895
|
+
initPromise;
|
|
1896
|
+
lastEnableOptions = {};
|
|
1897
|
+
constructor(collector) {
|
|
1898
|
+
this.collector = collector;
|
|
1899
|
+
this.touchSplitMembersForTypeCheck();
|
|
1900
|
+
}
|
|
1901
|
+
touchSplitMembersForTypeCheck() {
|
|
1902
|
+
this.MAX_INJECTED_DYNAMIC_SCRIPTS;
|
|
1903
|
+
this.MAX_OBJECT_CACHE_SIZE;
|
|
1904
|
+
this.clearDynamicScriptBuffer;
|
|
1905
|
+
this.resetDynamicScriptMonitoring;
|
|
1906
|
+
this.usingManagedTargetSession;
|
|
1907
|
+
this.playwrightErrorHandler;
|
|
1908
|
+
this.messages;
|
|
1909
|
+
this.MAX_MESSAGES;
|
|
1910
|
+
this.exceptions;
|
|
1911
|
+
this.MAX_EXCEPTIONS;
|
|
1912
|
+
this.formatRemoteObject;
|
|
1913
|
+
this.extractValue;
|
|
1914
|
+
}
|
|
1915
|
+
setPlaywrightPage(page) {
|
|
1916
|
+
this.playwrightPage = page;
|
|
1917
|
+
this.playwrightNetworkMonitor?.setPage(page);
|
|
1918
|
+
}
|
|
1919
|
+
clearPlaywrightPage() {
|
|
1920
|
+
this.playwrightPage = null;
|
|
1921
|
+
this.contextSwitchPending = false;
|
|
1922
|
+
this.playwrightConsoleHandler = null;
|
|
1923
|
+
this.playwrightErrorHandler = null;
|
|
1924
|
+
this.playwrightNetworkMonitor?.setPage(null);
|
|
1925
|
+
this.playwrightNetworkMonitor = null;
|
|
1926
|
+
}
|
|
1927
|
+
getManagedTargetSession() {
|
|
1928
|
+
return this.collector.getAttachedTargetSession?.() ?? null;
|
|
1929
|
+
}
|
|
1930
|
+
async createCdpSession() {
|
|
1931
|
+
const managedSession = this.getManagedTargetSession();
|
|
1932
|
+
if (managedSession) return {
|
|
1933
|
+
session: managedSession,
|
|
1934
|
+
managed: true
|
|
1935
|
+
};
|
|
1936
|
+
const page = await this.collector.getActivePage();
|
|
1937
|
+
return {
|
|
1938
|
+
session: await Promise.race([page.createCDPSession(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("cdp_session_timeout")), 500))]),
|
|
1939
|
+
managed: false
|
|
1940
|
+
};
|
|
1941
|
+
}
|
|
1942
|
+
markContextChanged() {
|
|
1943
|
+
if (!this.cdpSession && !this.playwrightPage && !this.networkMonitor && !this.playwrightNetworkMonitor && !this.fetchInterceptor) return;
|
|
1944
|
+
this.contextSwitchPending = true;
|
|
1945
|
+
this.clearLogs();
|
|
1946
|
+
this.clearExceptions();
|
|
1947
|
+
this.clearNetworkRecords();
|
|
1948
|
+
this.clearObjectCache();
|
|
1949
|
+
logger.info("ConsoleMonitor marked stale after active context switch");
|
|
1950
|
+
}
|
|
1951
|
+
async enable(options) {
|
|
1952
|
+
if (this.contextSwitchPending) await this.disable();
|
|
1953
|
+
if (this.initPromise) {
|
|
1954
|
+
await this.initPromise;
|
|
1955
|
+
await this.applyPostEnableOptions(options);
|
|
1956
|
+
return;
|
|
1957
|
+
}
|
|
1958
|
+
this.initPromise = this.doEnable(options);
|
|
1959
|
+
try {
|
|
1960
|
+
await this.initPromise;
|
|
1961
|
+
} finally {
|
|
1962
|
+
this.initPromise = void 0;
|
|
1963
|
+
}
|
|
1964
|
+
}
|
|
1965
|
+
async doEnable(options) {
|
|
1966
|
+
if (this.playwrightPage) {
|
|
1967
|
+
this.lastEnableOptions = { ...options };
|
|
1968
|
+
return enablePlaywrightCore(this, options);
|
|
1969
|
+
}
|
|
1970
|
+
if (this.cdpSession) {
|
|
1971
|
+
if (options?.enableNetwork && !this.networkMonitor) {
|
|
1972
|
+
this.networkMonitor = new NetworkMonitor(this.cdpSession);
|
|
1973
|
+
await this.networkMonitor.enable();
|
|
1974
|
+
logger.info("Network monitoring added to existing ConsoleMonitor session");
|
|
1975
|
+
}
|
|
1976
|
+
return;
|
|
1977
|
+
}
|
|
1978
|
+
const { session, managed } = await this.createCdpSession();
|
|
1979
|
+
await doEnableCdpCore(this, session, managed, options);
|
|
1980
|
+
}
|
|
1981
|
+
async applyPostEnableOptions(options) {
|
|
1982
|
+
if (!options?.enableNetwork) return;
|
|
1983
|
+
this.lastEnableOptions = {
|
|
1984
|
+
...this.lastEnableOptions,
|
|
1985
|
+
...options
|
|
1986
|
+
};
|
|
1987
|
+
if (this.playwrightPage && this.playwrightConsoleHandler && !this.playwrightNetworkMonitor) {
|
|
1988
|
+
await enablePlaywrightCore(this, options);
|
|
1989
|
+
return;
|
|
1990
|
+
}
|
|
1991
|
+
if (this.cdpSession && !this.networkMonitor) {
|
|
1992
|
+
this.networkMonitor = new NetworkMonitor(this.cdpSession);
|
|
1993
|
+
await this.networkMonitor.enable();
|
|
1994
|
+
logger.info("Network monitoring added to existing ConsoleMonitor session");
|
|
1995
|
+
}
|
|
1996
|
+
}
|
|
1997
|
+
async disable() {
|
|
1998
|
+
try {
|
|
1999
|
+
if (this.cdpSession && this.fetchInterceptor) {
|
|
2000
|
+
await this.fetchInterceptor.disable();
|
|
2001
|
+
this.fetchInterceptor = null;
|
|
2002
|
+
}
|
|
2003
|
+
await disableCore(this);
|
|
2004
|
+
} finally {
|
|
2005
|
+
this.fetchInterceptor = null;
|
|
2006
|
+
this.initPromise = void 0;
|
|
2007
|
+
this.contextSwitchPending = false;
|
|
2008
|
+
this.objectCache.clear();
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
async ensureSession() {
|
|
2012
|
+
if (this.contextSwitchPending) {
|
|
2013
|
+
logger.info("ConsoleMonitor context switched, rebinding on demand...");
|
|
2014
|
+
const rebindOptions = { ...this.lastEnableOptions };
|
|
2015
|
+
await this.disable();
|
|
2016
|
+
await this.enable(rebindOptions);
|
|
2017
|
+
return;
|
|
2018
|
+
}
|
|
2019
|
+
if (!this.cdpSession && !this.playwrightPage) {
|
|
2020
|
+
logger.info("ConsoleMonitor CDP session lost, reinitializing...");
|
|
2021
|
+
await this.enable(this.lastEnableOptions);
|
|
2022
|
+
return;
|
|
2023
|
+
}
|
|
2024
|
+
if (this.cdpSession) try {
|
|
2025
|
+
await Promise.race([this.cdpSession.send("Runtime.evaluate", {
|
|
2026
|
+
expression: "1",
|
|
2027
|
+
returnByValue: true
|
|
2028
|
+
}), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("session_unreachable")), 3e3))]);
|
|
2029
|
+
return;
|
|
2030
|
+
} catch {
|
|
2031
|
+
logger.warn("ConsoleMonitor CDP session unresponsive (zombie), reinitializing...");
|
|
2032
|
+
this.cdpSession = null;
|
|
2033
|
+
this.networkMonitor = null;
|
|
2034
|
+
this.fetchInterceptor = null;
|
|
2035
|
+
this.usingManagedTargetSession = false;
|
|
2036
|
+
await this.enable(this.lastEnableOptions);
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
isSessionActive() {
|
|
2040
|
+
return !this.contextSwitchPending && (this.cdpSession !== null || this.playwrightPage !== null);
|
|
2041
|
+
}
|
|
2042
|
+
getLogs(filter) {
|
|
2043
|
+
return getLogsCore(this, filter);
|
|
2044
|
+
}
|
|
2045
|
+
async execute(expression) {
|
|
2046
|
+
await this.ensureSession();
|
|
2047
|
+
try {
|
|
2048
|
+
const result = await cdpSendWithTimeout(this.cdpSession, "Runtime.evaluate", {
|
|
2049
|
+
expression,
|
|
2050
|
+
returnByValue: true
|
|
2051
|
+
});
|
|
2052
|
+
if (result.exceptionDetails) {
|
|
2053
|
+
logger.error("Console execute error:", result.exceptionDetails);
|
|
2054
|
+
throw new Error(result.exceptionDetails.text);
|
|
2055
|
+
}
|
|
2056
|
+
logger.info("Console expression executed");
|
|
2057
|
+
return result.result.value;
|
|
2058
|
+
} catch (error) {
|
|
2059
|
+
logger.error("Console execute failed:", error);
|
|
2060
|
+
throw error;
|
|
2061
|
+
}
|
|
2062
|
+
}
|
|
2063
|
+
clearLogs() {
|
|
2064
|
+
clearLogsCore(this);
|
|
2065
|
+
}
|
|
2066
|
+
getStats() {
|
|
2067
|
+
return getStatsCore(this);
|
|
2068
|
+
}
|
|
2069
|
+
async close() {
|
|
2070
|
+
try {
|
|
2071
|
+
await this.disable();
|
|
2072
|
+
} finally {
|
|
2073
|
+
this.initPromise = void 0;
|
|
2074
|
+
this.objectCache.clear();
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
isNetworkEnabled() {
|
|
2078
|
+
return isNetworkEnabledCore(this);
|
|
2079
|
+
}
|
|
2080
|
+
getNetworkStatus() {
|
|
2081
|
+
return getNetworkStatusCore(this);
|
|
2082
|
+
}
|
|
2083
|
+
getNetworkRequests(filter) {
|
|
2084
|
+
return getNetworkRequestsCore(this, filter);
|
|
2085
|
+
}
|
|
2086
|
+
getNetworkResponses(filter) {
|
|
2087
|
+
return getNetworkResponsesCore(this, filter);
|
|
2088
|
+
}
|
|
2089
|
+
getNetworkActivity(requestId) {
|
|
2090
|
+
return getNetworkActivityCore(this, requestId);
|
|
2091
|
+
}
|
|
2092
|
+
async getResponseBody(requestId) {
|
|
2093
|
+
return getResponseBodyCore(this, requestId);
|
|
2094
|
+
}
|
|
2095
|
+
async getAllJavaScriptResponses() {
|
|
2096
|
+
return getAllJavaScriptResponsesCore(this);
|
|
2097
|
+
}
|
|
2098
|
+
clearNetworkRecords() {
|
|
2099
|
+
clearNetworkRecordsCore(this);
|
|
2100
|
+
}
|
|
2101
|
+
async clearInjectedBuffers() {
|
|
2102
|
+
return clearInjectedBuffersCore(this);
|
|
2103
|
+
}
|
|
2104
|
+
async resetInjectedInterceptors() {
|
|
2105
|
+
return resetInjectedInterceptorsCore(this);
|
|
2106
|
+
}
|
|
2107
|
+
getNetworkStats() {
|
|
2108
|
+
return getNetworkStatsCore(this);
|
|
2109
|
+
}
|
|
2110
|
+
async injectXHRInterceptor(options) {
|
|
2111
|
+
return injectXHRInterceptorCore(this, options);
|
|
2112
|
+
}
|
|
2113
|
+
async injectFetchInterceptor(options) {
|
|
2114
|
+
return injectFetchInterceptorCore(this, options);
|
|
2115
|
+
}
|
|
2116
|
+
async getXHRRequests() {
|
|
2117
|
+
return getXHRRequestsCore(this);
|
|
2118
|
+
}
|
|
2119
|
+
async getFetchRequests() {
|
|
2120
|
+
return getFetchRequestsCore(this);
|
|
2121
|
+
}
|
|
2122
|
+
getExceptions(filter) {
|
|
2123
|
+
return getExceptionsCore(this, filter);
|
|
2124
|
+
}
|
|
2125
|
+
clearExceptions() {
|
|
2126
|
+
clearExceptionsCore(this);
|
|
2127
|
+
}
|
|
2128
|
+
async inspectObject(objectId) {
|
|
2129
|
+
return inspectObjectCore(this, objectId);
|
|
2130
|
+
}
|
|
2131
|
+
clearObjectCache() {
|
|
2132
|
+
clearObjectCacheCore(this);
|
|
2133
|
+
}
|
|
2134
|
+
async enableDynamicScriptMonitoring(options) {
|
|
2135
|
+
return enableDynamicScriptMonitoringCore(this, options);
|
|
2136
|
+
}
|
|
2137
|
+
async clearDynamicScriptBuffer() {
|
|
2138
|
+
return clearDynamicScriptBufferCore(this);
|
|
2139
|
+
}
|
|
2140
|
+
async resetDynamicScriptMonitoring() {
|
|
2141
|
+
return resetDynamicScriptMonitoringCore(this);
|
|
2142
|
+
}
|
|
2143
|
+
async getDynamicScripts() {
|
|
2144
|
+
return getDynamicScriptsCore(this);
|
|
2145
|
+
}
|
|
2146
|
+
async injectFunctionTracer(functionName, options) {
|
|
2147
|
+
return injectFunctionTracerCore(this, functionName, options);
|
|
2148
|
+
}
|
|
2149
|
+
async injectPropertyWatcher(objectPath, propertyName, options) {
|
|
2150
|
+
return injectPropertyWatcherCore(this, objectPath, propertyName, options);
|
|
2151
|
+
}
|
|
2152
|
+
async enableFetchIntercept(rules) {
|
|
2153
|
+
await this.ensureSession();
|
|
2154
|
+
if (!this.cdpSession) throw new Error("No CDP session available for Fetch interception");
|
|
2155
|
+
if (!this.fetchInterceptor) this.fetchInterceptor = new FetchInterceptor(this.cdpSession);
|
|
2156
|
+
return this.fetchInterceptor.enable(rules);
|
|
2157
|
+
}
|
|
2158
|
+
async disableFetchIntercept() {
|
|
2159
|
+
if (!this.fetchInterceptor) return { removedRules: 0 };
|
|
2160
|
+
const result = await this.fetchInterceptor.disable();
|
|
2161
|
+
this.fetchInterceptor = null;
|
|
2162
|
+
return result;
|
|
2163
|
+
}
|
|
2164
|
+
async removeFetchInterceptRule(ruleId) {
|
|
2165
|
+
if (!this.fetchInterceptor) return false;
|
|
2166
|
+
const removed = await this.fetchInterceptor.removeRule(ruleId);
|
|
2167
|
+
if (!this.fetchInterceptor.isEnabled()) this.fetchInterceptor = null;
|
|
2168
|
+
return removed;
|
|
2169
|
+
}
|
|
2170
|
+
getFetchInterceptStatus() {
|
|
2171
|
+
if (!this.fetchInterceptor) return {
|
|
2172
|
+
enabled: false,
|
|
2173
|
+
rules: [],
|
|
2174
|
+
totalHits: 0
|
|
2175
|
+
};
|
|
2176
|
+
return this.fetchInterceptor.listRules();
|
|
2177
|
+
}
|
|
2178
|
+
formatRemoteObject(obj) {
|
|
2179
|
+
if (obj.value !== void 0) return String(obj.value);
|
|
2180
|
+
if (obj.description) return obj.description;
|
|
2181
|
+
if (obj.type === "undefined") return "undefined";
|
|
2182
|
+
if (obj.type === "object" && obj.subtype === "null") return "null";
|
|
2183
|
+
return `[${obj.type}]`;
|
|
2184
|
+
}
|
|
2185
|
+
extractValue(obj) {
|
|
2186
|
+
if (obj.value !== void 0) return obj.value;
|
|
2187
|
+
if (obj.type === "undefined") return;
|
|
2188
|
+
if (obj.type === "object" && obj.subtype === "null") return null;
|
|
2189
|
+
if (obj.objectId) return {
|
|
2190
|
+
__objectId: obj.objectId,
|
|
2191
|
+
__type: obj.type,
|
|
2192
|
+
__description: obj.description
|
|
2193
|
+
};
|
|
2194
|
+
return obj.description || `[${obj.type}]`;
|
|
2195
|
+
}
|
|
2196
|
+
};
|
|
2197
|
+
//#endregion
|
|
2198
|
+
//#region src/modules/monitor/ConsoleMonitor.ts
|
|
2199
|
+
var ConsoleMonitor_exports = /* @__PURE__ */ __exportAll({ ConsoleMonitor: () => ConsoleMonitor });
|
|
2200
|
+
//#endregion
|
|
2201
|
+
export { ConsoleMonitor_exports as t };
|