@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.
Files changed (157) hide show
  1. package/README.md +36 -5
  2. package/README.zh.md +36 -5
  3. package/dist/{AntiCheatDetector-S8VRj-dD.mjs → AntiCheatDetector-BNk-EoBt.mjs} +3 -3
  4. package/dist/{CodeInjector-4Z3ngPoX.mjs → CodeInjector-Cq8q01kp.mjs} +5 -5
  5. package/dist/ConsoleMonitor-CPVQW1Y-.mjs +2201 -0
  6. package/dist/{DarwinAPI-B8hg_yhz.mjs → DarwinAPI-BNPxu0RH.mjs} +1 -1
  7. package/dist/DetailedDataManager-BQQcxh64.mjs +217 -0
  8. package/dist/EventBus-DgPmwpeu.mjs +141 -0
  9. package/dist/EvidenceGraphBridge-SFesNera.mjs +153 -0
  10. package/dist/{ExtensionManager-CZ6IveoV.mjs → ExtensionManager-CWYgw0YW.mjs} +13 -6
  11. package/dist/{FingerprintManager-BVxFJL2-.mjs → FingerprintManager-gzWtkKuf.mjs} +1 -1
  12. package/dist/{HardwareBreakpoint-DK1yjWkV.mjs → HardwareBreakpoint-B9gZCdFP.mjs} +3 -3
  13. package/dist/{HeapAnalyzer-CEbo10xU.mjs → HeapAnalyzer-BLDH0dCv.mjs} +4 -4
  14. package/dist/HookGeneratorBuilders.core.generators.storage-CtcdK78Q.mjs +639 -0
  15. package/dist/InstrumentationSession-CvPC7Jwy.mjs +244 -0
  16. package/dist/{MemoryController-DdtnBdD4.mjs → MemoryController-CbVdCIJF.mjs} +3 -3
  17. package/dist/{MemoryScanSession-RMixN3bX.mjs → MemoryScanSession-BsDZbLYm.mjs} +81 -78
  18. package/dist/{MemoryScanner-QjK4ld0B.mjs → MemoryScanner-Bcpml6II.mjs} +44 -18
  19. package/dist/{NativeMemoryManager.impl-CB6gJ0NM.mjs → NativeMemoryManager.impl-dZtA1ZGn.mjs} +14 -53
  20. package/dist/{NativeMemoryManager.utils-BML4q1ry.mjs → NativeMemoryManager.utils-B-FjA2mJ.mjs} +1 -1
  21. package/dist/{PEAnalyzer-CK0xe0Fs.mjs → PEAnalyzer-D1lzJ_VG.mjs} +2 -2
  22. package/dist/PageController-Bqm2kZ_X.mjs +417 -0
  23. package/dist/{PointerChainEngine-Cd73qu5b.mjs → PointerChainEngine-BOhyVsjx.mjs} +4 -4
  24. package/dist/PrerequisiteError-Dl33Svkz.mjs +20 -0
  25. package/dist/ResponseBuilder-D3iFYx2N.mjs +143 -0
  26. package/dist/ReverseEvidenceGraph-Dlsk94LC.mjs +269 -0
  27. package/dist/ScriptManager-aHHq0X7U.mjs +3000 -0
  28. package/dist/{Speedhack-CeF0XmEz.mjs → Speedhack-CqdIFlQl.mjs} +2 -2
  29. package/dist/{StructureAnalyzer-D4GkMduU.mjs → StructureAnalyzer-DhFaPvRO.mjs} +3 -3
  30. package/dist/ToolCatalog-C0JGZoOm.mjs +582 -0
  31. package/dist/ToolError-jh9whhMd.mjs +15 -0
  32. package/dist/ToolProbe-oC7aPrkv.mjs +45 -0
  33. package/dist/ToolRegistry-BjaF4oNz.mjs +131 -0
  34. package/dist/ToolRouter.policy-BWV67ZK-.mjs +304 -0
  35. package/dist/TraceRecorder-DgxyVbdQ.mjs +519 -0
  36. package/dist/{Win32API-Bc0QnQsN.mjs → Win32API-CePkipZY.mjs} +1 -1
  37. package/dist/{Win32Debug-DUHt9XUn.mjs → Win32Debug-BvKs-gxc.mjs} +2 -2
  38. package/dist/WorkflowEngine-CuvkZtWu.mjs +598 -0
  39. package/dist/analysis-CL9uACt9.mjs +463 -0
  40. package/dist/antidebug-CqDTB_uk.mjs +1081 -0
  41. package/dist/artifactRetention-CFEprwPw.mjs +591 -0
  42. package/dist/artifacts-Bk2-_uPq.mjs +59 -0
  43. package/dist/betterSqlite3-0pqusHHH.mjs +74 -0
  44. package/dist/binary-instrument-CXfpx6fT.mjs +979 -0
  45. package/dist/bind-helpers-xFfRF-qm.mjs +22 -0
  46. package/dist/boringssl-inspector-BH2D3VKc.mjs +180 -0
  47. package/dist/browser-BpOr5PEx.mjs +4082 -0
  48. package/dist/concurrency-Bt0yv1kJ.mjs +41 -0
  49. package/dist/{constants-CCvsN80K.mjs → constants-B0OANIBL.mjs} +88 -46
  50. package/dist/coordination-qUbyF8KU.mjs +259 -0
  51. package/dist/debugger-gnKxRSN0.mjs +1271 -0
  52. package/dist/definitions-6M-eejaT.mjs +53 -0
  53. package/dist/definitions-B18eyf0B.mjs +18 -0
  54. package/dist/definitions-B3QdlrHv.mjs +34 -0
  55. package/dist/definitions-B4rAvHNZ.mjs +63 -0
  56. package/dist/definitions-BB_4jnmy.mjs +37 -0
  57. package/dist/definitions-BMfYXoNC.mjs +43 -0
  58. package/dist/definitions-Beid2EB3.mjs +27 -0
  59. package/dist/definitions-C1UvM5Iy.mjs +126 -0
  60. package/dist/definitions-CXEI7QC72.mjs +216 -0
  61. package/dist/definitions-C_4r7Fo-2.mjs +14 -0
  62. package/dist/definitions-CkFDALoa.mjs +26 -0
  63. package/dist/definitions-Cke7zEb8.mjs +94 -0
  64. package/dist/definitions-ClJLzsJQ.mjs +25 -0
  65. package/dist/definitions-Cq-zroAU.mjs +28 -0
  66. package/dist/definitions-Cy3Sl6gV.mjs +34 -0
  67. package/dist/definitions-D3VsGcvz.mjs +47 -0
  68. package/dist/definitions-DVGfrn7y.mjs +96 -0
  69. package/dist/definitions-LKpC3-nL.mjs +9 -0
  70. package/dist/definitions-bAhHQJq9.mjs +359 -0
  71. package/dist/encoding-Bvz5jLRv.mjs +1065 -0
  72. package/dist/evidence-graph-bridge-C_fv9PuC.mjs +135 -0
  73. package/dist/{factory-CibqTNC8.mjs → factory-DxlGh9Xf.mjs} +37 -52
  74. package/dist/graphql-DYWzJ29s.mjs +1026 -0
  75. package/dist/handlers-9sAbfIg-.mjs +2552 -0
  76. package/dist/handlers-Bl8zkwz1.mjs +2716 -0
  77. package/dist/handlers-C67ktuRN.mjs +710 -0
  78. package/dist/handlers-C87g8oCe.mjs +276 -0
  79. package/dist/handlers-CTsDAO6p.mjs +681 -0
  80. package/dist/handlers-Cgyg6c0U.mjs +645 -0
  81. package/dist/handlers-D6j6yka7.mjs +2124 -0
  82. package/dist/handlers-DdFzXLvF.mjs +446 -0
  83. package/dist/handlers-DeLOCd5m.mjs +799 -0
  84. package/dist/handlers-DlCJN4Td.mjs +757 -0
  85. package/dist/handlers-DxGIq15_2.mjs +917 -0
  86. package/dist/handlers-U6L4xhuF.mjs +585 -0
  87. package/dist/handlers-tB9Mp9ZK.mjs +84 -0
  88. package/dist/handlers-tiy7EIBp.mjs +572 -0
  89. package/dist/handlers.impl-DS0d9fUw.mjs +761 -0
  90. package/dist/hooks-CzCWByww.mjs +898 -0
  91. package/dist/index.mjs +384 -155
  92. package/dist/{logger-BmWzC2lM.mjs → logger-Dh_xb7_2.mjs} +14 -6
  93. package/dist/maintenance-P7ePRXQC.mjs +830 -0
  94. package/dist/manifest-2ToTpjv8.mjs +106 -0
  95. package/dist/manifest-3g71z6Bg.mjs +79 -0
  96. package/dist/manifest-82baTv4U.mjs +45 -0
  97. package/dist/manifest-B3QVVeBS.mjs +82 -0
  98. package/dist/manifest-BB2J8IMJ.mjs +149 -0
  99. package/dist/manifest-BKbgbSiY.mjs +60 -0
  100. package/dist/manifest-Bcf-TJzH.mjs +848 -0
  101. package/dist/manifest-BmtZzQiQ2.mjs +45 -0
  102. package/dist/manifest-Bnd7kqEY.mjs +55 -0
  103. package/dist/manifest-BqQX6OQC2.mjs +65 -0
  104. package/dist/manifest-BqrQ4Tpj.mjs +81 -0
  105. package/dist/manifest-Br4RPFt5.mjs +370 -0
  106. package/dist/manifest-C5qDjysN.mjs +107 -0
  107. package/dist/manifest-C9RT5nk32.mjs +34 -0
  108. package/dist/manifest-CAhOuvSl.mjs +204 -0
  109. package/dist/manifest-CBYWCUBJ.mjs +51 -0
  110. package/dist/manifest-CFADCRa1.mjs +37 -0
  111. package/dist/manifest-CQVhavRF.mjs +114 -0
  112. package/dist/manifest-CT7zZBV1.mjs +48 -0
  113. package/dist/manifest-CV12bcrF.mjs +121 -0
  114. package/dist/manifest-CXsRWjjI.mjs +224 -0
  115. package/dist/manifest-CZLUCfG02.mjs +95 -0
  116. package/dist/manifest-D6phHKFd.mjs +131 -0
  117. package/dist/manifest-DCyjf4n2.mjs +294 -0
  118. package/dist/manifest-DHsnKgP6.mjs +60 -0
  119. package/dist/manifest-Df_dliIe.mjs +55 -0
  120. package/dist/manifest-Dh8WBmEW.mjs +129 -0
  121. package/dist/manifest-DhKRAT8_.mjs +92 -0
  122. package/dist/manifest-DlpTj4ic2.mjs +193 -0
  123. package/dist/manifest-DrbmZcFl2.mjs +253 -0
  124. package/dist/manifest-DuwHjUa5.mjs +70 -0
  125. package/dist/manifest-DzwvxPJX.mjs +38 -0
  126. package/dist/manifest-NXctwWQq.mjs +68 -0
  127. package/dist/manifest-Sc_0JQ13.mjs +418 -0
  128. package/dist/manifest-gZ4s_UtG.mjs +96 -0
  129. package/dist/manifest-qSleDqdO.mjs +1023 -0
  130. package/dist/modules-C184v-S9.mjs +11365 -0
  131. package/dist/mojo-ipc-B_H61Afw.mjs +525 -0
  132. package/dist/network-671Cw6hV.mjs +3346 -0
  133. package/dist/{artifacts-BbdOMET5.mjs → outputPaths-B1uGmrWZ.mjs} +219 -212
  134. package/dist/parse-args-BlRjqlkL.mjs +39 -0
  135. package/dist/platform-WmNn8Sxb.mjs +2070 -0
  136. package/dist/process-QcbIy5Zq.mjs +1401 -0
  137. package/dist/proxy-DqNs0bAd.mjs +170 -0
  138. package/dist/registry-D-6e18lB.mjs +34 -0
  139. package/dist/response-BQVP-xUn.mjs +28 -0
  140. package/dist/server/plugin-api.mjs +2 -2
  141. package/dist/shared-state-board-DV-dpHFJ.mjs +586 -0
  142. package/dist/sourcemap-Dq8ez8vS.mjs +650 -0
  143. package/dist/ssrf-policy-ZaUfvhq7.mjs +166 -0
  144. package/dist/streaming-BUQ0VJsg.mjs +725 -0
  145. package/dist/tool-builder-DCbIC5Eo.mjs +186 -0
  146. package/dist/transform-CiYJfNX0.mjs +1007 -0
  147. package/dist/types-Bx92KJfT.mjs +4 -0
  148. package/dist/wasm-DQTnHDs4.mjs +531 -0
  149. package/dist/workflow-f3xJOcjx.mjs +725 -0
  150. package/package.json +48 -78
  151. package/dist/ExtensionManager-DqUSOamB.mjs +0 -2
  152. package/dist/ToolCatalog-CnwmMIw3.mjs +0 -61483
  153. package/dist/{CacheAdapters-CzFNpD9a.mjs → CacheAdapters-CDe5WPSV.mjs} +0 -0
  154. package/dist/{StealthVerifier-BzBCFiwx.mjs → StealthVerifier-Bo4T3bz8.mjs} +0 -0
  155. package/dist/{VersionDetector-CNXcvD46.mjs → VersionDetector-CwVLVdDM.mjs} +0 -0
  156. package/dist/{formatAddress-ChCSIRWT.mjs → formatAddress-DVkj9kpI.mjs} +0 -0
  157. package/dist/{types-BBjOqye-.mjs → types-CPhOReNX.mjs} +1 -1
@@ -0,0 +1,3346 @@
1
+ import { t as logger } from "./logger-Dh_xb7_2.mjs";
2
+ import { Gt as NETWORK_REPLAY_MAX_REDIRECTS, Wt as NETWORK_HAR_BODY_CONCURRENCY, dt as ICMP_DEFAULT_PACKET_SIZE, ft as ICMP_PROBE_TIMEOUT_MS, pt as ICMP_TRACEROUTE_MAX_HOPS } from "./constants-B0OANIBL.mjs";
3
+ import { t as DetailedDataManager } from "./DetailedDataManager-BQQcxh64.mjs";
4
+ import { a as PerformanceMonitor } from "./modules-C184v-S9.mjs";
5
+ import { n as argEnum, t as argBool } from "./parse-args-BlRjqlkL.mjs";
6
+ import { a as isLoopbackHost, c as isSsrfTarget, i as isLocalSsrfBypassEnabled, l as resolveNetworkTarget, n as hasAuthorizedTargets, o as isNetworkAuthorizationExpired, r as isAuthorizedNetworkTarget, s as isPrivateHost, t as createNetworkAuthorizationPolicy } from "./ssrf-policy-ZaUfvhq7.mjs";
7
+ import { t as R } from "./ResponseBuilder-D3iFYx2N.mjs";
8
+ import "./definitions-CXEI7QC72.mjs";
9
+ import * as http$1 from "node:http";
10
+ import { promises } from "node:fs";
11
+ import koffi from "koffi";
12
+ import * as net from "node:net";
13
+ import * as tls from "node:tls";
14
+ import * as dns from "node:dns/promises";
15
+ import * as https from "node:https";
16
+ import * as http2 from "node:http2";
17
+ //#region src/server/domains/network/handlers.base.types.ts
18
+ /** Resource types excluded by default when no explicit filters are set. */
19
+ const EXCLUDED_RESOURCE_TYPES = new Set([
20
+ "Image",
21
+ "Font",
22
+ "Stylesheet",
23
+ "Media",
24
+ "Manifest",
25
+ "Ping"
26
+ ]);
27
+ /** Priority order for smart sorting (lower = higher priority). */
28
+ const TYPE_SORT_PRIORITY = {
29
+ XHR: 0,
30
+ Fetch: 1,
31
+ Document: 2,
32
+ Script: 3,
33
+ WebSocket: 4,
34
+ EventSource: 5
35
+ };
36
+ const isObjectRecord$1 = (value) => typeof value === "object" && value !== null;
37
+ const isNetworkRequestPayload = (value) => {
38
+ if (!isObjectRecord$1(value)) return false;
39
+ return typeof value.url === "string" && typeof value.method === "string";
40
+ };
41
+ const isNetworkResponsePayload = (value) => {
42
+ if (!isObjectRecord$1(value)) return false;
43
+ return typeof value.status === "number";
44
+ };
45
+ const isFiniteNumber = (value) => typeof value === "number" && Number.isFinite(value);
46
+ const asOptionalString = (value) => typeof value === "string" ? value : void 0;
47
+ const asOptionalBoolean = (value) => typeof value === "boolean" ? value : void 0;
48
+ const asOptionalNumber = (value) => typeof value === "number" && Number.isFinite(value) ? value : void 0;
49
+ const asOptionalStringArray = (value) => {
50
+ if (!Array.isArray(value)) return;
51
+ return value.every((item) => typeof item === "string") ? value : void 0;
52
+ };
53
+ const isCpuProfileNodePayload = (value) => {
54
+ if (!isObjectRecord$1(value)) return false;
55
+ if (value.hitCount !== void 0 && typeof value.hitCount !== "number") return false;
56
+ if (value.callFrame !== void 0 && !isObjectRecord$1(value.callFrame)) return false;
57
+ if (isObjectRecord$1(value.callFrame)) {
58
+ if (value.callFrame.functionName !== void 0 && typeof value.callFrame.functionName !== "string") return false;
59
+ if (value.callFrame.url !== void 0 && typeof value.callFrame.url !== "string") return false;
60
+ if (value.callFrame.lineNumber !== void 0 && typeof value.callFrame.lineNumber !== "number") return false;
61
+ }
62
+ return true;
63
+ };
64
+ const toCpuProfilePayload = (value) => {
65
+ if (!isObjectRecord$1(value)) return null;
66
+ if (!Array.isArray(value.nodes)) return null;
67
+ if (typeof value.startTime !== "number" || typeof value.endTime !== "number") return null;
68
+ if (!value.nodes.every((node) => isCpuProfileNodePayload(node))) return null;
69
+ return {
70
+ nodes: value.nodes,
71
+ samples: Array.isArray(value.samples) ? value.samples : void 0,
72
+ startTime: value.startTime,
73
+ endTime: value.endTime
74
+ };
75
+ };
76
+ //#endregion
77
+ //#region src/server/domains/network/handlers/shared.ts
78
+ function getDetailedDataManager() {
79
+ return DetailedDataManager.getInstance();
80
+ }
81
+ function emitEvent(eventBus, event, payload) {
82
+ eventBus?.emit(event, payload);
83
+ }
84
+ function parseBooleanArg(value, defaultValue) {
85
+ if (typeof value === "boolean") return value;
86
+ if (typeof value === "number") {
87
+ if (value === 1) return true;
88
+ if (value === 0) return false;
89
+ return defaultValue;
90
+ }
91
+ if (typeof value === "string") {
92
+ const normalized = value.trim().toLowerCase();
93
+ if ([
94
+ "true",
95
+ "1",
96
+ "yes",
97
+ "on"
98
+ ].includes(normalized)) return true;
99
+ if ([
100
+ "false",
101
+ "0",
102
+ "no",
103
+ "off"
104
+ ].includes(normalized)) return false;
105
+ }
106
+ return defaultValue;
107
+ }
108
+ function parseNumberArg(value, options) {
109
+ let parsed;
110
+ if (typeof value === "number" && Number.isFinite(value)) parsed = value;
111
+ else if (typeof value === "string") {
112
+ const trimmed = value.trim();
113
+ if (trimmed.length > 0) {
114
+ const n = Number(trimmed);
115
+ if (Number.isFinite(n)) parsed = n;
116
+ }
117
+ }
118
+ if (parsed === void 0) parsed = options.defaultValue;
119
+ if (options.integer) parsed = Math.trunc(parsed);
120
+ if (typeof options.min === "number") parsed = Math.max(options.min, parsed);
121
+ if (typeof options.max === "number") parsed = Math.min(options.max, parsed);
122
+ return parsed;
123
+ }
124
+ //#endregion
125
+ //#region src/server/domains/network/handlers/core-handlers.ts
126
+ /**
127
+ * Core network handlers — enable/disable/status/requests/response/stats.
128
+ *
129
+ * Extracted from NetworkHandlersCore (handlers.base.core.ts).
130
+ */
131
+ var CoreHandlers = class {
132
+ detailedDataManager = getDetailedDataManager();
133
+ constructor(deps) {
134
+ this.deps = deps;
135
+ }
136
+ async handleNetworkMonitor(args) {
137
+ const action = String(args["action"] ?? "");
138
+ switch (action) {
139
+ case "enable": return this.handleNetworkEnable(args);
140
+ case "disable": return this.handleNetworkDisable(args);
141
+ case "status": return this.handleNetworkGetStatus(args);
142
+ default: return R.fail(`Invalid generic action parameter: ${action}. Expected enable, disable, status.`).json();
143
+ }
144
+ }
145
+ async handleNetworkEnable(args) {
146
+ try {
147
+ const enableExceptions = parseBooleanArg(args.enableExceptions, true);
148
+ await this.deps.consoleMonitor.enable({
149
+ enableNetwork: true,
150
+ enableExceptions
151
+ });
152
+ const status = this.deps.consoleMonitor.getNetworkStatus();
153
+ return R.ok().merge({
154
+ message: " Network monitoring enabled successfully",
155
+ enabled: status.enabled,
156
+ cdpSessionActive: status.cdpSessionActive,
157
+ listenerCount: status.listenerCount,
158
+ usage: {
159
+ step1: "Network monitoring is now active",
160
+ step2: "Navigate to a page using page_navigate tool",
161
+ step3: "Use network_get_requests to retrieve captured requests",
162
+ step4: "Use network_get_response_body to get response content"
163
+ },
164
+ important: "Network monitoring must be enabled BEFORE navigating to capture requests"
165
+ }).json();
166
+ } catch (error) {
167
+ return R.fail(error).json();
168
+ }
169
+ }
170
+ async handleNetworkDisable(_args) {
171
+ try {
172
+ await this.deps.consoleMonitor.disable();
173
+ return R.ok().set("message", "Network monitoring disabled").json();
174
+ } catch (error) {
175
+ return R.fail(error).json();
176
+ }
177
+ }
178
+ async handleNetworkGetStatus(_args) {
179
+ try {
180
+ const status = this.deps.consoleMonitor.getNetworkStatus();
181
+ if (!status.enabled) return R.fail(" Network monitoring is NOT enabled").merge({
182
+ enabled: false,
183
+ nextSteps: {
184
+ step1: "Call network_enable tool to start monitoring",
185
+ step2: "Then navigate to a page using page_navigate",
186
+ step3: "Finally use network_get_requests to see captured requests"
187
+ },
188
+ example: "network_enable -> page_navigate -> network_get_requests"
189
+ }).json();
190
+ return R.ok().merge({
191
+ enabled: true,
192
+ message: ` Network monitoring is active. Captured ${status.requestCount} requests and ${status.responseCount} responses.`,
193
+ requestCount: status.requestCount,
194
+ responseCount: status.responseCount,
195
+ listenerCount: status.listenerCount,
196
+ cdpSessionActive: status.cdpSessionActive,
197
+ nextSteps: status.requestCount === 0 ? {
198
+ hint: "No requests captured yet",
199
+ action: "Navigate to a page using page_navigate to capture network traffic"
200
+ } : {
201
+ hint: `${status.requestCount} requests captured`,
202
+ action: "Use network_get_requests to retrieve them"
203
+ }
204
+ }).json();
205
+ } catch (error) {
206
+ return R.fail(error).json();
207
+ }
208
+ }
209
+ async handleNetworkGetRequests(args) {
210
+ try {
211
+ const autoEnable = parseBooleanArg(args.autoEnable, true);
212
+ const enableExceptions = parseBooleanArg(args.enableExceptions, true);
213
+ const networkState = await this.ensureNetworkEnabled({
214
+ autoEnable,
215
+ enableExceptions
216
+ });
217
+ if (!networkState.enabled) return this.buildNotEnabledResponse(autoEnable, networkState.error);
218
+ const url = asOptionalString(args.url);
219
+ const urlRegex = asOptionalString(args.urlRegex);
220
+ const method = asOptionalString(args.method);
221
+ const sinceTimestamp = isFiniteNumber(args.sinceTimestamp) ? args.sinceTimestamp : void 0;
222
+ const sinceRequestId = asOptionalString(args.sinceRequestId);
223
+ const tail = isFiniteNumber(args.tail) && args.tail > 0 ? Math.floor(args.tail) : void 0;
224
+ const limit = parseNumberArg(args.limit, {
225
+ defaultValue: 100,
226
+ min: 1,
227
+ max: 1e3,
228
+ integer: true
229
+ });
230
+ const offset = parseNumberArg(args.offset, {
231
+ defaultValue: 0,
232
+ min: 0,
233
+ integer: true
234
+ });
235
+ const requests = this.deps.consoleMonitor.getNetworkRequests().filter((req) => isNetworkRequestPayload(req)).map((r) => r);
236
+ if (requests.length === 0) return this.buildEmptyRequestsResponse(networkState.autoEnabled);
237
+ const result = this.applyRequestFilters(requests, {
238
+ url,
239
+ urlRegex,
240
+ method,
241
+ sinceTimestamp,
242
+ sinceRequestId,
243
+ tail,
244
+ limit,
245
+ offset
246
+ });
247
+ const processedResult = this.detailedDataManager.smartHandle(result.finalPayload, 25600);
248
+ return R.ok().merge(processedResult).json();
249
+ } catch (error) {
250
+ return R.fail(error).json();
251
+ }
252
+ }
253
+ async handleNetworkGetResponseBody(args) {
254
+ try {
255
+ const requestId = asOptionalString(args.requestId) || "";
256
+ const maxSize = parseNumberArg(args.maxSize, {
257
+ defaultValue: 1e5,
258
+ min: 1024,
259
+ max: 20 * 1024 * 1024,
260
+ integer: true
261
+ });
262
+ const returnSummary = parseBooleanArg(args.returnSummary, false);
263
+ const retries = parseNumberArg(args.retries, {
264
+ defaultValue: 3,
265
+ min: 0,
266
+ max: 10,
267
+ integer: true
268
+ });
269
+ const retryIntervalMs = parseNumberArg(args.retryIntervalMs, {
270
+ defaultValue: 500,
271
+ min: 50,
272
+ max: 5e3,
273
+ integer: true
274
+ });
275
+ const autoEnable = parseBooleanArg(args.autoEnable, false);
276
+ const enableExceptions = parseBooleanArg(args.enableExceptions, true);
277
+ if (!requestId) return R.fail("requestId parameter is required").set("hint", "Get requestId from network_get_requests tool").json();
278
+ const networkState = await this.ensureNetworkEnabled({
279
+ autoEnable,
280
+ enableExceptions
281
+ });
282
+ if (!networkState.enabled) return R.fail("Network monitoring is not enabled").merge({
283
+ hint: autoEnable ? "Auto-enable failed. Check active page and call network_enable manually." : "Use network_enable tool first, or set autoEnable=true",
284
+ detail: networkState.error
285
+ }).json();
286
+ let body = null;
287
+ let attemptsMade = 0;
288
+ for (let attempt = 0; attempt <= retries; attempt += 1) {
289
+ attemptsMade = attempt + 1;
290
+ body = await this.deps.consoleMonitor.getResponseBody(requestId);
291
+ if (body) break;
292
+ if (attempt < retries) await new Promise((resolve) => setTimeout(resolve, retryIntervalMs));
293
+ }
294
+ if (!body) return R.fail(`No response body found for requestId: ${requestId}`).merge({
295
+ hint: "The request may not have completed yet, or the requestId is invalid",
296
+ attempts: attemptsMade,
297
+ waitedMs: retries * retryIntervalMs,
298
+ retryConfig: {
299
+ retries,
300
+ retryIntervalMs
301
+ }
302
+ }).json();
303
+ return this.buildResponseBodyResult(requestId, body, attemptsMade, maxSize, returnSummary);
304
+ } catch (error) {
305
+ return R.fail(error).json();
306
+ }
307
+ }
308
+ async handleNetworkGetStats(_args) {
309
+ try {
310
+ if (!this.deps.consoleMonitor.isNetworkEnabled()) return R.fail("Network monitoring is not enabled").set("hint", "Use network_enable tool first").json();
311
+ const requests = this.deps.consoleMonitor.getNetworkRequests().filter((req) => isNetworkRequestPayload(req));
312
+ const responses = this.deps.consoleMonitor.getNetworkResponses().filter(isNetworkResponsePayload);
313
+ const byMethod = {};
314
+ requests.forEach((req) => {
315
+ byMethod[req.method] = (byMethod[req.method] || 0) + 1;
316
+ });
317
+ const byStatus = {};
318
+ responses.forEach((res) => {
319
+ byStatus[res.status] = (byStatus[res.status] || 0) + 1;
320
+ });
321
+ const byType = {};
322
+ requests.forEach((req) => {
323
+ const type = req.type || "unknown";
324
+ byType[type] = (byType[type] || 0) + 1;
325
+ });
326
+ const timestamps = requests.map((r) => r.timestamp).filter((t) => isFiniteNumber(t));
327
+ const timeStats = timestamps.length > 0 ? {
328
+ earliest: Math.min(...timestamps),
329
+ latest: Math.max(...timestamps),
330
+ duration: Math.max(...timestamps) - Math.min(...timestamps)
331
+ } : null;
332
+ return R.ok().set("stats", {
333
+ totalRequests: requests.length,
334
+ totalResponses: responses.length,
335
+ byMethod,
336
+ byStatus,
337
+ byType,
338
+ timeStats,
339
+ monitoringEnabled: true
340
+ }).json();
341
+ } catch (error) {
342
+ return R.fail(error).json();
343
+ }
344
+ }
345
+ async ensureNetworkEnabled(options) {
346
+ if (this.deps.consoleMonitor.isNetworkEnabled()) return {
347
+ enabled: true,
348
+ autoEnabled: false
349
+ };
350
+ if (!options.autoEnable) return {
351
+ enabled: false,
352
+ autoEnabled: false
353
+ };
354
+ try {
355
+ await this.deps.consoleMonitor.enable({
356
+ enableNetwork: true,
357
+ enableExceptions: options.enableExceptions
358
+ });
359
+ return {
360
+ enabled: this.deps.consoleMonitor.isNetworkEnabled(),
361
+ autoEnabled: true
362
+ };
363
+ } catch (error) {
364
+ return {
365
+ enabled: false,
366
+ autoEnabled: false,
367
+ error: error instanceof Error ? error.message : String(error)
368
+ };
369
+ }
370
+ }
371
+ buildNotEnabledResponse(autoEnable, error) {
372
+ if (autoEnable && error) return R.fail("Failed to auto-enable network monitoring").merge({
373
+ detail: error,
374
+ solution: {
375
+ step1: "Ensure browser page is active and reachable",
376
+ step2: "Call network_enable manually",
377
+ step3: "Navigate to target page: page_navigate(url)",
378
+ step4: "Get requests: network_get_requests"
379
+ }
380
+ }).json();
381
+ return R.fail(" Network monitoring is not enabled").merge({
382
+ requests: [],
383
+ total: 0,
384
+ solution: {
385
+ step1: "Enable network monitoring: network_enable",
386
+ step2: "Navigate to target page: page_navigate(url)",
387
+ step3: "Get requests: network_get_requests"
388
+ },
389
+ tip: "Set autoEnable=true to auto-enable monitoring in this call"
390
+ }).json();
391
+ }
392
+ buildEmptyRequestsResponse(autoEnabled) {
393
+ return R.ok().merge({
394
+ message: "No network requests captured yet",
395
+ requests: [],
396
+ total: 0,
397
+ hint: "Network monitoring is enabled, but no requests have been captured",
398
+ possibleReasons: [
399
+ "1. You haven't navigated to any page yet (use page_navigate)",
400
+ "2. The page has already loaded before network monitoring was enabled",
401
+ "3. The page doesn't make any network requests",
402
+ "4. The page uses frontend-wrapped fetch/XHR not captured by CDP"
403
+ ],
404
+ recommended_actions: [
405
+ "console_inject_fetch_interceptor() — capture frontend-wrapped fetch calls (SPAs, React, Vue)",
406
+ "console_inject_xhr_interceptor() — capture XMLHttpRequest calls",
407
+ "page_navigate(url, enableNetworkMonitoring=true) — re-navigate with monitoring enabled"
408
+ ],
409
+ nextAction: "Call console_inject_fetch_interceptor(), then re-navigate or trigger the target action",
410
+ monitoring: { autoEnabled }
411
+ }).json();
412
+ }
413
+ applyRequestFilters(requests, filters) {
414
+ const { url, urlRegex, method, sinceTimestamp, sinceRequestId, tail, limit, offset } = filters;
415
+ const originalCount = requests.length;
416
+ const allUrls = requests.map((r) => r.url);
417
+ const hasAnyFilter = !!(url || urlRegex || method && method.toUpperCase() !== "ALL" || sinceTimestamp || sinceRequestId || tail);
418
+ let excludedStaticCount = 0;
419
+ if (!hasAnyFilter) {
420
+ const beforeTypeFilter = requests.length;
421
+ requests = requests.filter((r) => !r.type || !EXCLUDED_RESOURCE_TYPES.has(r.type));
422
+ excludedStaticCount = beforeTypeFilter - requests.length;
423
+ }
424
+ if (sinceRequestId) {
425
+ const idx = requests.findIndex((r) => r.requestId === sinceRequestId);
426
+ if (idx >= 0) requests = requests.slice(idx + 1);
427
+ }
428
+ if (sinceTimestamp !== void 0) requests = requests.filter((r) => (r.timestamp ?? 0) > sinceTimestamp);
429
+ if (urlRegex) {
430
+ if (urlRegex.length > 500) throw new Error("urlRegex too long (max 500 characters)");
431
+ const re = new RegExp(urlRegex, "i");
432
+ if (requests.length > 0) {
433
+ const start = performance.now();
434
+ re.test(requests[0].url);
435
+ const elapsed = performance.now() - start;
436
+ if (elapsed > 100) throw new Error(`urlRegex pattern is too expensive (${elapsed.toFixed(0)}ms on first URL). Use a simpler pattern.`);
437
+ }
438
+ requests = requests.filter((req) => re.test(req.url));
439
+ } else if (url) {
440
+ const urlLower = url.toLowerCase();
441
+ requests = requests.filter((req) => req.url.toLowerCase().includes(urlLower));
442
+ }
443
+ if (method && method.toUpperCase() !== "ALL") requests = requests.filter((req) => req.method.toUpperCase() === method.toUpperCase());
444
+ if (tail !== void 0 && requests.length > tail) requests = requests.slice(-tail);
445
+ requests.sort((a, b) => (TYPE_SORT_PRIORITY[a.type ?? ""] ?? 6) - (TYPE_SORT_PRIORITY[b.type ?? ""] ?? 6));
446
+ const beforeLimit = requests.length;
447
+ requests = requests.slice(offset, offset + limit);
448
+ const hasMore = offset + requests.length < beforeLimit;
449
+ const filterMiss = beforeLimit === 0 && originalCount > 0 && !!(url || method && method.toUpperCase() !== "ALL");
450
+ const urlSamples = filterMiss ? allUrls.slice(0, 10).map((u) => u.substring(0, 120)) : void 0;
451
+ return { finalPayload: {
452
+ message: ` Retrieved ${requests.length} network request(s)`,
453
+ requests,
454
+ total: requests.length,
455
+ page: {
456
+ offset,
457
+ limit,
458
+ returned: requests.length,
459
+ totalAfterFilter: beforeLimit,
460
+ hasMore,
461
+ nextOffset: hasMore ? offset + requests.length : null
462
+ },
463
+ stats: {
464
+ totalCaptured: originalCount,
465
+ afterFilter: beforeLimit,
466
+ returned: requests.length,
467
+ truncated: beforeLimit > offset + limit
468
+ },
469
+ filtered: hasAnyFilter,
470
+ filters: {
471
+ url,
472
+ urlRegex,
473
+ method,
474
+ sinceTimestamp,
475
+ sinceRequestId,
476
+ tail,
477
+ limit,
478
+ offset
479
+ },
480
+ monitoring: {},
481
+ ...filterMiss && {
482
+ filterMiss: true,
483
+ hint: `URL filter "${url}" matched 0 of ${originalCount} captured requests. Check urlSamples to verify the correct filter substring.`,
484
+ urlSamples
485
+ },
486
+ tip: requests.length > 0 ? "Use network_get_response_body(requestId) to get response content" : void 0,
487
+ ...excludedStaticCount > 0 && {
488
+ staticResourcesExcluded: excludedStaticCount,
489
+ staticFilterNote: `${excludedStaticCount} static resources (Image/Font/Stylesheet/Media) excluded by default. Set any filter to include all types.`
490
+ },
491
+ ...originalCount > 100 && !hasAnyFilter && { optimizationHint: `${originalCount} requests captured. Use url/method filters to reduce payload size.` }
492
+ } };
493
+ }
494
+ buildResponseBodyResult(requestId, body, attemptsMade, maxSize, returnSummary) {
495
+ const originalSize = body.body.length;
496
+ const isTooLarge = originalSize > maxSize;
497
+ if (returnSummary || isTooLarge) {
498
+ const preview = body.body.substring(0, 500);
499
+ return R.ok().merge({
500
+ requestId,
501
+ attempts: attemptsMade,
502
+ summary: {
503
+ size: originalSize,
504
+ sizeKB: (originalSize / 1024).toFixed(2),
505
+ base64Encoded: body.base64Encoded,
506
+ preview: preview + (originalSize > 500 ? "..." : ""),
507
+ truncated: isTooLarge,
508
+ reason: isTooLarge ? `Response too large (${(originalSize / 1024).toFixed(2)} KB > ${(maxSize / 1024).toFixed(2)} KB)` : "Summary mode enabled"
509
+ },
510
+ tip: isTooLarge ? "Use collect_code tool to collect and compress this script, or increase maxSize parameter" : "Set returnSummary=false to get full body"
511
+ }).json();
512
+ }
513
+ return R.ok().merge({
514
+ requestId,
515
+ attempts: attemptsMade,
516
+ body: body.body,
517
+ base64Encoded: body.base64Encoded,
518
+ size: originalSize,
519
+ sizeKB: (originalSize / 1024).toFixed(2)
520
+ }).json();
521
+ }
522
+ };
523
+ //#endregion
524
+ //#region src/server/domains/network/handlers/performance-handlers.ts
525
+ var PerformanceHandlers = class {
526
+ constructor(deps) {
527
+ this.deps = deps;
528
+ }
529
+ async handlePerformanceGetMetrics(args) {
530
+ try {
531
+ const includeTimeline = args.includeTimeline === true;
532
+ const monitor = this.deps.getPerformanceMonitor();
533
+ const metrics = await monitor.getPerformanceMetrics();
534
+ const builder = R.ok().set("metrics", metrics);
535
+ if (includeTimeline) builder.set("timeline", await monitor.getPerformanceTimeline());
536
+ return builder.json();
537
+ } catch (error) {
538
+ return R.fail(error).json();
539
+ }
540
+ }
541
+ async handlePerformanceCoverage(args) {
542
+ return argEnum(args, "action", new Set(["start", "stop"])) === "stop" ? this.handlePerformanceStopCoverage(args) : this.handlePerformanceStartCoverage(args);
543
+ }
544
+ async handlePerformanceStartCoverage(_args) {
545
+ try {
546
+ await this.deps.getPerformanceMonitor().startCoverage();
547
+ return R.ok().set("message", "Code coverage collection started").json();
548
+ } catch (error) {
549
+ return R.fail(error).json();
550
+ }
551
+ }
552
+ async handlePerformanceStopCoverage(_args) {
553
+ try {
554
+ const coverage = await this.deps.getPerformanceMonitor().stopCoverage();
555
+ const avgCoverage = coverage.length > 0 ? coverage.reduce((sum, info) => sum + info.coveragePercentage, 0) / coverage.length : 0;
556
+ return R.ok().merge({
557
+ coverage,
558
+ totalScripts: coverage.length,
559
+ avgCoverage
560
+ }).json();
561
+ } catch (error) {
562
+ return R.fail(error).json();
563
+ }
564
+ }
565
+ async handlePerformanceTakeHeapSnapshot(_args) {
566
+ try {
567
+ const snapshotSize = await this.deps.getPerformanceMonitor().takeHeapSnapshot();
568
+ return R.ok().merge({
569
+ snapshotSize,
570
+ message: "Heap snapshot taken (data too large to return, saved internally)"
571
+ }).json();
572
+ } catch (error) {
573
+ return R.fail(error).json();
574
+ }
575
+ }
576
+ async handlePerformanceTraceStart(args) {
577
+ try {
578
+ const monitor = this.deps.getPerformanceMonitor();
579
+ const categories = asOptionalStringArray(args.categories);
580
+ const screenshots = asOptionalBoolean(args.screenshots);
581
+ await monitor.startTracing({
582
+ categories,
583
+ screenshots
584
+ });
585
+ return R.ok().set("message", "Performance tracing started. Call performance_trace_stop to save the trace.").json();
586
+ } catch (error) {
587
+ return R.fail(error).json();
588
+ }
589
+ }
590
+ async handlePerformanceTraceStop(args) {
591
+ try {
592
+ const monitor = this.deps.getPerformanceMonitor();
593
+ const artifactPath = asOptionalString(args.artifactPath);
594
+ const result = await monitor.stopTracing({ artifactPath });
595
+ return R.ok().merge({
596
+ artifactPath: result.artifactPath,
597
+ eventCount: result.eventCount,
598
+ sizeBytes: result.sizeBytes,
599
+ sizeKB: (result.sizeBytes / 1024).toFixed(1),
600
+ hint: "Open the trace file in Chrome DevTools -> Performance tab -> Load profile"
601
+ }).json();
602
+ } catch (error) {
603
+ return R.fail(error).json();
604
+ }
605
+ }
606
+ async handleProfilerCpuStart(_args) {
607
+ try {
608
+ await this.deps.getPerformanceMonitor().startCPUProfiling();
609
+ return R.ok().set("message", "CPU profiling started. Call profiler_cpu_stop to save the profile.").json();
610
+ } catch (error) {
611
+ return R.fail(error).json();
612
+ }
613
+ }
614
+ async handleProfilerCpuStop(args) {
615
+ try {
616
+ const profileRaw = await this.deps.getPerformanceMonitor().stopCPUProfiling();
617
+ const profile = toCpuProfilePayload(profileRaw) || profileRaw;
618
+ const { writeFile } = await import("node:fs/promises");
619
+ const { resolveArtifactPath } = await import("./artifacts-Bk2-_uPq.mjs").then((n) => n.t);
620
+ const artifactPath = asOptionalString(args.artifactPath);
621
+ const profileJson = JSON.stringify(profile, null, 2);
622
+ let savedPath;
623
+ if (artifactPath) {
624
+ await writeFile(artifactPath, profileJson, "utf-8");
625
+ savedPath = artifactPath;
626
+ } else {
627
+ const { absolutePath, displayPath } = await resolveArtifactPath({
628
+ category: "profiles",
629
+ toolName: "cpu-profile",
630
+ ext: "cpuprofile"
631
+ });
632
+ await writeFile(absolutePath, profileJson, "utf-8");
633
+ savedPath = displayPath;
634
+ }
635
+ const hotFunctions = profile.nodes.filter((n) => (n.hitCount || 0) > 0).toSorted((a, b) => (b.hitCount || 0) - (a.hitCount || 0)).slice(0, 20).map((n) => ({
636
+ functionName: n.callFrame?.functionName || "(anonymous)",
637
+ url: n.callFrame?.url,
638
+ line: n.callFrame?.lineNumber,
639
+ hitCount: n.hitCount
640
+ }));
641
+ return R.ok().merge({
642
+ artifactPath: savedPath,
643
+ totalNodes: profile.nodes.length,
644
+ totalSamples: profile.samples?.length || 0,
645
+ durationMs: profile.endTime - profile.startTime,
646
+ hotFunctions,
647
+ hint: "Open the .cpuprofile file in Chrome DevTools -> Performance tab"
648
+ }).json();
649
+ } catch (error) {
650
+ return R.fail(error).json();
651
+ }
652
+ }
653
+ async handleProfilerHeapSamplingStart(args) {
654
+ try {
655
+ const monitor = this.deps.getPerformanceMonitor();
656
+ const samplingInterval = asOptionalNumber(args.samplingInterval);
657
+ await monitor.startHeapSampling({ samplingInterval });
658
+ return R.ok().set("message", "Heap sampling started. Call profiler_heap_sampling_stop to save the report.").json();
659
+ } catch (error) {
660
+ return R.fail(error).json();
661
+ }
662
+ }
663
+ async handleProfilerHeapSamplingStop(args) {
664
+ try {
665
+ const monitor = this.deps.getPerformanceMonitor();
666
+ const artifactPath = asOptionalString(args.artifactPath);
667
+ const topN = asOptionalNumber(args.topN);
668
+ const result = await monitor.stopHeapSampling({
669
+ artifactPath,
670
+ topN
671
+ });
672
+ return R.ok().merge({
673
+ artifactPath: result.artifactPath,
674
+ sampleCount: result.sampleCount,
675
+ topAllocations: result.topAllocations
676
+ }).json();
677
+ } catch (error) {
678
+ return R.fail(error).json();
679
+ }
680
+ }
681
+ };
682
+ //#endregion
683
+ //#region src/server/domains/network/handlers/console-handlers.ts
684
+ /**
685
+ * Console exception, interceptor, tracer, and monitoring handlers.
686
+ *
687
+ * Extracted from AdvancedHandlersBase (handlers.base.ts).
688
+ */
689
+ var ConsoleHandlers = class {
690
+ constructor(deps) {
691
+ this.deps = deps;
692
+ }
693
+ async handleConsoleGetExceptions(args) {
694
+ try {
695
+ const url = asOptionalString(args.url);
696
+ const limit = parseNumberArg(args.limit, {
697
+ defaultValue: 50,
698
+ min: 1,
699
+ max: 1e3,
700
+ integer: true
701
+ });
702
+ let exceptions = this.deps.consoleMonitor.getExceptions();
703
+ if (url) exceptions = exceptions.filter((ex) => ex.url?.includes(url));
704
+ exceptions = exceptions.slice(0, limit);
705
+ return R.ok().merge({
706
+ exceptions,
707
+ total: exceptions.length
708
+ }).json();
709
+ } catch (error) {
710
+ return R.fail(error).json();
711
+ }
712
+ }
713
+ async handleConsoleInjectScriptMonitor(args) {
714
+ try {
715
+ const persistent = argBool(args, "persistent", false);
716
+ await this.deps.consoleMonitor.enableDynamicScriptMonitoring({ persistent });
717
+ return R.ok().set("message", persistent ? "Dynamic script monitoring enabled (persistent — survives navigations)" : "Dynamic script monitoring enabled").json();
718
+ } catch (error) {
719
+ return R.fail(error).json();
720
+ }
721
+ }
722
+ async handleConsoleInjectXhrInterceptor(args) {
723
+ try {
724
+ const persistent = argBool(args, "persistent", false);
725
+ await this.deps.consoleMonitor.injectXHRInterceptor({ persistent });
726
+ return R.ok().set("message", persistent ? "XHR interceptor injected (persistent)" : "XHR interceptor injected").json();
727
+ } catch (error) {
728
+ return R.fail(error).json();
729
+ }
730
+ }
731
+ async handleConsoleInjectFetchInterceptor(args) {
732
+ try {
733
+ const persistent = argBool(args, "persistent", false);
734
+ await this.deps.consoleMonitor.injectFetchInterceptor({ persistent });
735
+ return R.ok().set("message", persistent ? "Fetch interceptor injected (persistent)" : "Fetch interceptor injected").json();
736
+ } catch (error) {
737
+ return R.fail(error).json();
738
+ }
739
+ }
740
+ async handleConsoleClearInjectedBuffers(_args) {
741
+ try {
742
+ const result = await this.deps.consoleMonitor.clearInjectedBuffers();
743
+ return R.ok().merge({
744
+ message: "Injected buffers cleared",
745
+ ...result
746
+ }).json();
747
+ } catch (error) {
748
+ return R.fail(error).json();
749
+ }
750
+ }
751
+ async handleConsoleResetInjectedInterceptors(_args) {
752
+ try {
753
+ const result = await this.deps.consoleMonitor.resetInjectedInterceptors();
754
+ return R.ok().merge({
755
+ message: "Injected interceptors/monitors reset",
756
+ ...result
757
+ }).json();
758
+ } catch (error) {
759
+ return R.fail(error).json();
760
+ }
761
+ }
762
+ async handleConsoleInjectFunctionTracer(args) {
763
+ try {
764
+ const functionName = asOptionalString(args.functionName) || "";
765
+ if (!functionName) return R.fail("functionName is required").json();
766
+ const persistent = argBool(args, "persistent", false);
767
+ await this.deps.consoleMonitor.injectFunctionTracer(functionName, { persistent });
768
+ return R.ok().set("message", persistent ? `Function tracer injected for: ${functionName} (persistent — survives navigations)` : `Function tracer injected for: ${functionName}`).json();
769
+ } catch (error) {
770
+ return R.fail(error).json();
771
+ }
772
+ }
773
+ };
774
+ //#endregion
775
+ //#region src/server/domains/network/auth-extractor.ts
776
+ const AUTH_HEADER_KEYS = [
777
+ "authorization",
778
+ "cookie",
779
+ "x-token",
780
+ "x-auth-token",
781
+ "x-access-token",
782
+ "x-api-key",
783
+ "x-signature",
784
+ "x-sign",
785
+ "x-csrf-token"
786
+ ];
787
+ const TOKEN_BODY_KEYS = /^(token|access_token|refresh_token|sign|signature|auth|jwt|api_key|apikey|key|secret)$/i;
788
+ const JWT_RE = /^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/;
789
+ const BEARER_RE = /^Bearer\s+\S+/i;
790
+ function maskSecret(raw) {
791
+ const trimmed = raw.trim();
792
+ if (trimmed.length <= 12) return "***";
793
+ return `${trimmed.slice(0, 6)}***${trimmed.slice(-4)}`;
794
+ }
795
+ function scoreValue(value) {
796
+ const v = value.trim();
797
+ if (BEARER_RE.test(v)) return .95;
798
+ if (JWT_RE.test(v)) return .9;
799
+ if (v.length > 20 && /^[A-Za-z0-9+/=_-]+$/.test(v)) return .7;
800
+ if (v.length > 10) return .5;
801
+ return .3;
802
+ }
803
+ function extractAuthFromRequests(requests) {
804
+ const findings = [];
805
+ const seen = /* @__PURE__ */ new Set();
806
+ for (const req of requests) {
807
+ const headers = req.headers ?? {};
808
+ for (const [k, v] of Object.entries(headers)) {
809
+ const lk = k.toLowerCase();
810
+ if (!AUTH_HEADER_KEYS.includes(lk)) continue;
811
+ if (!v || v.length < 4) continue;
812
+ if (lk === "cookie") {
813
+ for (const part of v.split(";")) {
814
+ const eqIdx = part.indexOf("=");
815
+ if (eqIdx === -1) continue;
816
+ const name = part.slice(0, eqIdx).trim();
817
+ const val = part.slice(eqIdx + 1).trim();
818
+ if (!val || val.length < 8) continue;
819
+ const dedupeKey = `cookie:${name}:${val.slice(0, 8)}`;
820
+ if (seen.has(dedupeKey)) continue;
821
+ seen.add(dedupeKey);
822
+ findings.push({
823
+ header: `cookie[${name}]`,
824
+ value_masked: maskSecret(val),
825
+ request_url: req.url,
826
+ confidence: scoreValue(val),
827
+ source: "cookie"
828
+ });
829
+ }
830
+ continue;
831
+ }
832
+ const dedupeKey = `header:${lk}:${v.slice(0, 8)}`;
833
+ if (seen.has(dedupeKey)) continue;
834
+ seen.add(dedupeKey);
835
+ findings.push({
836
+ header: k,
837
+ value_masked: maskSecret(v),
838
+ request_url: req.url,
839
+ confidence: scoreValue(v),
840
+ source: "header"
841
+ });
842
+ }
843
+ try {
844
+ const u = new URL(req.url);
845
+ for (const [k, v] of u.searchParams.entries()) {
846
+ if (!TOKEN_BODY_KEYS.test(k)) continue;
847
+ if (!v || v.length < 8) continue;
848
+ const dedupeKey = `query:${k}:${v.slice(0, 8)}`;
849
+ if (seen.has(dedupeKey)) continue;
850
+ seen.add(dedupeKey);
851
+ findings.push({
852
+ header: k,
853
+ value_masked: maskSecret(v),
854
+ request_url: req.url,
855
+ confidence: scoreValue(v) * .9,
856
+ source: "query"
857
+ });
858
+ }
859
+ } catch {}
860
+ if (req.postData) try {
861
+ const body = JSON.parse(req.postData);
862
+ if (body && typeof body === "object") for (const [k, v] of Object.entries(body)) {
863
+ if (!TOKEN_BODY_KEYS.test(k)) continue;
864
+ if (typeof v !== "string" || v.length < 8) continue;
865
+ const dedupeKey = `body:${k}:${v.slice(0, 8)}`;
866
+ if (seen.has(dedupeKey)) continue;
867
+ seen.add(dedupeKey);
868
+ findings.push({
869
+ header: k,
870
+ value_masked: maskSecret(v),
871
+ request_url: req.url,
872
+ confidence: scoreValue(v) * .85,
873
+ source: "body"
874
+ });
875
+ }
876
+ } catch {}
877
+ }
878
+ return findings.toSorted((a, b) => b.confidence - a.confidence);
879
+ }
880
+ //#endregion
881
+ //#region src/server/domains/network/har.ts
882
+ /**
883
+ * HAR 1.2 builder — converts NetworkMonitor captured data to standard HAR format.
884
+ * Ref: http://www.softwareishard.com/blog/har-12-spec/
885
+ */
886
+ function headersToHar(headers = {}) {
887
+ return Object.entries(headers).map(([name, value]) => ({
888
+ name,
889
+ value
890
+ }));
891
+ }
892
+ function parseCookies(cookieHeader) {
893
+ return cookieHeader.split(";").map((part) => {
894
+ const eq = part.indexOf("=");
895
+ if (eq === -1) return {
896
+ name: part.trim(),
897
+ value: ""
898
+ };
899
+ return {
900
+ name: part.slice(0, eq).trim(),
901
+ value: part.slice(eq + 1).trim()
902
+ };
903
+ });
904
+ }
905
+ function queryStringFromUrl(url) {
906
+ try {
907
+ const u = new URL(url);
908
+ return Array.from(u.searchParams.entries()).map(([name, value]) => ({
909
+ name,
910
+ value
911
+ }));
912
+ } catch {
913
+ return [];
914
+ }
915
+ }
916
+ async function buildHar(params) {
917
+ const { requests, getResponse, getResponseBody, includeBodies, creatorVersion = "unknown" } = params;
918
+ const entries = [];
919
+ const bodyResults = /* @__PURE__ */ new Map();
920
+ if (includeBodies) {
921
+ const BODY_CONCURRENCY = NETWORK_HAR_BODY_CONCURRENCY;
922
+ for (let i = 0; i < requests.length; i += BODY_CONCURRENCY) {
923
+ const batch = requests.slice(i, i + BODY_CONCURRENCY);
924
+ const settled = await Promise.allSettled(batch.map(async (req) => {
925
+ try {
926
+ const bodyResult = await getResponseBody(req.requestId);
927
+ if (bodyResult) return {
928
+ requestId: req.requestId,
929
+ text: bodyResult.body
930
+ };
931
+ return {
932
+ requestId: req.requestId,
933
+ _bodyUnavailable: true
934
+ };
935
+ } catch {
936
+ return {
937
+ requestId: req.requestId,
938
+ _bodyUnavailable: true
939
+ };
940
+ }
941
+ }));
942
+ for (const result of settled) if (result.status === "fulfilled") {
943
+ const val = result.value;
944
+ bodyResults.set(val.requestId, "_bodyUnavailable" in val ? { _bodyUnavailable: true } : { text: val.text });
945
+ }
946
+ }
947
+ }
948
+ for (const req of requests) {
949
+ const res = getResponse(req.requestId);
950
+ const startedDateTime = req.timestamp ? (/* @__PURE__ */ new Date(req.timestamp * 1e3)).toISOString() : (/* @__PURE__ */ new Date()).toISOString();
951
+ const bodyContent = includeBodies ? bodyResults.get(req.requestId) ?? { _bodyUnavailable: true } : {};
952
+ const postData = req.postData ? {
953
+ mimeType: req.headers?.["content-type"] ?? "application/octet-stream",
954
+ text: req.postData
955
+ } : void 0;
956
+ const reqCookieHeader = req.headers?.["cookie"] ?? "";
957
+ const resCookieHeader = res?.headers?.["set-cookie"] ?? "";
958
+ const entry = {
959
+ startedDateTime,
960
+ time: res?.timing?.receiveHeadersEnd ?? 0,
961
+ request: {
962
+ method: req.method,
963
+ url: req.url,
964
+ httpVersion: "HTTP/1.1",
965
+ headers: headersToHar(req.headers),
966
+ queryString: queryStringFromUrl(req.url),
967
+ cookies: reqCookieHeader ? parseCookies(reqCookieHeader) : [],
968
+ headersSize: -1,
969
+ bodySize: req.postData ? req.postData.length : 0,
970
+ ...postData ? { postData } : {}
971
+ },
972
+ response: {
973
+ status: res?.status ?? 0,
974
+ statusText: res?.statusText ?? "",
975
+ httpVersion: "HTTP/1.1",
976
+ headers: headersToHar(res?.headers),
977
+ cookies: resCookieHeader ? parseCookies(resCookieHeader) : [],
978
+ content: {
979
+ size: bodyContent.text ? bodyContent.text.length : -1,
980
+ mimeType: res?.mimeType ?? "application/octet-stream",
981
+ ...bodyContent
982
+ },
983
+ redirectURL: res?.headers?.["location"] ?? "",
984
+ headersSize: -1,
985
+ bodySize: bodyContent.text ? bodyContent.text.length : -1
986
+ },
987
+ cache: {},
988
+ timings: {
989
+ send: 0,
990
+ wait: res?.timing?.receiveHeadersEnd ?? 0,
991
+ receive: 0
992
+ },
993
+ _requestId: req.requestId
994
+ };
995
+ entries.push(entry);
996
+ }
997
+ return { log: {
998
+ version: "1.2",
999
+ creator: {
1000
+ name: "jshookmcp",
1001
+ version: creatorVersion
1002
+ },
1003
+ entries
1004
+ } };
1005
+ }
1006
+ //#endregion
1007
+ //#region src/server/domains/network/replay.ts
1008
+ /**
1009
+ * Request Replay — rebuilds and re-sends a captured network request
1010
+ * with optional header/body/method/URL overrides.
1011
+ *
1012
+ * Security: dryRun defaults to true to prevent accidental side-effects.
1013
+ * Always sanitize headers that would conflict (host, content-length, transfer-encoding).
1014
+ * SSRF guard resolves DNS before checking to defeat rebinding attacks.
1015
+ */
1016
+ const STRIPPED_HEADERS = new Set([
1017
+ "host",
1018
+ "content-length",
1019
+ "transfer-encoding",
1020
+ "connection",
1021
+ "keep-alive",
1022
+ "proxy-authenticate",
1023
+ "proxy-authorization",
1024
+ "te",
1025
+ "trailers",
1026
+ "upgrade"
1027
+ ]);
1028
+ const DANGEROUS_KEYS = new Set([
1029
+ "__proto__",
1030
+ "constructor",
1031
+ "prototype"
1032
+ ]);
1033
+ function sanitizeHeaders(headers) {
1034
+ const out = Object.create(null);
1035
+ for (const [k, v] of Object.entries(headers)) if (!STRIPPED_HEADERS.has(k.toLowerCase()) && !DANGEROUS_KEYS.has(k)) out[k] = v;
1036
+ return out;
1037
+ }
1038
+ async function replayRequest(base, args, maxBodyBytes = 512e3) {
1039
+ const url = args.urlOverride ?? base.url;
1040
+ const method = (args.methodOverride ?? base.method).toUpperCase();
1041
+ const mergedHeaders = sanitizeHeaders({
1042
+ ...base.headers,
1043
+ ...args.headerPatch
1044
+ });
1045
+ const body = args.bodyPatch !== void 0 ? args.bodyPatch : base.postData;
1046
+ const authorizationPolicy = createNetworkAuthorizationPolicy(args.authorization);
1047
+ const allowLegacyLocalSsrf = !authorizationPolicy && isLocalSsrfBypassEnabled();
1048
+ if (authorizationPolicy && (authorizationPolicy.allowPrivateNetwork || authorizationPolicy.allowInsecureHttp) && !hasAuthorizedTargets(authorizationPolicy)) throw new Error("Replay authorization must include at least one allowed host or CIDR when enabling private network or insecure HTTP access.");
1049
+ if (isNetworkAuthorizationExpired(authorizationPolicy)) throw new Error("Replay authorization expired before the request was executed.");
1050
+ const isPrivateTargetAllowed = (target) => {
1051
+ if (allowLegacyLocalSsrf) return true;
1052
+ return authorizationPolicy?.allowPrivateNetwork === true && isAuthorizedNetworkTarget(authorizationPolicy, target);
1053
+ };
1054
+ const isInsecureHttpAllowed = (target) => {
1055
+ if (target.parsedUrl.protocol !== "http:") return true;
1056
+ if (allowLegacyLocalSsrf) return true;
1057
+ if (isLoopbackHost(target.hostname)) return true;
1058
+ return authorizationPolicy?.allowInsecureHttp === true && isAuthorizedNetworkTarget(authorizationPolicy, target);
1059
+ };
1060
+ const resolvePinned = async (targetUrl) => {
1061
+ let target;
1062
+ try {
1063
+ target = await resolveNetworkTarget(targetUrl);
1064
+ } catch {
1065
+ throw new Error(`Replay blocked: DNS resolution failed for "${targetUrl}"`);
1066
+ }
1067
+ if (!isInsecureHttpAllowed(target)) throw new Error(`Replay blocked: insecure HTTP is only allowed for loopback or explicitly authorized targets, got "${targetUrl}"`);
1068
+ const hostnameIsPrivate = isPrivateHost(target.hostname);
1069
+ const resolvedAddressIsPrivate = isPrivateHost(target.resolvedAddress ?? "");
1070
+ if ((hostnameIsPrivate || resolvedAddressIsPrivate) && !isPrivateTargetAllowed(target)) {
1071
+ if (!hostnameIsPrivate && resolvedAddressIsPrivate && target.resolvedAddress) throw new Error(`Replay blocked: "${targetUrl}" resolved to private IP ${target.resolvedAddress}`);
1072
+ throw new Error(`Replay blocked: target URL "${targetUrl}" resolves to a private/reserved address.`);
1073
+ }
1074
+ if (target.parsedUrl.protocol === "https:" || target.isIpLiteral) return {
1075
+ pinnedUrl: targetUrl,
1076
+ originalHost: target.parsedUrl.host,
1077
+ target
1078
+ };
1079
+ const originalHost = target.parsedUrl.host;
1080
+ target.parsedUrl.hostname = target.resolvedAddress && target.resolvedAddress.includes(":") ? `[${target.resolvedAddress}]` : target.resolvedAddress ?? target.hostname;
1081
+ return {
1082
+ pinnedUrl: target.parsedUrl.toString(),
1083
+ originalHost,
1084
+ target
1085
+ };
1086
+ };
1087
+ if (args.dryRun !== false) {
1088
+ if (await isSsrfTarget(url, args.authorization)) throw new Error(`Replay blocked: target URL "${url}" resolves to a private/reserved address.`);
1089
+ const dryRunTarget = await resolveNetworkTarget(url).catch(() => null);
1090
+ if (dryRunTarget && !isInsecureHttpAllowed(dryRunTarget)) throw new Error(`Replay blocked: insecure HTTP is only allowed for loopback or explicitly authorized targets, got "${url}"`);
1091
+ return {
1092
+ dryRun: true,
1093
+ preview: {
1094
+ url,
1095
+ method,
1096
+ headers: mergedHeaders,
1097
+ body
1098
+ }
1099
+ };
1100
+ }
1101
+ const controller = new AbortController();
1102
+ const timeoutId = setTimeout(() => controller.abort(), args.timeoutMs ?? 3e4);
1103
+ const MAX_REDIRECTS = NETWORK_REPLAY_MAX_REDIRECTS;
1104
+ try {
1105
+ let currentUrl = url;
1106
+ let currentMethod = method;
1107
+ let currentBody = body;
1108
+ let resp;
1109
+ for (let hop = 0; hop < MAX_REDIRECTS; hop++) {
1110
+ const { pinnedUrl, originalHost, target } = await resolvePinned(currentUrl);
1111
+ const hopHeaders = { ...mergedHeaders };
1112
+ if (target.parsedUrl.protocol === "http:" && target.resolvedAddress && !target.isIpLiteral) hopHeaders.Host = originalHost;
1113
+ resp = await fetch(pinnedUrl, {
1114
+ method: currentMethod,
1115
+ headers: hopHeaders,
1116
+ body: currentMethod !== "GET" && currentMethod !== "HEAD" ? currentBody : void 0,
1117
+ signal: controller.signal,
1118
+ redirect: "manual"
1119
+ });
1120
+ if (resp.status >= 300 && resp.status < 400) {
1121
+ const location = resp.headers.get("location");
1122
+ if (!location) break;
1123
+ currentUrl = new URL(location, currentUrl).toString();
1124
+ if (resp.status === 301 || resp.status === 302 || resp.status === 303) {
1125
+ currentMethod = "GET";
1126
+ currentBody = void 0;
1127
+ }
1128
+ delete mergedHeaders["Host"];
1129
+ delete mergedHeaders["host"];
1130
+ continue;
1131
+ }
1132
+ break;
1133
+ }
1134
+ if (resp.status >= 300 && resp.status < 400) throw new Error(`Replay blocked: too many redirects (>${MAX_REDIRECTS})`);
1135
+ const responseHeaders = {};
1136
+ resp.headers.forEach((v, k) => {
1137
+ responseHeaders[k] = v;
1138
+ });
1139
+ const rawText = await resp.text();
1140
+ const bodyTruncated = rawText.length > maxBodyBytes;
1141
+ const bodyOut = bodyTruncated ? rawText.slice(0, maxBodyBytes) : rawText;
1142
+ return {
1143
+ dryRun: false,
1144
+ status: resp.status,
1145
+ statusText: resp.statusText,
1146
+ headers: responseHeaders,
1147
+ body: bodyOut,
1148
+ bodyTruncated,
1149
+ requestId: args.requestId
1150
+ };
1151
+ } finally {
1152
+ clearTimeout(timeoutId);
1153
+ }
1154
+ }
1155
+ //#endregion
1156
+ //#region src/server/domains/network/handlers/replay-handlers.ts
1157
+ /**
1158
+ * Replay and analysis handlers — auth extraction, HAR export, request replay.
1159
+ *
1160
+ * Extracted from AdvancedToolHandlersRuntime (handlers.impl.core.runtime.replay.ts).
1161
+ */
1162
+ const isReplayableRequest = (value) => {
1163
+ if (typeof value !== "object" || value === null) return false;
1164
+ const record = value;
1165
+ return typeof record.requestId === "string" && typeof record.url === "string" && typeof record.method === "string";
1166
+ };
1167
+ const parseStringArray$1 = (value, field) => {
1168
+ if (value === void 0) return [];
1169
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${field} must be an array of strings`);
1170
+ return value.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1171
+ };
1172
+ const parseOptionalString$1 = (value, field) => {
1173
+ if (value === void 0) return;
1174
+ if (typeof value !== "string") throw new Error(`${field} must be a string`);
1175
+ const trimmed = value.trim();
1176
+ return trimmed.length > 0 ? trimmed : void 0;
1177
+ };
1178
+ const parseOptionalBoolean$1 = (value, field) => {
1179
+ if (value === void 0) return;
1180
+ if (typeof value !== "boolean") throw new Error(`${field} must be a boolean`);
1181
+ return value;
1182
+ };
1183
+ const decodeAuthorizationCapability = (capability, requestId) => {
1184
+ if (typeof capability !== "string" || capability.trim().length === 0) throw new Error("authorizationCapability must be a non-empty base64url string");
1185
+ let parsed;
1186
+ try {
1187
+ parsed = JSON.parse(Buffer.from(capability, "base64url").toString("utf8"));
1188
+ } catch {
1189
+ throw new Error("authorizationCapability must be valid base64url-encoded JSON");
1190
+ }
1191
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) throw new Error("authorizationCapability payload must be an object");
1192
+ const payload = parsed;
1193
+ if (payload.version !== void 0 && payload.version !== 1) throw new Error(`authorizationCapability version ${String(payload.version)} is not supported`);
1194
+ if (payload.requestId !== requestId) throw new Error("authorizationCapability requestId does not match the replay requestId");
1195
+ return payload;
1196
+ };
1197
+ const parseReplayAuthorization = (args, requestId) => {
1198
+ const authorizationArg = args.authorization;
1199
+ const capabilityArg = args.authorizationCapability;
1200
+ if (authorizationArg !== void 0 && capabilityArg !== void 0) throw new Error("Provide either authorization or authorizationCapability, not both");
1201
+ let source;
1202
+ if (authorizationArg !== void 0) {
1203
+ if (typeof authorizationArg !== "object" || authorizationArg === null || Array.isArray(authorizationArg)) throw new Error("authorization must be an object");
1204
+ source = authorizationArg;
1205
+ } else if (capabilityArg !== void 0) source = decodeAuthorizationCapability(capabilityArg, requestId);
1206
+ else return;
1207
+ const allowedHosts = parseStringArray$1(source.allowedHosts, "authorization.allowedHosts");
1208
+ const allowedCidrs = parseStringArray$1(source.allowedCidrs, "authorization.allowedCidrs");
1209
+ const allowPrivateNetwork = parseOptionalBoolean$1(source.allowPrivateNetwork, "authorization.allowPrivateNetwork");
1210
+ const allowInsecureHttp = parseOptionalBoolean$1(source.allowInsecureHttp, "authorization.allowInsecureHttp");
1211
+ const expiresAt = parseOptionalString$1(source.expiresAt, "authorization.expiresAt");
1212
+ const reason = parseOptionalString$1(source.reason, "authorization.reason");
1213
+ const authorization = {};
1214
+ if (allowedHosts.length > 0) authorization.allowedHosts = allowedHosts;
1215
+ if (allowedCidrs.length > 0) authorization.allowedCidrs = allowedCidrs;
1216
+ if (allowPrivateNetwork !== void 0) authorization.allowPrivateNetwork = allowPrivateNetwork;
1217
+ if (allowInsecureHttp !== void 0) authorization.allowInsecureHttp = allowInsecureHttp;
1218
+ if (expiresAt !== void 0) authorization.expiresAt = expiresAt;
1219
+ if (reason !== void 0) authorization.reason = reason;
1220
+ return authorization;
1221
+ };
1222
+ var ReplayHandlers = class {
1223
+ detailedDataManager = getDetailedDataManager();
1224
+ constructor(deps) {
1225
+ this.deps = deps;
1226
+ }
1227
+ async handleNetworkExtractAuth(args) {
1228
+ try {
1229
+ const minConfidence = parseNumberArg(args.minConfidence, { defaultValue: .4 });
1230
+ const requests = this.deps.consoleMonitor.getNetworkRequests();
1231
+ if (requests.length === 0) return R.fail("No captured requests found. Call network_enable then page_navigate first.").json();
1232
+ const findings = extractAuthFromRequests(requests).filter((f) => f.confidence >= minConfidence);
1233
+ return R.ok().merge({
1234
+ scannedRequests: requests.length,
1235
+ found: findings.length,
1236
+ findings,
1237
+ note: "Values are masked (first 6 + last 4 chars). Use network_replay_request to test with actual values."
1238
+ }).json();
1239
+ } catch (error) {
1240
+ return R.fail(error).json();
1241
+ }
1242
+ }
1243
+ async handleNetworkExportHar(args) {
1244
+ try {
1245
+ const outputPath = args.outputPath;
1246
+ const includeBodies = parseBooleanArg(args.includeBodies, false);
1247
+ let resolvedOutputPath;
1248
+ if (outputPath) {
1249
+ const path = await import("node:path");
1250
+ const fsDynamic = await import("node:fs/promises");
1251
+ const resolved = path.resolve(outputPath);
1252
+ const cwd = await fsDynamic.realpath(process.cwd());
1253
+ const tmpDir = await fsDynamic.realpath((await import("node:os")).tmpdir());
1254
+ const parentDir = path.dirname(resolved);
1255
+ let realParent;
1256
+ try {
1257
+ realParent = await fsDynamic.realpath(parentDir);
1258
+ } catch {
1259
+ realParent = parentDir;
1260
+ }
1261
+ const realPath = path.join(realParent, path.basename(resolved));
1262
+ const inCwd = realPath === cwd || realPath.startsWith(cwd + path.sep);
1263
+ const inTmp = realPath === tmpDir || realPath.startsWith(tmpDir + path.sep);
1264
+ if (!inCwd && !inTmp) return R.fail("outputPath must be within the current working directory or system temp dir.").json();
1265
+ resolvedOutputPath = realPath;
1266
+ }
1267
+ const requests = this.deps.consoleMonitor.getNetworkRequests();
1268
+ if (requests.length === 0) return R.fail("No captured requests to export. Call network_enable then page_navigate first.").json();
1269
+ const getResponse = (id) => this.deps.consoleMonitor.getNetworkActivity(id)?.response;
1270
+ const har = await buildHar({
1271
+ requests,
1272
+ getResponse,
1273
+ getResponseBody: async (id) => {
1274
+ try {
1275
+ return await this.deps.consoleMonitor.getResponseBody(id);
1276
+ } catch {
1277
+ return null;
1278
+ }
1279
+ },
1280
+ includeBodies,
1281
+ creatorVersion: "1.0.0"
1282
+ });
1283
+ if (resolvedOutputPath) {
1284
+ try {
1285
+ if ((await promises.lstat(resolvedOutputPath)).isSymbolicLink()) return R.fail("outputPath must not be a symbolic link.").json();
1286
+ } catch {}
1287
+ await promises.writeFile(resolvedOutputPath, JSON.stringify(har, null, 2), "utf-8");
1288
+ return R.ok().merge({
1289
+ message: `HAR exported to ${resolvedOutputPath}`,
1290
+ entryCount: har.log.entries.length,
1291
+ outputPath: resolvedOutputPath
1292
+ }).json();
1293
+ }
1294
+ const result = this.detailedDataManager.smartHandle({
1295
+ entryCount: har.log.entries.length,
1296
+ har
1297
+ }, 51200);
1298
+ return R.ok().merge(result).json();
1299
+ } catch (error) {
1300
+ return R.fail(error).json();
1301
+ }
1302
+ }
1303
+ async handleNetworkReplayRequest(args) {
1304
+ try {
1305
+ const requestId = args.requestId;
1306
+ if (!requestId) return R.fail("requestId is required").json();
1307
+ const base = this.deps.consoleMonitor.getNetworkRequests().find((request) => isReplayableRequest(request) && request.requestId === requestId);
1308
+ if (!base) return R.fail(`Request ${requestId} not found in captured requests`).merge({ hint: "Use network_get_requests to list available requestIds" }).json();
1309
+ const authorization = parseReplayAuthorization(args, requestId);
1310
+ const result = await replayRequest(base, {
1311
+ requestId,
1312
+ headerPatch: args.headerPatch,
1313
+ bodyPatch: args.bodyPatch,
1314
+ methodOverride: args.methodOverride,
1315
+ urlOverride: args.urlOverride,
1316
+ timeoutMs: args.timeoutMs,
1317
+ dryRun: args.dryRun !== false,
1318
+ authorization
1319
+ });
1320
+ return R.ok().merge(result).json();
1321
+ } catch (error) {
1322
+ return R.fail(error).json();
1323
+ }
1324
+ }
1325
+ };
1326
+ //#endregion
1327
+ //#region src/server/domains/network/handlers/intercept-handlers.ts
1328
+ const isObjectRecord = (value) => typeof value === "object" && value !== null;
1329
+ var InterceptHandlers = class {
1330
+ constructor(deps) {
1331
+ this.deps = deps;
1332
+ }
1333
+ async handleNetworkInterceptResponse(args) {
1334
+ try {
1335
+ const rules = this.parseInterceptRules(args);
1336
+ if (rules.length === 0) return R.fail("No valid rules provided. Provide either \"urlPattern\" (single) or \"rules\" array (batch).").merge({ usage: {
1337
+ single: {
1338
+ urlPattern: "*api/status*",
1339
+ responseCode: 200,
1340
+ responseBody: "{\"status\":\"active\"}"
1341
+ },
1342
+ batch: { rules: [{
1343
+ urlPattern: "*api/status*",
1344
+ responseBody: "{\"status\":\"active\"}"
1345
+ }] }
1346
+ } }).json();
1347
+ const createdRules = await this.deps.consoleMonitor.enableFetchIntercept(rules);
1348
+ const status = this.deps.consoleMonitor.getFetchInterceptStatus();
1349
+ emitEvent(this.deps.eventBus, "network:intercept_started", {
1350
+ interceptType: "fetch",
1351
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
1352
+ });
1353
+ return R.ok().merge({
1354
+ message: `Added ${createdRules.length} interception rule(s)`,
1355
+ createdRules: createdRules.map((r) => ({
1356
+ id: r.id,
1357
+ urlPattern: r.urlPattern,
1358
+ stage: r.stage,
1359
+ responseCode: r.responseCode
1360
+ })),
1361
+ totalActiveRules: status.rules.length,
1362
+ hint: "Use network_intercept(action: \"list\") to see all rules and hit counts. Use network_intercept(action: \"disable\") to remove rules."
1363
+ }).json();
1364
+ } catch (error) {
1365
+ return R.fail(error instanceof Error ? error.message : String(error)).merge({ hint: "Ensure browser is launched and a page is active before enabling interception." }).json();
1366
+ }
1367
+ }
1368
+ async handleNetworkInterceptList(_args) {
1369
+ const status = this.deps.consoleMonitor.getFetchInterceptStatus();
1370
+ return R.ok().merge(status).merge({ hint: status.rules.length > 0 ? "Use network_intercept(action: \"disable\", ruleId) to remove a specific rule, or network_intercept(action: \"disable\", all: true) to remove all." : "No active interception rules. Use network_intercept(action: \"add\") to add rules." }).json();
1371
+ }
1372
+ async handleNetworkInterceptDisable(args) {
1373
+ const ruleId = typeof args.ruleId === "string" ? args.ruleId : void 0;
1374
+ const all = args.all === true;
1375
+ if (!ruleId && !all) return R.fail("Provide either \"ruleId\" to remove a specific rule, or \"all\": true to disable all.").json();
1376
+ try {
1377
+ if (all) {
1378
+ const result = await this.deps.consoleMonitor.disableFetchIntercept();
1379
+ return R.ok().merge({
1380
+ message: `Disabled all interception. Removed ${result.removedRules} rule(s).`,
1381
+ removedRules: result.removedRules
1382
+ }).json();
1383
+ }
1384
+ const removed = await this.deps.consoleMonitor.removeFetchInterceptRule(ruleId);
1385
+ const status = this.deps.consoleMonitor.getFetchInterceptStatus();
1386
+ return R.ok().merge({
1387
+ success: removed,
1388
+ message: removed ? `Rule ${ruleId} removed.` : `Rule ${ruleId} not found.`,
1389
+ remainingRules: status.rules.length
1390
+ }).json();
1391
+ } catch (error) {
1392
+ return R.fail(error instanceof Error ? error.message : String(error)).json();
1393
+ }
1394
+ }
1395
+ parseInterceptRules(args) {
1396
+ const rules = [];
1397
+ if (Array.isArray(args.rules)) {
1398
+ for (const rawRule of args.rules) if (isObjectRecord(rawRule) && typeof rawRule.urlPattern === "string") rules.push(this.toInterceptRule(rawRule));
1399
+ } else if (typeof args.urlPattern === "string") rules.push(this.toInterceptRule(args));
1400
+ return rules;
1401
+ }
1402
+ toInterceptRule(source) {
1403
+ return {
1404
+ urlPattern: source.urlPattern,
1405
+ urlPatternType: source.urlPatternType === "regex" ? "regex" : "glob",
1406
+ stage: source.stage === "Request" ? "Request" : "Response",
1407
+ responseCode: typeof source.responseCode === "number" ? source.responseCode : 200,
1408
+ responseHeaders: isObjectRecord(source.responseHeaders) ? source.responseHeaders : void 0,
1409
+ responseBody: typeof source.responseBody === "string" ? source.responseBody : typeof source.responseBody === "object" ? JSON.stringify(source.responseBody) : void 0
1410
+ };
1411
+ }
1412
+ };
1413
+ //#endregion
1414
+ //#region src/utils/BufferChain.ts
1415
+ /**
1416
+ * Zero-copy buffer chain — avoids repeated Buffer.concat allocations.
1417
+ *
1418
+ * Appends chunks without copying. Materializes into a single Buffer only
1419
+ * when `toBuffer()` is called. Tracks total byte length for size limits.
1420
+ */
1421
+ var BufferChain = class {
1422
+ chunks = [];
1423
+ totalLength = 0;
1424
+ /** Number of bytes accumulated so far. */
1425
+ get length() {
1426
+ return this.totalLength;
1427
+ }
1428
+ /** Append a chunk without copying. */
1429
+ append(chunk) {
1430
+ if (chunk.length === 0) return;
1431
+ this.chunks.push(chunk);
1432
+ this.totalLength += chunk.length;
1433
+ }
1434
+ /** Materialize all chunks into a single Buffer. */
1435
+ toBuffer() {
1436
+ if (this.chunks.length === 0) return Buffer.alloc(0);
1437
+ if (this.chunks.length === 1) return this.chunks[0];
1438
+ const result = Buffer.concat(this.chunks, this.totalLength);
1439
+ this.chunks = [result];
1440
+ return result;
1441
+ }
1442
+ /** Reset the chain, releasing all chunk references. */
1443
+ reset() {
1444
+ this.chunks = [];
1445
+ this.totalLength = 0;
1446
+ }
1447
+ /** Whether any data has been accumulated. */
1448
+ get isEmpty() {
1449
+ return this.totalLength === 0;
1450
+ }
1451
+ };
1452
+ //#endregion
1453
+ //#region src/server/domains/network/http-raw.ts
1454
+ const HEADER_NAME_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
1455
+ const TEXTUAL_CONTENT_TYPE_RE = /^(?:text\/|application\/(?:json|ld\+json|xml|xhtml\+xml|javascript|x-javascript|problem\+json|problem\+xml|graphql-response\+json|x-www-form-urlencoded)|image\/svg\+xml)/i;
1456
+ function assertNoLineBreak(value, field) {
1457
+ if (value.includes("\r") || value.includes("\n")) throw new Error(`${field} must not contain CR or LF characters`);
1458
+ }
1459
+ function hasHeader(headers, headerName) {
1460
+ return Object.keys(headers).some((key) => key.toLowerCase() === headerName.toLowerCase());
1461
+ }
1462
+ function findHeaderValue(headers, headerName) {
1463
+ return headers.find((header) => header.name.toLowerCase() === headerName.toLowerCase())?.value ?? null;
1464
+ }
1465
+ function isBodylessResponse(statusCode, requestMethod) {
1466
+ return requestMethod?.toUpperCase() === "HEAD" || statusCode >= 100 && statusCode < 200 || statusCode === 204 || statusCode === 304;
1467
+ }
1468
+ function findHeaderTerminator(buffer) {
1469
+ const crlf = buffer.indexOf("\r\n\r\n");
1470
+ if (crlf >= 0) return crlf + 4;
1471
+ const lf = buffer.indexOf("\n\n");
1472
+ if (lf >= 0) return lf + 2;
1473
+ return null;
1474
+ }
1475
+ function findLineBreak(buffer, start) {
1476
+ const lfIndex = buffer.indexOf(10, start);
1477
+ if (lfIndex < 0) return null;
1478
+ return {
1479
+ lineEnd: lfIndex > start && buffer[lfIndex - 1] === 13 ? lfIndex - 1 : lfIndex,
1480
+ nextOffset: lfIndex + 1
1481
+ };
1482
+ }
1483
+ function decodeChunkedBody(buffer) {
1484
+ const chunks = [];
1485
+ let offset = 0;
1486
+ while (offset < buffer.length) {
1487
+ const line = findLineBreak(buffer, offset);
1488
+ if (!line) return {
1489
+ complete: false,
1490
+ consumedBytes: offset,
1491
+ body: Buffer.concat(chunks)
1492
+ };
1493
+ const sizeToken = buffer.subarray(offset, line.lineEnd).toString("latin1").trim().split(";", 1)[0]?.trim() ?? "";
1494
+ const chunkSize = Number.parseInt(sizeToken, 16);
1495
+ if (!Number.isFinite(chunkSize) || chunkSize < 0) return {
1496
+ complete: false,
1497
+ consumedBytes: offset,
1498
+ body: Buffer.concat(chunks)
1499
+ };
1500
+ if (chunkSize === 0) {
1501
+ const trailerSection = buffer.subarray(line.nextOffset);
1502
+ const trailerEnd = findHeaderTerminator(trailerSection);
1503
+ if (trailerEnd !== null) return {
1504
+ complete: true,
1505
+ consumedBytes: line.nextOffset + trailerEnd,
1506
+ body: Buffer.concat(chunks)
1507
+ };
1508
+ if (trailerSection.length >= 2 && trailerSection[0] === 13 && trailerSection[1] === 10) return {
1509
+ complete: true,
1510
+ consumedBytes: line.nextOffset + 2,
1511
+ body: Buffer.concat(chunks)
1512
+ };
1513
+ if (trailerSection.length >= 1 && trailerSection[0] === 10) return {
1514
+ complete: true,
1515
+ consumedBytes: line.nextOffset + 1,
1516
+ body: Buffer.concat(chunks)
1517
+ };
1518
+ return {
1519
+ complete: false,
1520
+ consumedBytes: offset,
1521
+ body: Buffer.concat(chunks)
1522
+ };
1523
+ }
1524
+ const dataStart = line.nextOffset;
1525
+ const dataEnd = dataStart + chunkSize;
1526
+ if (dataEnd > buffer.length) return {
1527
+ complete: false,
1528
+ consumedBytes: offset,
1529
+ body: Buffer.concat(chunks)
1530
+ };
1531
+ chunks.push(buffer.subarray(dataStart, dataEnd));
1532
+ const afterChunkLine = findLineBreak(buffer, dataEnd);
1533
+ if (!afterChunkLine || afterChunkLine.lineEnd !== dataEnd) return {
1534
+ complete: false,
1535
+ consumedBytes: offset,
1536
+ body: Buffer.concat(chunks)
1537
+ };
1538
+ offset = afterChunkLine.nextOffset;
1539
+ }
1540
+ return {
1541
+ complete: false,
1542
+ consumedBytes: offset,
1543
+ body: Buffer.concat(chunks)
1544
+ };
1545
+ }
1546
+ function buildHeaderSection(headers) {
1547
+ const entries = Object.entries(headers);
1548
+ if (entries.length === 0) return "";
1549
+ return `${entries.map(([name, value]) => `${name}: ${value}`).join("\r\n")}\r\n`;
1550
+ }
1551
+ function buildHttpRequest(input) {
1552
+ const method = input.method.trim().toUpperCase();
1553
+ const target = input.target.trim();
1554
+ const httpVersion = input.httpVersion ?? "1.1";
1555
+ if (!HEADER_NAME_RE.test(method)) throw new Error("method must be a valid HTTP token");
1556
+ if (target.length === 0) throw new Error("target is required");
1557
+ assertNoLineBreak(target, "target");
1558
+ if (httpVersion !== "1.0" && httpVersion !== "1.1") throw new Error("httpVersion must be either \"1.0\" or \"1.1\"");
1559
+ const headers = {};
1560
+ for (const [name, value] of Object.entries(input.headers ?? {})) {
1561
+ if (!HEADER_NAME_RE.test(name)) throw new Error(`Invalid HTTP header name: ${name}`);
1562
+ if (typeof value !== "string") throw new Error(`HTTP header "${name}" must be a string`);
1563
+ assertNoLineBreak(value, `headers.${name}`);
1564
+ headers[name] = value;
1565
+ }
1566
+ if (input.addHostHeader !== false && input.host && !hasHeader(headers, "Host")) {
1567
+ assertNoLineBreak(input.host, "host");
1568
+ headers.Host = input.host;
1569
+ }
1570
+ const body = input.body ?? "";
1571
+ if (input.body !== void 0 && input.addContentLength !== false && !hasHeader(headers, "Content-Length")) {
1572
+ if (!hasHeader(headers, "Transfer-Encoding")) headers["Content-Length"] = String(Buffer.byteLength(body, "utf8"));
1573
+ }
1574
+ if (input.addConnectionClose !== false && !hasHeader(headers, "Connection")) headers.Connection = "close";
1575
+ const startLine = `${method} ${target} HTTP/${httpVersion}`;
1576
+ const requestText = `${startLine}\r\n${buildHeaderSection(headers)}\r\n${body}`;
1577
+ const requestBuffer = Buffer.from(requestText, "utf8");
1578
+ return {
1579
+ requestText,
1580
+ requestHex: requestBuffer.toString("hex"),
1581
+ requestBytes: requestBuffer.length,
1582
+ startLine,
1583
+ headers,
1584
+ bodyBytes: Buffer.byteLength(body, "utf8"),
1585
+ httpVersion
1586
+ };
1587
+ }
1588
+ function analyzeHttpResponse(rawResponse, requestMethod) {
1589
+ const headerBytes = findHeaderTerminator(rawResponse);
1590
+ if (headerBytes === null) return null;
1591
+ const headerLines = rawResponse.subarray(0, headerBytes).toString("latin1").replace(/\r?\n\r?\n$/, "").split(/\r?\n/).filter((line) => line.length > 0);
1592
+ if (headerLines.length === 0) throw new Error("HTTP response did not contain a status line");
1593
+ const statusLine = headerLines[0];
1594
+ const statusMatch = /^HTTP\/(\d+\.\d+)\s+(\d{3})(?:\s+(.*))?$/.exec(statusLine);
1595
+ if (!statusMatch) throw new Error(`Invalid HTTP status line: ${statusLine}`);
1596
+ const rawHeaders = [];
1597
+ const headers = {};
1598
+ for (const line of headerLines.slice(1)) {
1599
+ const separator = line.indexOf(":");
1600
+ if (separator <= 0) continue;
1601
+ const name = line.slice(0, separator).trim();
1602
+ const value = line.slice(separator + 1).trim();
1603
+ rawHeaders.push({
1604
+ name,
1605
+ value
1606
+ });
1607
+ if (!(name in headers)) headers[name] = value;
1608
+ else if (name.toLowerCase() === "set-cookie") headers[name] = `${headers[name]}, ${value}`;
1609
+ else headers[name] = `${headers[name]}, ${value}`;
1610
+ }
1611
+ const statusCode = Number.parseInt(statusMatch[2], 10);
1612
+ const responseBody = rawResponse.subarray(headerBytes);
1613
+ if (isBodylessResponse(statusCode, requestMethod)) return {
1614
+ statusLine,
1615
+ httpVersion: statusMatch[1],
1616
+ statusCode,
1617
+ statusText: statusMatch[3] ?? "",
1618
+ headers,
1619
+ rawHeaders,
1620
+ headerBytes,
1621
+ bodyBytes: 0,
1622
+ bodyBuffer: Buffer.alloc(0),
1623
+ bodyMode: "none",
1624
+ complete: true,
1625
+ expectedRawBytes: headerBytes,
1626
+ chunkedDecoded: false
1627
+ };
1628
+ if (findHeaderValue(rawHeaders, "transfer-encoding")?.toLowerCase().includes("chunked")) {
1629
+ const decoded = decodeChunkedBody(responseBody);
1630
+ return {
1631
+ statusLine,
1632
+ httpVersion: statusMatch[1],
1633
+ statusCode,
1634
+ statusText: statusMatch[3] ?? "",
1635
+ headers,
1636
+ rawHeaders,
1637
+ headerBytes,
1638
+ bodyBytes: decoded.body.length,
1639
+ bodyBuffer: decoded.complete ? decoded.body : responseBody,
1640
+ bodyMode: "chunked",
1641
+ complete: decoded.complete,
1642
+ expectedRawBytes: decoded.complete ? headerBytes + decoded.consumedBytes : null,
1643
+ chunkedDecoded: decoded.complete
1644
+ };
1645
+ }
1646
+ const contentLengthValue = findHeaderValue(rawHeaders, "content-length");
1647
+ if (contentLengthValue !== null) {
1648
+ const contentLength = Number.parseInt(contentLengthValue, 10);
1649
+ if (Number.isFinite(contentLength) && contentLength >= 0) {
1650
+ const bodyBuffer = responseBody.subarray(0, Math.min(responseBody.length, contentLength));
1651
+ return {
1652
+ statusLine,
1653
+ httpVersion: statusMatch[1],
1654
+ statusCode,
1655
+ statusText: statusMatch[3] ?? "",
1656
+ headers,
1657
+ rawHeaders,
1658
+ headerBytes,
1659
+ bodyBytes: bodyBuffer.length,
1660
+ bodyBuffer,
1661
+ bodyMode: "content-length",
1662
+ complete: responseBody.length >= contentLength,
1663
+ expectedRawBytes: headerBytes + contentLength,
1664
+ chunkedDecoded: false
1665
+ };
1666
+ }
1667
+ }
1668
+ return {
1669
+ statusLine,
1670
+ httpVersion: statusMatch[1],
1671
+ statusCode,
1672
+ statusText: statusMatch[3] ?? "",
1673
+ headers,
1674
+ rawHeaders,
1675
+ headerBytes,
1676
+ bodyBytes: responseBody.length,
1677
+ bodyBuffer: responseBody,
1678
+ bodyMode: "until-close",
1679
+ complete: false,
1680
+ expectedRawBytes: null,
1681
+ chunkedDecoded: false
1682
+ };
1683
+ }
1684
+ function isLikelyTextHttpBody(contentType, body) {
1685
+ if (body.length === 0) return true;
1686
+ if (contentType && TEXTUAL_CONTENT_TYPE_RE.test(contentType)) return true;
1687
+ const sample = body.subarray(0, Math.min(body.length, 64));
1688
+ for (const byte of sample) if (byte === 0) return false;
1689
+ return true;
1690
+ }
1691
+ //#endregion
1692
+ //#region src/server/domains/network/http2-raw.ts
1693
+ const HTTP2_MAX_FRAME_SIZE = 16777215;
1694
+ const HTTP2_MAX_STREAM_ID = 2147483647;
1695
+ const HTTP2_MAX_SETTINGS_ID = 65535;
1696
+ const HTTP2_MAX_UNSIGNED_INT32 = 4294967295;
1697
+ const FRAME_TYPE_CODES = {
1698
+ DATA: 0,
1699
+ RST_STREAM: 3,
1700
+ SETTINGS: 4,
1701
+ PING: 6,
1702
+ GOAWAY: 7,
1703
+ WINDOW_UPDATE: 8
1704
+ };
1705
+ function assertIntegerInRange(value, field, min, max) {
1706
+ if (!Number.isInteger(value) || value < min || value > max) throw new Error(`${field} must be an integer between ${String(min)} and ${String(max)}`);
1707
+ }
1708
+ function parseHexBytes(value, field) {
1709
+ const normalized = value.replace(/\s+/g, "").trim();
1710
+ if (normalized.length === 0) return Buffer.alloc(0);
1711
+ if (normalized.length % 2 !== 0 || !/^[0-9a-fA-F]+$/.test(normalized)) throw new Error(`${field} must be an even-length hexadecimal string`);
1712
+ return Buffer.from(normalized, "hex");
1713
+ }
1714
+ function encodeTextBytes(value, encoding) {
1715
+ return Buffer.from(value, encoding);
1716
+ }
1717
+ function resolvePayloadBytes(payloadHex, payloadText, payloadEncoding) {
1718
+ if (payloadHex !== void 0 && payloadText !== void 0) throw new Error("payloadHex and payloadText are mutually exclusive");
1719
+ if (payloadHex !== void 0) return parseHexBytes(payloadHex, "payloadHex");
1720
+ if (payloadText !== void 0) return encodeTextBytes(payloadText, payloadEncoding);
1721
+ return Buffer.alloc(0);
1722
+ }
1723
+ function buildSettingsPayload(entries) {
1724
+ const payload = Buffer.alloc(entries.length * 6);
1725
+ entries.forEach((entry, index) => {
1726
+ assertIntegerInRange(entry.id, `settings[${String(index)}].id`, 0, HTTP2_MAX_SETTINGS_ID);
1727
+ assertIntegerInRange(entry.value, `settings[${String(index)}].value`, 0, HTTP2_MAX_UNSIGNED_INT32);
1728
+ payload.writeUInt16BE(entry.id, index * 6);
1729
+ payload.writeUInt32BE(entry.value >>> 0, index * 6 + 2);
1730
+ });
1731
+ return payload;
1732
+ }
1733
+ function encodeUInt31(value, field) {
1734
+ assertIntegerInRange(value, field, 0, HTTP2_MAX_STREAM_ID);
1735
+ const buffer = Buffer.alloc(4);
1736
+ buffer.writeUInt32BE(value >>> 0, 0);
1737
+ buffer[0] = buffer[0] & 127;
1738
+ return buffer;
1739
+ }
1740
+ function buildFramePayload(input) {
1741
+ const frameType = input.frameType;
1742
+ const flags = input.flags ?? 0;
1743
+ assertIntegerInRange(flags, "flags", 0, 255);
1744
+ switch (frameType) {
1745
+ case "DATA": return {
1746
+ payload: resolvePayloadBytes(input.payloadHex, input.payloadText, input.payloadEncoding ?? "utf8"),
1747
+ typeCode: FRAME_TYPE_CODES.DATA,
1748
+ flags
1749
+ };
1750
+ case "SETTINGS":
1751
+ if (input.ack === true && (input.settings?.length ?? 0) > 0) throw new Error("SETTINGS ack frames must not include settings payload");
1752
+ return {
1753
+ payload: buildSettingsPayload(input.settings ?? []),
1754
+ typeCode: FRAME_TYPE_CODES.SETTINGS,
1755
+ flags: input.ack ? flags | 1 : flags
1756
+ };
1757
+ case "PING": {
1758
+ const payload = input.pingOpaqueDataHex ? parseHexBytes(input.pingOpaqueDataHex, "pingOpaqueDataHex") : Buffer.alloc(8);
1759
+ if (payload.length !== 8) throw new Error("PING frames require exactly 8 bytes of opaque data");
1760
+ return {
1761
+ payload,
1762
+ typeCode: FRAME_TYPE_CODES.PING,
1763
+ flags: input.ack ? flags | 1 : flags
1764
+ };
1765
+ }
1766
+ case "WINDOW_UPDATE": {
1767
+ const increment = input.windowSizeIncrement;
1768
+ if (increment === void 0) throw new Error("windowSizeIncrement is required for WINDOW_UPDATE frames");
1769
+ assertIntegerInRange(increment, "windowSizeIncrement", 1, HTTP2_MAX_STREAM_ID);
1770
+ return {
1771
+ payload: encodeUInt31(increment, "windowSizeIncrement"),
1772
+ typeCode: FRAME_TYPE_CODES.WINDOW_UPDATE,
1773
+ flags
1774
+ };
1775
+ }
1776
+ case "RST_STREAM": {
1777
+ const errorCode = input.errorCode ?? 0;
1778
+ assertIntegerInRange(errorCode, "errorCode", 0, HTTP2_MAX_UNSIGNED_INT32);
1779
+ const payload = Buffer.alloc(4);
1780
+ payload.writeUInt32BE(errorCode >>> 0, 0);
1781
+ return {
1782
+ payload,
1783
+ typeCode: FRAME_TYPE_CODES.RST_STREAM,
1784
+ flags
1785
+ };
1786
+ }
1787
+ case "GOAWAY": {
1788
+ const lastStreamId = input.lastStreamId ?? 0;
1789
+ const errorCode = input.errorCode ?? 0;
1790
+ assertIntegerInRange(lastStreamId, "lastStreamId", 0, HTTP2_MAX_STREAM_ID);
1791
+ assertIntegerInRange(errorCode, "errorCode", 0, HTTP2_MAX_UNSIGNED_INT32);
1792
+ const debugData = input.debugDataText !== void 0 ? encodeTextBytes(input.debugDataText, input.debugDataEncoding ?? "utf8") : Buffer.alloc(0);
1793
+ return {
1794
+ payload: Buffer.concat([
1795
+ encodeUInt31(lastStreamId, "lastStreamId"),
1796
+ Buffer.from([
1797
+ errorCode >>> 24 & 255,
1798
+ errorCode >>> 16 & 255,
1799
+ errorCode >>> 8 & 255,
1800
+ errorCode & 255
1801
+ ]),
1802
+ debugData
1803
+ ]),
1804
+ typeCode: FRAME_TYPE_CODES.GOAWAY,
1805
+ flags
1806
+ };
1807
+ }
1808
+ case "RAW": {
1809
+ const typeCode = input.frameTypeCode;
1810
+ if (typeCode === void 0) throw new Error("frameTypeCode is required when frameType is RAW");
1811
+ assertIntegerInRange(typeCode, "frameTypeCode", 0, 255);
1812
+ return {
1813
+ payload: resolvePayloadBytes(input.payloadHex, input.payloadText, input.payloadEncoding ?? "utf8"),
1814
+ typeCode,
1815
+ flags
1816
+ };
1817
+ }
1818
+ }
1819
+ }
1820
+ function validateFrameTypeStream(frameType, streamId) {
1821
+ if ((frameType === "SETTINGS" || frameType === "PING" || frameType === "GOAWAY") && streamId !== 0) throw new Error(`${frameType} frames must use streamId 0`);
1822
+ if ((frameType === "DATA" || frameType === "RST_STREAM") && streamId === 0) throw new Error(`${frameType} frames must use a non-zero streamId`);
1823
+ }
1824
+ function buildHttp2Frame(input) {
1825
+ const streamId = input.streamId ?? 0;
1826
+ assertIntegerInRange(streamId, "streamId", 0, HTTP2_MAX_STREAM_ID);
1827
+ validateFrameTypeStream(input.frameType, streamId);
1828
+ const { payload, typeCode, flags } = buildFramePayload(input);
1829
+ if (payload.length > HTTP2_MAX_FRAME_SIZE) throw new Error(`payload exceeds the HTTP/2 maximum frame size of ${String(HTTP2_MAX_FRAME_SIZE)} bytes`);
1830
+ const header = Buffer.alloc(9);
1831
+ header[0] = payload.length >>> 16 & 255;
1832
+ header[1] = payload.length >>> 8 & 255;
1833
+ header[2] = payload.length & 255;
1834
+ header[3] = typeCode & 255;
1835
+ header[4] = flags & 255;
1836
+ header.writeUInt32BE(streamId >>> 0, 5);
1837
+ header[5] = header[5] & 127;
1838
+ const frame = Buffer.concat([header, payload]);
1839
+ return {
1840
+ frameType: input.frameType,
1841
+ typeCode,
1842
+ streamId,
1843
+ flags,
1844
+ payloadBytes: payload.length,
1845
+ payloadHex: payload.toString("hex"),
1846
+ frameHeaderHex: header.toString("hex"),
1847
+ frameHex: frame.toString("hex")
1848
+ };
1849
+ }
1850
+ //#endregion
1851
+ //#region src/server/domains/network/handlers/raw-helpers.ts
1852
+ const HTTP_TOKEN_RE = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
1853
+ function parseOptionalString(value, field) {
1854
+ if (value === void 0) return void 0;
1855
+ if (typeof value !== "string") throw new Error(`${field} must be a string`);
1856
+ const trimmed = value.trim();
1857
+ return trimmed.length > 0 ? trimmed : void 0;
1858
+ }
1859
+ function parseRawString(value, field, options = {}) {
1860
+ if (value === void 0) return void 0;
1861
+ if (typeof value !== "string") throw new Error(`${field} must be a string`);
1862
+ if (value.length === 0 && !options.allowEmpty) return void 0;
1863
+ return value;
1864
+ }
1865
+ function parseOptionalBoolean(value, field) {
1866
+ if (value === void 0) return void 0;
1867
+ if (typeof value !== "boolean") throw new Error(`${field} must be a boolean`);
1868
+ return value;
1869
+ }
1870
+ function parseStringArray(value, field) {
1871
+ if (value === void 0) return [];
1872
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) throw new Error(`${field} must be an array of strings`);
1873
+ return value.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
1874
+ }
1875
+ function parseHeaderRecord(value, field) {
1876
+ if (value === void 0) return void 0;
1877
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${field} must be an object`);
1878
+ const headers = {};
1879
+ for (const [name, headerValue] of Object.entries(value)) {
1880
+ if (!HTTP_TOKEN_RE.test(name)) throw new Error(`${field} contains an invalid HTTP header name: ${name}`);
1881
+ if (typeof headerValue !== "string") throw new Error(`${field}.${name} must be a string`);
1882
+ headers[name] = headerValue;
1883
+ }
1884
+ return headers;
1885
+ }
1886
+ function parseNetworkAuthorization(value, field = "authorization") {
1887
+ if (value === void 0) return void 0;
1888
+ if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${field} must be an object`);
1889
+ const record = value;
1890
+ const allowedHosts = parseStringArray(record.allowedHosts, `${field}.allowedHosts`);
1891
+ const allowedCidrs = parseStringArray(record.allowedCidrs, `${field}.allowedCidrs`);
1892
+ const allowPrivateNetwork = parseOptionalBoolean(record.allowPrivateNetwork, `${field}.allowPrivateNetwork`);
1893
+ const allowInsecureHttp = parseOptionalBoolean(record.allowInsecureHttp, `${field}.allowInsecureHttp`);
1894
+ const expiresAt = parseOptionalString(record.expiresAt, `${field}.expiresAt`);
1895
+ const reason = parseOptionalString(record.reason, `${field}.reason`);
1896
+ const authorization = {};
1897
+ if (allowedHosts.length > 0) authorization.allowedHosts = allowedHosts;
1898
+ if (allowedCidrs.length > 0) authorization.allowedCidrs = allowedCidrs;
1899
+ if (allowPrivateNetwork !== void 0) authorization.allowPrivateNetwork = allowPrivateNetwork;
1900
+ if (allowInsecureHttp !== void 0) authorization.allowInsecureHttp = allowInsecureHttp;
1901
+ if (expiresAt !== void 0) authorization.expiresAt = expiresAt;
1902
+ if (reason !== void 0) authorization.reason = reason;
1903
+ return authorization;
1904
+ }
1905
+ function clamp(value, min, max) {
1906
+ return Math.min(Math.max(value, min), max);
1907
+ }
1908
+ function roundMs(ms) {
1909
+ return Math.round(ms * 100) / 100;
1910
+ }
1911
+ function computeRttStats(samples) {
1912
+ const sorted = [...samples].toSorted((a, b) => a - b);
1913
+ if (sorted.length === 0) return null;
1914
+ return {
1915
+ count: sorted.length,
1916
+ minMs: sorted[0],
1917
+ maxMs: sorted[sorted.length - 1],
1918
+ avgMs: roundMs(sorted.reduce((s, v) => s + v, 0) / sorted.length),
1919
+ p50Ms: sorted[Math.floor(sorted.length * .5)],
1920
+ p95Ms: sorted[Math.floor(sorted.length * .95)]
1921
+ };
1922
+ }
1923
+ async function resolveAuthorizedTransportTarget(rawUrl, authorization, operationLabel) {
1924
+ let url;
1925
+ try {
1926
+ url = new URL(rawUrl);
1927
+ } catch {
1928
+ throw new Error("url must be an absolute http:// or https:// URL");
1929
+ }
1930
+ if (url.protocol !== "http:" && url.protocol !== "https:") throw new Error("url must use the http:// or https:// scheme");
1931
+ const authorizationPolicy = createNetworkAuthorizationPolicy(authorization);
1932
+ const allowLegacyLocalSsrf = !authorizationPolicy && isLocalSsrfBypassEnabled();
1933
+ if (authorizationPolicy && (authorizationPolicy.allowPrivateNetwork || authorizationPolicy.allowInsecureHttp) && !hasAuthorizedTargets(authorizationPolicy)) throw new Error("authorization must include at least one allowed host or CIDR when enabling private network or insecure HTTP access.");
1934
+ if (isNetworkAuthorizationExpired(authorizationPolicy)) throw new Error("authorization expired before the request was executed.");
1935
+ let target;
1936
+ try {
1937
+ target = await resolveNetworkTarget(url.toString());
1938
+ } catch {
1939
+ throw new Error(`${operationLabel} blocked: DNS resolution failed for "${url.toString()}"`);
1940
+ }
1941
+ const isPrivateTargetAllowed = (resolvedTarget) => {
1942
+ if (allowLegacyLocalSsrf) return true;
1943
+ return authorizationPolicy?.allowPrivateNetwork === true && isAuthorizedNetworkTarget(authorizationPolicy, resolvedTarget);
1944
+ };
1945
+ const isInsecureHttpAllowed = (resolvedTarget) => {
1946
+ if (allowLegacyLocalSsrf) return true;
1947
+ if (isLoopbackHost(resolvedTarget.hostname)) return true;
1948
+ return authorizationPolicy?.allowInsecureHttp === true && isAuthorizedNetworkTarget(authorizationPolicy, resolvedTarget);
1949
+ };
1950
+ const effectivePort = Number.parseInt(url.port || (url.protocol === "https:" ? "443" : "80"), 10);
1951
+ if (url.protocol === "http:" && !isInsecureHttpAllowed(target)) throw new Error(`${operationLabel} blocked: insecure HTTP is only allowed for loopback or explicitly authorized targets, got "${target.hostname}:${String(effectivePort)}"`);
1952
+ const hostnameIsPrivate = isPrivateHost(target.hostname);
1953
+ const resolvedAddressIsPrivate = isPrivateHost(target.resolvedAddress ?? "");
1954
+ const loopbackTarget = isLoopbackHost(target.hostname) || isLoopbackHost(target.resolvedAddress ?? "");
1955
+ if ((hostnameIsPrivate || resolvedAddressIsPrivate) && !loopbackTarget && !isPrivateTargetAllowed(target)) {
1956
+ if (!hostnameIsPrivate && resolvedAddressIsPrivate && target.resolvedAddress) throw new Error(`${operationLabel} blocked: "${target.hostname}:${String(effectivePort)}" resolved to private IP ${target.resolvedAddress}`);
1957
+ throw new Error(`${operationLabel} blocked: target "${target.hostname}:${String(effectivePort)}" resolves to a private or reserved address.`);
1958
+ }
1959
+ return {
1960
+ url,
1961
+ target,
1962
+ authorizationPolicy,
1963
+ allowLegacyLocalSsrf
1964
+ };
1965
+ }
1966
+ function normalizeTargetHost(host) {
1967
+ const trimmed = host.trim();
1968
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) return trimmed.slice(1, -1);
1969
+ return trimmed;
1970
+ }
1971
+ function formatHostForUrl(host) {
1972
+ return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
1973
+ }
1974
+ function getRequestMethod(requestText) {
1975
+ const method = (requestText.split(/\r?\n/, 1)[0]?.trim() ?? "").split(/\s+/, 1)[0]?.trim().toUpperCase() ?? "";
1976
+ if (!HTTP_TOKEN_RE.test(method)) throw new Error("requestText must start with a valid HTTP request line");
1977
+ return method;
1978
+ }
1979
+ async function exchangePlainHttp(host, port, requestBuffer, requestMethod, timeoutMs, maxResponseBytes) {
1980
+ return await new Promise((resolve, reject) => {
1981
+ const socket = net.createConnection({
1982
+ host,
1983
+ port
1984
+ });
1985
+ let settled = false;
1986
+ let sawData = false;
1987
+ const responseChain = new BufferChain();
1988
+ const cleanup = () => {
1989
+ socket.removeAllListeners();
1990
+ socket.destroy();
1991
+ };
1992
+ const finalize = (endedBy) => {
1993
+ if (settled) return;
1994
+ settled = true;
1995
+ cleanup();
1996
+ resolve({
1997
+ rawResponse: responseChain.toBuffer(),
1998
+ endedBy
1999
+ });
2000
+ };
2001
+ const fail = (error) => {
2002
+ if (settled) return;
2003
+ settled = true;
2004
+ cleanup();
2005
+ reject(error);
2006
+ };
2007
+ socket.setTimeout(timeoutMs);
2008
+ socket.once("connect", () => {
2009
+ socket.end(requestBuffer);
2010
+ });
2011
+ socket.on("data", (chunk) => {
2012
+ sawData = true;
2013
+ responseChain.append(chunk);
2014
+ if (responseChain.length > maxResponseBytes) {
2015
+ finalize("max-bytes");
2016
+ return;
2017
+ }
2018
+ const analysis = analyzeHttpResponse(responseChain.toBuffer(), requestMethod);
2019
+ if (!analysis || !analysis.complete) return;
2020
+ if (analysis.bodyMode === "none") {
2021
+ finalize("no-body");
2022
+ return;
2023
+ }
2024
+ if (analysis.bodyMode === "content-length") {
2025
+ finalize("content-length");
2026
+ return;
2027
+ }
2028
+ if (analysis.bodyMode === "chunked") finalize("chunked");
2029
+ });
2030
+ socket.once("timeout", () => {
2031
+ if (!sawData) {
2032
+ fail(/* @__PURE__ */ new Error(`Timed out waiting for HTTP response from ${host}:${String(port)}`));
2033
+ return;
2034
+ }
2035
+ finalize("timeout");
2036
+ });
2037
+ socket.once("end", () => {
2038
+ finalize("socket-close");
2039
+ });
2040
+ socket.once("error", (error) => {
2041
+ fail(error);
2042
+ });
2043
+ });
2044
+ }
2045
+ function normalizeHttp2HeaderValue(value) {
2046
+ if (value === void 0) return null;
2047
+ if (Array.isArray(value)) return value.map((entry) => String(entry));
2048
+ return String(value);
2049
+ }
2050
+ function normalizeHttp2Headers(headers) {
2051
+ const normalized = {};
2052
+ for (const [name, value] of Object.entries(headers)) {
2053
+ const normalizedValue = normalizeHttp2HeaderValue(value);
2054
+ if (normalizedValue !== null) normalized[name] = normalizedValue;
2055
+ }
2056
+ return normalized;
2057
+ }
2058
+ function normalizeAlpnProtocol(protocol) {
2059
+ if (!protocol) return null;
2060
+ const trimmed = protocol.trim();
2061
+ return trimmed.length > 0 ? trimmed : null;
2062
+ }
2063
+ function toHttp2RequestHeaders(headers) {
2064
+ const output = {};
2065
+ for (const [name, value] of Object.entries(headers ?? {})) output[name.toLowerCase()] = value;
2066
+ return output;
2067
+ }
2068
+ function performHttp2ProbeInternal(options) {
2069
+ const { url, target, method, requestHeaders, bodyBuffer, timeoutMs, maxBodyBytes, effectivePort, requestedAlpnProtocols } = options;
2070
+ let observedAlpnProtocol = null;
2071
+ return new Promise((resolve, reject) => {
2072
+ let settled = false;
2073
+ let responseHeaders;
2074
+ const bodyChain = new BufferChain();
2075
+ let truncated = false;
2076
+ let request = null;
2077
+ let connectedSocket = null;
2078
+ const session = http2.connect(url.origin, { createConnection: () => {
2079
+ if (url.protocol === "https:") {
2080
+ const socket = tls.connect({
2081
+ host: target.resolvedAddress ?? target.hostname,
2082
+ port: effectivePort,
2083
+ servername: target.hostname,
2084
+ ALPNProtocols: requestedAlpnProtocols,
2085
+ rejectUnauthorized: true
2086
+ });
2087
+ socket.setTimeout(timeoutMs, () => {
2088
+ socket.destroy(/* @__PURE__ */ new Error(`Timed out probing HTTP/2 endpoint ${url.toString()}`));
2089
+ });
2090
+ socket.once("secureConnect", () => {
2091
+ observedAlpnProtocol = normalizeAlpnProtocol(socket.alpnProtocol);
2092
+ });
2093
+ connectedSocket = socket;
2094
+ return socket;
2095
+ }
2096
+ const socket = net.connect({
2097
+ host: target.resolvedAddress ?? target.hostname,
2098
+ port: effectivePort
2099
+ });
2100
+ socket.setTimeout(timeoutMs, () => {
2101
+ socket.destroy(/* @__PURE__ */ new Error(`Timed out probing HTTP/2 endpoint ${url.toString()}`));
2102
+ });
2103
+ connectedSocket = socket;
2104
+ return socket;
2105
+ } });
2106
+ const cleanup = () => {
2107
+ request?.removeAllListeners();
2108
+ session.removeAllListeners();
2109
+ };
2110
+ const finish = () => {
2111
+ if (settled) return;
2112
+ settled = true;
2113
+ cleanup();
2114
+ session.close();
2115
+ resolve({
2116
+ responseHeaders: responseHeaders ?? {},
2117
+ bodyBuffer: bodyChain.toBuffer(),
2118
+ truncated,
2119
+ alpnProtocol: observedAlpnProtocol
2120
+ });
2121
+ };
2122
+ const fail = (error) => {
2123
+ if (settled) return;
2124
+ settled = true;
2125
+ cleanup();
2126
+ session.destroy(error);
2127
+ reject(error);
2128
+ };
2129
+ session.once("error", (error) => {
2130
+ if (connectedSocket instanceof tls.TLSSocket) observedAlpnProtocol = normalizeAlpnProtocol(connectedSocket.alpnProtocol);
2131
+ fail(error instanceof Error ? error : new Error(String(error)));
2132
+ });
2133
+ session.once("connect", () => {
2134
+ if (connectedSocket instanceof tls.TLSSocket) observedAlpnProtocol = normalizeAlpnProtocol(connectedSocket.alpnProtocol);
2135
+ request = session.request({
2136
+ ":method": method,
2137
+ ":path": `${url.pathname}${url.search}`,
2138
+ ":scheme": url.protocol.slice(0, -1),
2139
+ ":authority": url.host,
2140
+ ...requestHeaders
2141
+ });
2142
+ request.once("response", (headers) => {
2143
+ responseHeaders = headers;
2144
+ });
2145
+ request.on("data", (chunk) => {
2146
+ const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk, "utf8");
2147
+ const remaining = maxBodyBytes - bodyChain.length;
2148
+ if (remaining > 0) bodyChain.append(buffer.subarray(0, remaining));
2149
+ if (buffer.length > remaining && !truncated) {
2150
+ truncated = true;
2151
+ request?.close(http2.constants.NGHTTP2_CANCEL);
2152
+ }
2153
+ });
2154
+ request.once("end", finish);
2155
+ request.once("close", () => {
2156
+ if (truncated) finish();
2157
+ });
2158
+ request.once("error", (error) => {
2159
+ fail(error instanceof Error ? error : new Error(String(error)));
2160
+ });
2161
+ if (bodyBuffer.length > 0) request.end(bodyBuffer);
2162
+ else request.end();
2163
+ });
2164
+ });
2165
+ }
2166
+ //#endregion
2167
+ //#region src/native/IcmpProbe.ts
2168
+ /**
2169
+ * Cross-platform ICMP probe and traceroute via koffi FFI.
2170
+ *
2171
+ * Windows: IcmpSendEcho from iphlpapi.dll (no admin required).
2172
+ * Linux/macOS: Raw ICMP sockets via libc (requires root/CAP_NET_RAW).
2173
+ *
2174
+ * Uses Buffer-based struct parsing (same pattern as Win32API.ts)
2175
+ * to avoid koffi struct registration issues in test environments.
2176
+ */
2177
+ function ipToString(addr) {
2178
+ return `${addr & 255}.${addr >>> 8 & 255}.${addr >>> 16 & 255}.${addr >>> 24 & 255}`;
2179
+ }
2180
+ function isValidIpv4(ip) {
2181
+ const parts = ip.split(".");
2182
+ if (parts.length !== 4) return false;
2183
+ return parts.every((p) => {
2184
+ const n = parseInt(p, 10);
2185
+ return !isNaN(n) && n >= 0 && n <= 255 && p === String(n);
2186
+ });
2187
+ }
2188
+ let _available = null;
2189
+ const IP_STATUS = {
2190
+ 0: "SUCCESS",
2191
+ 11001: "BUF_TOO_SMALL",
2192
+ 11002: "DEST_NET_UNREACHABLE",
2193
+ 11003: "DEST_HOST_UNREACHABLE",
2194
+ 11004: "DEST_PROT_UNREACHABLE",
2195
+ 11005: "DEST_PORT_UNREACHABLE",
2196
+ 11009: "PACKET_TOO_BIG",
2197
+ 11010: "REQ_TIMED_OUT",
2198
+ 11013: "TTL_EXPIRED_TRANSIT",
2199
+ 11014: "TTL_EXPIRED_REASSEM",
2200
+ 11015: "PARAM_PROBLEM",
2201
+ 11016: "SOURCE_QUENCH",
2202
+ 11050: "GENERAL_FAILURE"
2203
+ };
2204
+ function winStatusLabel(s) {
2205
+ return IP_STATUS[s] ?? `UNKNOWN_${s}`;
2206
+ }
2207
+ function winStatusClass(s) {
2208
+ if (s === 0) return "success";
2209
+ if (s === 11010) return "timeout";
2210
+ if (s === 11013 || s === 11014) return "time_exceeded";
2211
+ if (s >= 11002 && s <= 11005) return "destination_unreachable";
2212
+ if (s === 11016) return "source_quench";
2213
+ if (s === 11009) return "packet_too_big";
2214
+ if (s === 11015) return "parameter_problem";
2215
+ return "error";
2216
+ }
2217
+ let iphlpapi = null;
2218
+ let ws2_32 = null;
2219
+ function getIphlpapi() {
2220
+ if (!iphlpapi) {
2221
+ iphlpapi = koffi.load("iphlpapi.dll");
2222
+ logger.debug("Loaded iphlpapi.dll via koffi");
2223
+ }
2224
+ return iphlpapi;
2225
+ }
2226
+ function getWs2_32() {
2227
+ if (!ws2_32) {
2228
+ ws2_32 = koffi.load("ws2_32.dll");
2229
+ logger.debug("Loaded ws2_32.dll via koffi");
2230
+ }
2231
+ return ws2_32;
2232
+ }
2233
+ const IP_OPT_SIZE = 16;
2234
+ const MIN_REPLY_BUF_SIZE = 256;
2235
+ const ICMP_REPLY_OVERHEAD = 64;
2236
+ function getReplyBufferSize(packetSize) {
2237
+ return Math.max(MIN_REPLY_BUF_SIZE, packetSize + ICMP_REPLY_OVERHEAD);
2238
+ }
2239
+ function buildOptionBuf(ttl) {
2240
+ const buf = Buffer.alloc(IP_OPT_SIZE, 0);
2241
+ buf.writeUInt8(ttl, 0);
2242
+ return buf;
2243
+ }
2244
+ function parseReply(buf) {
2245
+ return {
2246
+ address: buf.readUInt32LE(0),
2247
+ status: buf.readUInt32LE(4),
2248
+ rtt: buf.readUInt32LE(8)
2249
+ };
2250
+ }
2251
+ function win_inet_addr(ip) {
2252
+ return getWs2_32().func("uint32 inet_addr(char *)")(ip);
2253
+ }
2254
+ function win_IcmpCreateFile() {
2255
+ return getIphlpapi().func("void * IcmpCreateFile()")();
2256
+ }
2257
+ function win_IcmpCloseHandle(h) {
2258
+ return getIphlpapi().func("int IcmpCloseHandle(void *)")(h) !== 0;
2259
+ }
2260
+ function win_IcmpSendEcho(handle, destAddr, sendData, optionBuf, timeoutMs) {
2261
+ const fn = getIphlpapi().func("uint32 IcmpSendEcho(void *, uint32, void *, uint16, void *, void *, uint32, uint32)");
2262
+ const replyBuf = Buffer.alloc(getReplyBufferSize(sendData.length));
2263
+ const n = fn(handle, destAddr, sendData, sendData.length, optionBuf, replyBuf, replyBuf.length, timeoutMs);
2264
+ return {
2265
+ numReplies: Number(n),
2266
+ replyBuf
2267
+ };
2268
+ }
2269
+ function winIcmpProbe(params) {
2270
+ const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2271
+ const destAddr = win_inet_addr(target);
2272
+ if (destAddr === 4294967295) return {
2273
+ target,
2274
+ ip: "",
2275
+ alive: false,
2276
+ rtt: null,
2277
+ ttl,
2278
+ icmpStatus: "INVALID_ADDRESS",
2279
+ errorClass: "error",
2280
+ packetSize
2281
+ };
2282
+ const handle = win_IcmpCreateFile();
2283
+ try {
2284
+ const { numReplies, replyBuf } = win_IcmpSendEcho(handle, destAddr, Buffer.alloc(packetSize, 170), buildOptionBuf(ttl), timeout);
2285
+ if (numReplies === 0) return {
2286
+ target,
2287
+ ip: ipToString(destAddr),
2288
+ alive: false,
2289
+ rtt: null,
2290
+ ttl,
2291
+ icmpStatus: "REQ_TIMED_OUT",
2292
+ errorClass: "timeout",
2293
+ packetSize
2294
+ };
2295
+ const reply = parseReply(replyBuf);
2296
+ return {
2297
+ target,
2298
+ ip: ipToString(reply.address),
2299
+ alive: reply.status === 0,
2300
+ rtt: reply.status === 0 ? reply.rtt : null,
2301
+ ttl,
2302
+ icmpStatus: winStatusLabel(reply.status),
2303
+ errorClass: winStatusClass(reply.status),
2304
+ packetSize
2305
+ };
2306
+ } finally {
2307
+ win_IcmpCloseHandle(handle);
2308
+ }
2309
+ }
2310
+ function winTraceroute(params) {
2311
+ const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2312
+ const destAddr = win_inet_addr(target);
2313
+ if (destAddr === 4294967295) return {
2314
+ target,
2315
+ ip: "",
2316
+ hops: [],
2317
+ reached: false,
2318
+ totalHops: 0,
2319
+ totalTime: 0
2320
+ };
2321
+ const handle = win_IcmpCreateFile();
2322
+ const hops = [];
2323
+ const t0 = performance.now();
2324
+ try {
2325
+ for (let ttl = 1; ttl <= maxHops; ttl++) {
2326
+ const { numReplies, replyBuf } = win_IcmpSendEcho(handle, destAddr, Buffer.alloc(packetSize, 170), buildOptionBuf(ttl), timeout);
2327
+ if (numReplies === 0) {
2328
+ hops.push({
2329
+ hop: ttl,
2330
+ ip: null,
2331
+ rtt: null,
2332
+ status: "REQ_TIMED_OUT",
2333
+ errorClass: "timeout"
2334
+ });
2335
+ continue;
2336
+ }
2337
+ const reply = parseReply(replyBuf);
2338
+ const hopIp = ipToString(reply.address);
2339
+ hops.push({
2340
+ hop: ttl,
2341
+ ip: hopIp,
2342
+ rtt: reply.rtt,
2343
+ status: winStatusLabel(reply.status),
2344
+ errorClass: winStatusClass(reply.status)
2345
+ });
2346
+ if (reply.status === 0) break;
2347
+ }
2348
+ } finally {
2349
+ win_IcmpCloseHandle(handle);
2350
+ }
2351
+ const last = hops[hops.length - 1];
2352
+ return {
2353
+ target,
2354
+ ip: ipToString(destAddr),
2355
+ hops,
2356
+ reached: last?.status === "SUCCESS",
2357
+ totalHops: hops.length,
2358
+ totalTime: Math.round((performance.now() - t0) * 100) / 100
2359
+ };
2360
+ }
2361
+ const AF_INET = 2;
2362
+ const SOCK_RAW = 3;
2363
+ const IPPROTO_ICMP = 1;
2364
+ const IPPROTO_IP = 0;
2365
+ const IP_TTL = 2;
2366
+ const SOL_SOCKET = 1;
2367
+ const SO_RCVTIMEO = process.platform === "darwin" ? 4102 : 20;
2368
+ const POSIX_LIB = process.platform === "darwin" ? "/usr/lib/libSystem.B.dylib" : "libc.so.6";
2369
+ let posixLib = null;
2370
+ function getPosixLib() {
2371
+ if (!posixLib) {
2372
+ posixLib = koffi.load(POSIX_LIB);
2373
+ logger.debug(`Loaded ${POSIX_LIB} via koffi for ICMP`);
2374
+ }
2375
+ return posixLib;
2376
+ }
2377
+ function posixSocket(domain, type, protocol) {
2378
+ return getPosixLib().func("int socket(int, int, int)")(domain, type, protocol);
2379
+ }
2380
+ function posixSetsockopt(fd, level, optname, optval, optlen) {
2381
+ return getPosixLib().func("int setsockopt(int, int, int, void *, int)")(fd, level, optname, optval, optlen);
2382
+ }
2383
+ function posixSendto(fd, buf, addr) {
2384
+ return getPosixLib().func("int sendto(int, void *, int, int, void *, int)")(fd, buf, buf.length, 0, addr, 16);
2385
+ }
2386
+ function posixRecv(fd, buf) {
2387
+ return getPosixLib().func("int recv(int, void *, int, int)")(fd, buf, buf.length, 0);
2388
+ }
2389
+ function posixClose(fd) {
2390
+ return getPosixLib().func("int close(int)")(fd);
2391
+ }
2392
+ function computeChecksum(buf) {
2393
+ let sum = 0;
2394
+ for (let i = 0; i < buf.length - 1; i += 2) sum += buf.readUInt16BE(i);
2395
+ if (buf.length & 1) sum += (buf[buf.length - 1] ?? 0) << 8;
2396
+ while (sum > 65535) sum = (sum & 65535) + (sum >>> 16);
2397
+ return ~sum & 65535;
2398
+ }
2399
+ function buildIcmpEcho(id, seq, payloadSize) {
2400
+ const buf = Buffer.alloc(8 + payloadSize);
2401
+ buf[0] = 8;
2402
+ buf[1] = 0;
2403
+ buf.writeUInt16BE(id & 65535, 4);
2404
+ buf.writeUInt16BE(seq & 65535, 6);
2405
+ for (let i = 8; i < buf.length; i++) buf[i] = 170;
2406
+ buf.writeUInt16BE(computeChecksum(buf), 2);
2407
+ return buf;
2408
+ }
2409
+ function buildSockaddrIn(ip) {
2410
+ const buf = Buffer.alloc(16, 0);
2411
+ buf.writeUInt16LE(AF_INET, 0);
2412
+ const parts = ip.split(".").map(Number);
2413
+ buf[4] = parts[0] ?? 0;
2414
+ buf[5] = parts[1] ?? 0;
2415
+ buf[6] = parts[2] ?? 0;
2416
+ buf[7] = parts[3] ?? 0;
2417
+ return buf;
2418
+ }
2419
+ function parseIcmpPacket(buf, n, expectedId) {
2420
+ if (n < 20) return null;
2421
+ const ihl = ((buf[0] ?? 0) & 15) * 4;
2422
+ if (n < ihl + 8) return null;
2423
+ const icmpType = buf[ihl] ?? 0;
2424
+ const icmpCode = buf[ihl + 1] ?? 0;
2425
+ const fromIp = buf.readUInt32LE(12);
2426
+ if (icmpType === 0) {
2427
+ if (buf.readUInt16BE(ihl + 4) !== expectedId) return null;
2428
+ return {
2429
+ type: icmpType,
2430
+ code: icmpCode,
2431
+ fromIp
2432
+ };
2433
+ }
2434
+ if (icmpType === 11 || icmpType === 3) {
2435
+ const origStart = ihl + 8;
2436
+ if (n < origStart + 28) return null;
2437
+ const origIhl = ((buf[origStart] ?? 0) & 15) * 4;
2438
+ if (n < origStart + origIhl + 8) return null;
2439
+ if (buf.readUInt16BE(origStart + origIhl + 4) !== expectedId) return null;
2440
+ return {
2441
+ type: icmpType,
2442
+ code: icmpCode,
2443
+ fromIp
2444
+ };
2445
+ }
2446
+ return null;
2447
+ }
2448
+ function posixStatusLabel(type, code, timedOut) {
2449
+ if (type === 0) return "SUCCESS";
2450
+ if (timedOut) return "REQ_TIMED_OUT";
2451
+ if (type === 11 && code === 0) return "TTL_EXPIRED_TRANSIT";
2452
+ if (type === 11 && code === 1) return "TTL_EXPIRED_REASSEM";
2453
+ if (type === 3 && code === 0) return "DEST_NET_UNREACHABLE";
2454
+ if (type === 3 && code === 1) return "DEST_HOST_UNREACHABLE";
2455
+ if (type === 3 && code === 2) return "DEST_PROT_UNREACHABLE";
2456
+ if (type === 3 && code === 3) return "DEST_PORT_UNREACHABLE";
2457
+ return `UNKNOWN_${type}_${code}`;
2458
+ }
2459
+ function posixErrorClass(type, _code, timedOut) {
2460
+ if (type === 0) return "success";
2461
+ if (timedOut) return "timeout";
2462
+ if (type === 11) return "time_exceeded";
2463
+ if (type === 3) return "destination_unreachable";
2464
+ return "error";
2465
+ }
2466
+ function posixSetTtl(fd, ttl) {
2467
+ const buf = Buffer.alloc(4);
2468
+ buf.writeInt32LE(ttl);
2469
+ posixSetsockopt(fd, IPPROTO_IP, IP_TTL, buf, 4);
2470
+ }
2471
+ function posixSetRecvTimeout(fd, timeoutMs) {
2472
+ const tv = Buffer.alloc(16, 0);
2473
+ tv.writeInt32LE(Math.floor(timeoutMs / 1e3), 0);
2474
+ tv.writeInt32LE(timeoutMs % 1e3 * 1e3, 8);
2475
+ posixSetsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, tv, 16);
2476
+ }
2477
+ function posixIcmpProbe(params) {
2478
+ const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2479
+ if (!isValidIpv4(target)) return {
2480
+ target,
2481
+ ip: "",
2482
+ alive: false,
2483
+ rtt: null,
2484
+ ttl,
2485
+ icmpStatus: "INVALID_ADDRESS",
2486
+ errorClass: "error",
2487
+ packetSize
2488
+ };
2489
+ const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2490
+ if (fd < 0) return {
2491
+ target,
2492
+ ip: "",
2493
+ alive: false,
2494
+ rtt: null,
2495
+ ttl,
2496
+ icmpStatus: "SOCKET_ERROR",
2497
+ errorClass: "error",
2498
+ packetSize
2499
+ };
2500
+ try {
2501
+ posixSetTtl(fd, ttl);
2502
+ posixSetRecvTimeout(fd, timeout);
2503
+ const id = process.pid & 65535;
2504
+ const packet = buildIcmpEcho(id, 1, packetSize);
2505
+ const destAddr = buildSockaddrIn(target);
2506
+ const t0 = performance.now();
2507
+ if (posixSendto(fd, packet, destAddr) < 0) return {
2508
+ target,
2509
+ ip: target,
2510
+ alive: false,
2511
+ rtt: null,
2512
+ ttl,
2513
+ icmpStatus: "SEND_ERROR",
2514
+ errorClass: "error",
2515
+ packetSize
2516
+ };
2517
+ const recvBuf = Buffer.alloc(512);
2518
+ const n = posixRecv(fd, recvBuf);
2519
+ const rtt = Math.round(performance.now() - t0);
2520
+ if (n <= 0) return {
2521
+ target,
2522
+ ip: target,
2523
+ alive: false,
2524
+ rtt: null,
2525
+ ttl,
2526
+ icmpStatus: "REQ_TIMED_OUT",
2527
+ errorClass: "timeout",
2528
+ packetSize
2529
+ };
2530
+ const reply = parseIcmpPacket(recvBuf, n, id);
2531
+ if (!reply) return {
2532
+ target,
2533
+ ip: target,
2534
+ alive: false,
2535
+ rtt: null,
2536
+ ttl,
2537
+ icmpStatus: "UNEXPECTED_REPLY",
2538
+ errorClass: "error",
2539
+ packetSize
2540
+ };
2541
+ const alive = reply.type === 0;
2542
+ return {
2543
+ target,
2544
+ ip: ipToString(reply.fromIp),
2545
+ alive,
2546
+ rtt: alive ? rtt : null,
2547
+ ttl,
2548
+ icmpStatus: posixStatusLabel(reply.type, reply.code, false),
2549
+ errorClass: posixErrorClass(reply.type, reply.code, false),
2550
+ packetSize
2551
+ };
2552
+ } finally {
2553
+ posixClose(fd);
2554
+ }
2555
+ }
2556
+ function posixTraceroute(params) {
2557
+ const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2558
+ if (!isValidIpv4(target)) return {
2559
+ target,
2560
+ ip: "",
2561
+ hops: [],
2562
+ reached: false,
2563
+ totalHops: 0,
2564
+ totalTime: 0
2565
+ };
2566
+ const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2567
+ if (fd < 0) return {
2568
+ target,
2569
+ ip: "",
2570
+ hops: [],
2571
+ reached: false,
2572
+ totalHops: 0,
2573
+ totalTime: 0
2574
+ };
2575
+ const hops = [];
2576
+ const id = process.pid & 65535;
2577
+ const destAddr = buildSockaddrIn(target);
2578
+ const t0 = performance.now();
2579
+ const MAX_CONSECUTIVE_SEND_ERRORS = 5;
2580
+ let consecutiveSendErrors = 0;
2581
+ try {
2582
+ posixSetRecvTimeout(fd, timeout);
2583
+ for (let ttl = 1; ttl <= maxHops; ttl++) {
2584
+ posixSetTtl(fd, ttl);
2585
+ const packet = buildIcmpEcho(id, ttl, packetSize);
2586
+ const sendT0 = performance.now();
2587
+ if (posixSendto(fd, packet, destAddr) < 0) {
2588
+ consecutiveSendErrors++;
2589
+ hops.push({
2590
+ hop: ttl,
2591
+ ip: null,
2592
+ rtt: null,
2593
+ status: "SEND_ERROR",
2594
+ errorClass: "error"
2595
+ });
2596
+ if (consecutiveSendErrors >= MAX_CONSECUTIVE_SEND_ERRORS) break;
2597
+ continue;
2598
+ }
2599
+ consecutiveSendErrors = 0;
2600
+ const recvBuf = Buffer.alloc(512);
2601
+ const n = posixRecv(fd, recvBuf);
2602
+ const rtt = Math.round(performance.now() - sendT0);
2603
+ if (n <= 0) {
2604
+ hops.push({
2605
+ hop: ttl,
2606
+ ip: null,
2607
+ rtt: null,
2608
+ status: "REQ_TIMED_OUT",
2609
+ errorClass: "timeout"
2610
+ });
2611
+ continue;
2612
+ }
2613
+ const reply = parseIcmpPacket(recvBuf, n, id);
2614
+ if (!reply) {
2615
+ hops.push({
2616
+ hop: ttl,
2617
+ ip: null,
2618
+ rtt: null,
2619
+ status: "UNEXPECTED_REPLY",
2620
+ errorClass: "error"
2621
+ });
2622
+ continue;
2623
+ }
2624
+ const status = posixStatusLabel(reply.type, reply.code, false);
2625
+ const errorCls = posixErrorClass(reply.type, reply.code, false);
2626
+ hops.push({
2627
+ hop: ttl,
2628
+ ip: ipToString(reply.fromIp),
2629
+ rtt,
2630
+ status,
2631
+ errorClass: errorCls
2632
+ });
2633
+ if (reply.type === 0) break;
2634
+ }
2635
+ } finally {
2636
+ posixClose(fd);
2637
+ }
2638
+ return {
2639
+ target,
2640
+ ip: target,
2641
+ hops,
2642
+ reached: hops[hops.length - 1]?.status === "SUCCESS",
2643
+ totalHops: hops.length,
2644
+ totalTime: Math.round((performance.now() - t0) * 100) / 100
2645
+ };
2646
+ }
2647
+ const isPosix = process.platform === "linux" || process.platform === "darwin";
2648
+ function isIcmpAvailable() {
2649
+ if (_available !== null) return _available;
2650
+ if (process.platform === "win32") try {
2651
+ koffi.load("iphlpapi.dll").unload();
2652
+ _available = true;
2653
+ return true;
2654
+ } catch {
2655
+ _available = false;
2656
+ return false;
2657
+ }
2658
+ if (isPosix) {
2659
+ try {
2660
+ const fd = posixSocket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
2661
+ if (fd >= 0) {
2662
+ posixClose(fd);
2663
+ _available = true;
2664
+ } else _available = false;
2665
+ } catch {
2666
+ _available = false;
2667
+ }
2668
+ return _available;
2669
+ }
2670
+ _available = false;
2671
+ return false;
2672
+ }
2673
+ function icmpProbe(params) {
2674
+ const { target, ttl = 128, packetSize = ICMP_DEFAULT_PACKET_SIZE, timeout = ICMP_PROBE_TIMEOUT_MS } = params;
2675
+ if (!isIcmpAvailable()) return {
2676
+ target,
2677
+ ip: "",
2678
+ alive: false,
2679
+ rtt: null,
2680
+ ttl,
2681
+ icmpStatus: "PLATFORM_NOT_SUPPORTED",
2682
+ errorClass: "error",
2683
+ packetSize
2684
+ };
2685
+ if (process.platform === "win32") return winIcmpProbe({
2686
+ target,
2687
+ ttl,
2688
+ packetSize,
2689
+ timeout
2690
+ });
2691
+ return posixIcmpProbe({
2692
+ target,
2693
+ ttl,
2694
+ packetSize,
2695
+ timeout
2696
+ });
2697
+ }
2698
+ function traceroute(params) {
2699
+ const { target, maxHops = ICMP_TRACEROUTE_MAX_HOPS, timeout = ICMP_PROBE_TIMEOUT_MS, packetSize = ICMP_DEFAULT_PACKET_SIZE } = params;
2700
+ if (!isIcmpAvailable()) return {
2701
+ target,
2702
+ ip: "",
2703
+ hops: [],
2704
+ reached: false,
2705
+ totalHops: 0,
2706
+ totalTime: 0
2707
+ };
2708
+ if (process.platform === "win32") return winTraceroute({
2709
+ target,
2710
+ maxHops,
2711
+ timeout,
2712
+ packetSize
2713
+ });
2714
+ return posixTraceroute({
2715
+ target,
2716
+ maxHops,
2717
+ timeout,
2718
+ packetSize
2719
+ });
2720
+ }
2721
+ //#endregion
2722
+ //#region src/server/domains/network/handlers/raw-handlers.ts
2723
+ /**
2724
+ * Raw HTTP/HTTP2/DNS/RTT handlers — standalone class using composition.
2725
+ *
2726
+ * Extracted from AdvancedToolHandlersRaw (handlers.impl.core.runtime.raw.ts).
2727
+ * Uses helpers from ./raw-helpers.ts and ./shared.ts instead of inheritance.
2728
+ */
2729
+ var RawHandlers = class {
2730
+ constructor(eventBus) {
2731
+ this.eventBus = eventBus;
2732
+ }
2733
+ async handleDnsResolve(args) {
2734
+ try {
2735
+ const hostname = parseOptionalString(args.hostname, "hostname");
2736
+ if (!hostname) return R.text("hostname is required", true);
2737
+ const rrType = parseOptionalString(args.rrType, "rrType") ?? "A";
2738
+ const validTypes = [
2739
+ "A",
2740
+ "AAAA",
2741
+ "MX",
2742
+ "TXT",
2743
+ "NS",
2744
+ "CNAME",
2745
+ "SOA",
2746
+ "PTR",
2747
+ "SRV",
2748
+ "ANY"
2749
+ ];
2750
+ if (!validTypes.includes(rrType)) return R.text(`Invalid rrType: "${rrType}". Expected one of: ${validTypes.join(", ")}`, true);
2751
+ const start = performance.now();
2752
+ const records = await dns.resolve(hostname, rrType);
2753
+ const timing = roundMs(performance.now() - start);
2754
+ return R.ok().json({
2755
+ hostname,
2756
+ rrType,
2757
+ records,
2758
+ timing
2759
+ });
2760
+ } catch (err) {
2761
+ const message = err instanceof Error ? err.message : String(err);
2762
+ return R.fail(`DNS resolve failed: ${message}`).json();
2763
+ }
2764
+ }
2765
+ async handleDnsReverse(args) {
2766
+ try {
2767
+ const ip = parseOptionalString(args.ip, "ip");
2768
+ if (!ip) return R.text("ip is required", true);
2769
+ const start = performance.now();
2770
+ const hostnames = await dns.reverse(ip);
2771
+ const timing = roundMs(performance.now() - start);
2772
+ return R.ok().json({
2773
+ ip,
2774
+ hostnames,
2775
+ timing
2776
+ });
2777
+ } catch (err) {
2778
+ const message = err instanceof Error ? err.message : String(err);
2779
+ return R.fail(`DNS reverse lookup failed: ${message}`).json();
2780
+ }
2781
+ }
2782
+ async handleHttpRequestBuild(args) {
2783
+ try {
2784
+ const method = parseOptionalString(args.method, "method");
2785
+ const target = parseOptionalString(args.target, "target");
2786
+ if (!method) throw new Error("method is required");
2787
+ if (!target) throw new Error("target is required");
2788
+ const built = buildHttpRequest({
2789
+ method,
2790
+ target,
2791
+ host: parseOptionalString(args.host, "host"),
2792
+ headers: parseHeaderRecord(args.headers, "headers"),
2793
+ body: parseRawString(args.body, "body", { allowEmpty: true }),
2794
+ httpVersion: parseOptionalString(args.httpVersion, "httpVersion") ?? "1.1",
2795
+ addHostHeader: parseBooleanArg(args.addHostHeader, true),
2796
+ addContentLength: parseBooleanArg(args.addContentLength, true),
2797
+ addConnectionClose: parseBooleanArg(args.addConnectionClose, true)
2798
+ });
2799
+ emitEvent(this.eventBus, "network:http_request_built", {
2800
+ method: built.startLine.split(" ", 1)[0] ?? "UNKNOWN",
2801
+ target,
2802
+ byteLength: built.requestBytes,
2803
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2804
+ });
2805
+ return R.ok().merge(built).json();
2806
+ } catch (error) {
2807
+ return R.fail(error instanceof Error ? error.message : String(error)).json();
2808
+ }
2809
+ }
2810
+ async handleHttpPlainRequest(args) {
2811
+ try {
2812
+ const hostArg = parseOptionalString(args.host, "host");
2813
+ const requestText = parseRawString(args.requestText, "requestText");
2814
+ if (!hostArg) throw new Error("host is required");
2815
+ if (!requestText) throw new Error("requestText is required");
2816
+ const host = normalizeTargetHost(hostArg);
2817
+ const port = parseNumberArg(args.port, {
2818
+ defaultValue: 80,
2819
+ min: 1,
2820
+ max: 65535,
2821
+ integer: true
2822
+ });
2823
+ const timeoutMs = parseNumberArg(args.timeoutMs, {
2824
+ defaultValue: 3e4,
2825
+ min: 1,
2826
+ max: 12e4,
2827
+ integer: true
2828
+ });
2829
+ const maxResponseBytes = parseNumberArg(args.maxResponseBytes, {
2830
+ defaultValue: 512e3,
2831
+ min: 256,
2832
+ max: 5242880,
2833
+ integer: true
2834
+ });
2835
+ const requestMethod = getRequestMethod(requestText);
2836
+ const authorization = parseNetworkAuthorization(args.authorization);
2837
+ const { target } = await resolveAuthorizedTransportTarget(`http://${formatHostForUrl(host)}:${String(port)}/`, authorization, "HTTP request");
2838
+ if (authorization) {
2839
+ const requestTarget = (requestText.split(/\r?\n/, 1)[0] ?? "").split(/\s+/)[1] ?? "";
2840
+ if (requestTarget.includes("://")) try {
2841
+ const targetHost = normalizeTargetHost(new URL(requestTarget).hostname);
2842
+ if (targetHost !== host && targetHost !== (target.resolvedAddress ?? "")) throw new Error(`HTTP request blocked: request-line target host "${targetHost}" does not match authorized host "${host}"`);
2843
+ } catch (e) {
2844
+ if (e instanceof Error && e.message.startsWith("HTTP request blocked:")) throw e;
2845
+ }
2846
+ const hostHeaderValue = requestText.match(/^Host:\s*(\S+)/im)?.[1];
2847
+ if (hostHeaderValue) {
2848
+ const declaredHost = normalizeTargetHost(hostHeaderValue.replace(/:\d+$/, ""));
2849
+ if (declaredHost !== host && declaredHost !== (target.resolvedAddress ?? "")) throw new Error(`HTTP request blocked: Host header "${hostHeaderValue}" does not match authorized host "${host}"`);
2850
+ }
2851
+ }
2852
+ const exchange = await exchangePlainHttp(target.resolvedAddress ?? target.hostname, port, Buffer.from(requestText, "utf8"), requestMethod, timeoutMs, maxResponseBytes);
2853
+ const analysis = analyzeHttpResponse(exchange.rawResponse, requestMethod);
2854
+ if (!analysis) throw new Error("Received data but could not parse complete HTTP response headers.");
2855
+ const bodyIsText = isLikelyTextHttpBody(analysis.rawHeaders.find((h) => h.name.toLowerCase() === "content-type")?.value ?? null, analysis.bodyBuffer);
2856
+ const complete = analysis.complete || analysis.bodyMode === "until-close" && exchange.endedBy === "socket-close";
2857
+ emitEvent(this.eventBus, "network:http_plain_request_completed", {
2858
+ host,
2859
+ port,
2860
+ statusCode: analysis.statusCode,
2861
+ byteLength: exchange.rawResponse.length,
2862
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2863
+ });
2864
+ return R.ok().merge({
2865
+ host,
2866
+ port,
2867
+ resolvedAddress: target.resolvedAddress ?? target.hostname,
2868
+ requestBytes: Buffer.byteLength(requestText, "utf8"),
2869
+ response: {
2870
+ statusLine: analysis.statusLine,
2871
+ httpVersion: analysis.httpVersion,
2872
+ statusCode: analysis.statusCode,
2873
+ statusText: analysis.statusText,
2874
+ headers: analysis.headers,
2875
+ rawHeaders: analysis.rawHeaders,
2876
+ headerBytes: analysis.headerBytes,
2877
+ bodyBytes: analysis.bodyBytes,
2878
+ bodyMode: analysis.bodyMode,
2879
+ chunkedDecoded: analysis.chunkedDecoded,
2880
+ complete,
2881
+ truncated: exchange.endedBy === "max-bytes",
2882
+ endedBy: exchange.endedBy,
2883
+ bodyText: bodyIsText ? analysis.bodyBuffer.toString("utf8") : void 0,
2884
+ bodyBase64: bodyIsText ? void 0 : analysis.bodyBuffer.toString("base64")
2885
+ }
2886
+ }).json();
2887
+ } catch (error) {
2888
+ return R.fail(error instanceof Error ? error.message : String(error)).json();
2889
+ }
2890
+ }
2891
+ async handleHttp2Probe(args) {
2892
+ const rawUrl = parseOptionalString(args.url, "url");
2893
+ let eventUrl = rawUrl ?? "";
2894
+ let eventStatusCode = null;
2895
+ let eventAlpnProtocol = null;
2896
+ let eventSuccess = false;
2897
+ try {
2898
+ if (!rawUrl) throw new Error("url is required");
2899
+ const method = (parseOptionalString(args.method, "method") ?? "GET").toUpperCase();
2900
+ if (!HTTP_TOKEN_RE.test(method)) throw new Error("method must be a valid HTTP token");
2901
+ const timeoutMs = parseNumberArg(args.timeoutMs, {
2902
+ defaultValue: 3e4,
2903
+ min: 1,
2904
+ max: 12e4,
2905
+ integer: true
2906
+ });
2907
+ const maxBodyBytes = parseNumberArg(args.maxBodyBytes, {
2908
+ defaultValue: 32768,
2909
+ min: 128,
2910
+ max: 1048576,
2911
+ integer: true
2912
+ });
2913
+ const bodyBuffer = Buffer.from(parseRawString(args.body, "body", { allowEmpty: true }) ?? "", "utf8");
2914
+ const alpnProtocols = parseStringArray(args.alpnProtocols, "alpnProtocols");
2915
+ const requestHeaders = toHttp2RequestHeaders(parseHeaderRecord(args.headers, "headers"));
2916
+ const { url, target } = await resolveAuthorizedTransportTarget(rawUrl, parseNetworkAuthorization(args.authorization), "HTTP/2 probe");
2917
+ eventUrl = url.toString();
2918
+ if (!("content-length" in requestHeaders) && bodyBuffer.length > 0) requestHeaders["content-length"] = String(bodyBuffer.length);
2919
+ const { responseHeaders, bodyBuffer: capturedBody, truncated, alpnProtocol } = await performHttp2ProbeInternal({
2920
+ url,
2921
+ target,
2922
+ method,
2923
+ requestHeaders,
2924
+ bodyBuffer,
2925
+ timeoutMs,
2926
+ maxBodyBytes,
2927
+ effectivePort: Number.parseInt(url.port || (url.protocol === "https:" ? "443" : "80"), 10),
2928
+ requestedAlpnProtocols: alpnProtocols.length > 0 ? alpnProtocols : ["h2", "http/1.1"]
2929
+ });
2930
+ const normalizedHeaders = normalizeHttp2Headers(responseHeaders);
2931
+ const rawStatus = responseHeaders[":status"];
2932
+ const statusCode = typeof rawStatus === "number" ? rawStatus : typeof rawStatus === "string" ? Number.parseInt(rawStatus, 10) : null;
2933
+ const bodyIsText = isLikelyTextHttpBody(typeof normalizedHeaders["content-type"] === "string" ? normalizedHeaders["content-type"] : Array.isArray(normalizedHeaders["content-type"]) ? normalizedHeaders["content-type"][0] ?? null : null, capturedBody);
2934
+ eventStatusCode = Number.isFinite(statusCode ?? NaN) ? statusCode : null;
2935
+ eventAlpnProtocol = alpnProtocol;
2936
+ eventSuccess = true;
2937
+ return R.ok().merge({
2938
+ url: eventUrl,
2939
+ statusCode: eventStatusCode,
2940
+ alpnProtocol: eventAlpnProtocol,
2941
+ headers: normalizedHeaders,
2942
+ bodyBytes: capturedBody.length,
2943
+ truncated,
2944
+ bodyText: bodyIsText ? capturedBody.toString("utf8") : void 0,
2945
+ bodyBase64: bodyIsText ? void 0 : capturedBody.toString("base64")
2946
+ }).json();
2947
+ } catch (error) {
2948
+ return R.fail(error instanceof Error ? error.message : String(error)).json();
2949
+ } finally {
2950
+ emitEvent(this.eventBus, "network:http2_probed", {
2951
+ url: eventUrl,
2952
+ success: eventSuccess,
2953
+ statusCode: eventStatusCode,
2954
+ alpnProtocol: eventAlpnProtocol,
2955
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
2956
+ });
2957
+ }
2958
+ }
2959
+ async handleHttp2FrameBuild(args) {
2960
+ const frameTypeRaw = parseOptionalString(args.frameType, "frameType");
2961
+ if (!frameTypeRaw) throw new Error("frameType is required");
2962
+ const validFrameTypes = [
2963
+ "DATA",
2964
+ "SETTINGS",
2965
+ "PING",
2966
+ "WINDOW_UPDATE",
2967
+ "RST_STREAM",
2968
+ "GOAWAY",
2969
+ "RAW"
2970
+ ];
2971
+ const frameType = frameTypeRaw.toUpperCase();
2972
+ if (!validFrameTypes.includes(frameType)) throw new Error(`frameType must be one of: ${validFrameTypes.join(", ")}`);
2973
+ const streamId = args.streamId !== void 0 ? parseNumberArg(args.streamId, {
2974
+ defaultValue: 0,
2975
+ min: 0,
2976
+ integer: true
2977
+ }) : void 0;
2978
+ const flags = args.flags !== void 0 ? parseNumberArg(args.flags, {
2979
+ defaultValue: 0,
2980
+ min: 0,
2981
+ max: 255,
2982
+ integer: true
2983
+ }) : void 0;
2984
+ const frameTypeCode = args.frameTypeCode !== void 0 ? parseNumberArg(args.frameTypeCode, {
2985
+ defaultValue: 0,
2986
+ min: 0,
2987
+ max: 255,
2988
+ integer: true
2989
+ }) : void 0;
2990
+ const windowSizeIncrement = args.windowSizeIncrement !== void 0 ? parseNumberArg(args.windowSizeIncrement, {
2991
+ defaultValue: 1,
2992
+ min: 1,
2993
+ integer: true
2994
+ }) : void 0;
2995
+ const errorCode = args.errorCode !== void 0 ? parseNumberArg(args.errorCode, {
2996
+ defaultValue: 0,
2997
+ min: 0,
2998
+ integer: true
2999
+ }) : void 0;
3000
+ const lastStreamId = args.lastStreamId !== void 0 ? parseNumberArg(args.lastStreamId, {
3001
+ defaultValue: 0,
3002
+ min: 0,
3003
+ integer: true
3004
+ }) : void 0;
3005
+ const payloadHex = parseOptionalString(args.payloadHex, "payloadHex");
3006
+ const payloadText = parseRawString(args.payloadText, "payloadText", { allowEmpty: true });
3007
+ const payloadEncoding = parseOptionalString(args.payloadEncoding, "payloadEncoding");
3008
+ const ack = parseOptionalBoolean(args.ack, "ack");
3009
+ const pingOpaqueDataHex = parseOptionalString(args.pingOpaqueDataHex, "pingOpaqueDataHex");
3010
+ const debugDataText = parseRawString(args.debugDataText, "debugDataText", { allowEmpty: true });
3011
+ const debugDataEncoding = parseOptionalString(args.debugDataEncoding, "debugDataEncoding");
3012
+ let settings;
3013
+ if (args.settings !== void 0) {
3014
+ if (!Array.isArray(args.settings)) throw new Error("settings must be an array");
3015
+ settings = args.settings.map((entry, index) => {
3016
+ if (typeof entry !== "object" || entry === null || Array.isArray(entry)) throw new Error(`settings[${String(index)}] must be an object with id and value`);
3017
+ return {
3018
+ id: typeof entry.id === "number" ? entry.id : (() => {
3019
+ throw new Error(`settings[${String(index)}].id must be a number`);
3020
+ })(),
3021
+ value: typeof entry.value === "number" ? entry.value : (() => {
3022
+ throw new Error(`settings[${String(index)}].value must be a number`);
3023
+ })()
3024
+ };
3025
+ });
3026
+ }
3027
+ const result = buildHttp2Frame({
3028
+ frameType,
3029
+ ...streamId !== void 0 && { streamId },
3030
+ ...flags !== void 0 && { flags },
3031
+ ...frameTypeCode !== void 0 && { frameTypeCode },
3032
+ ...payloadHex !== void 0 && { payloadHex },
3033
+ ...payloadText !== void 0 && { payloadText },
3034
+ ...payloadEncoding !== void 0 && { payloadEncoding },
3035
+ ...settings !== void 0 && { settings },
3036
+ ...ack !== void 0 && { ack },
3037
+ ...pingOpaqueDataHex !== void 0 && { pingOpaqueDataHex },
3038
+ ...windowSizeIncrement !== void 0 && { windowSizeIncrement },
3039
+ ...errorCode !== void 0 && { errorCode },
3040
+ ...lastStreamId !== void 0 && { lastStreamId },
3041
+ ...debugDataText !== void 0 && { debugDataText },
3042
+ ...debugDataEncoding !== void 0 && { debugDataEncoding }
3043
+ });
3044
+ emitEvent(this.eventBus, "network:http2_frame_build_completed", {
3045
+ frameType: result.frameType,
3046
+ typeCode: result.typeCode,
3047
+ streamId: result.streamId,
3048
+ flags: result.flags,
3049
+ payloadBytes: result.payloadBytes,
3050
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3051
+ });
3052
+ return R.ok().merge(result).json();
3053
+ }
3054
+ async handleNetworkRttMeasure(args) {
3055
+ const urlRaw = parseOptionalString(args.url, "url");
3056
+ if (!urlRaw) throw new Error("url is required");
3057
+ const probeType = parseOptionalString(args.probeType, "probeType") ?? "tcp";
3058
+ if (![
3059
+ "tcp",
3060
+ "tls",
3061
+ "http"
3062
+ ].includes(probeType)) throw new Error("probeType must be one of: tcp, tls, http");
3063
+ const iterations = clamp(args.iterations !== void 0 ? parseNumberArg(args.iterations, {
3064
+ defaultValue: 5,
3065
+ min: 1,
3066
+ integer: true
3067
+ }) : 5, 1, 50);
3068
+ const timeoutMs = clamp(args.timeoutMs !== void 0 ? parseNumberArg(args.timeoutMs, {
3069
+ defaultValue: 5e3,
3070
+ min: 100,
3071
+ integer: true
3072
+ }) : 5e3, 100, 3e4);
3073
+ const { url, target } = await resolveAuthorizedTransportTarget(urlRaw, parseNetworkAuthorization(args.authorization), "RTT measurement");
3074
+ const hostname = target.hostname;
3075
+ const port = Number(url.port) || (url.protocol === "https:" ? 443 : 80);
3076
+ const resolvedIp = target.resolvedAddress ?? hostname;
3077
+ const useHttps = url.protocol === "https:";
3078
+ const samples = [];
3079
+ const errors = [];
3080
+ for (let i = 0; i < iterations; i++) try {
3081
+ const rtt = await this.measureSingleRtt(hostname, resolvedIp, port, probeType, timeoutMs, useHttps);
3082
+ samples.push(rtt);
3083
+ } catch (err) {
3084
+ errors.push(err instanceof Error ? err.message : String(err));
3085
+ }
3086
+ const stats = computeRttStats(samples);
3087
+ emitEvent(this.eventBus, "network:rtt_measured", {
3088
+ url: urlRaw,
3089
+ probeType,
3090
+ iterations,
3091
+ successCount: samples.length,
3092
+ errorCount: errors.length,
3093
+ stats,
3094
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3095
+ });
3096
+ return R.ok().merge({
3097
+ target: {
3098
+ hostname,
3099
+ port,
3100
+ resolvedIp,
3101
+ probeType
3102
+ },
3103
+ stats,
3104
+ samples,
3105
+ errors: errors.length > 0 ? errors : void 0,
3106
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
3107
+ }).json();
3108
+ }
3109
+ measureSingleRtt(hostname, address, port, probeType, timeoutMs, useHttps) {
3110
+ switch (probeType) {
3111
+ case "tcp": return this.probeTcp(address, port, timeoutMs);
3112
+ case "tls": return this.probeTls(hostname, address, port, timeoutMs);
3113
+ case "http": return this.probeHttp(hostname, address, port, timeoutMs, useHttps);
3114
+ }
3115
+ }
3116
+ createPinnedLookup(address) {
3117
+ const family = net.isIP(address) === 6 ? 6 : 4;
3118
+ return (_hostname, optionsOrCallback, maybeCallback) => {
3119
+ (typeof optionsOrCallback === "function" ? optionsOrCallback : maybeCallback)?.(null, address, family);
3120
+ };
3121
+ }
3122
+ probeTcp(host, port, timeoutMs) {
3123
+ return new Promise((resolve, reject) => {
3124
+ const start = performance.now();
3125
+ const timer = setTimeout(() => reject(/* @__PURE__ */ new Error(`TCP probe timed out after ${timeoutMs}ms`)), timeoutMs);
3126
+ const socket = net.createConnection({
3127
+ host,
3128
+ port
3129
+ }, () => {
3130
+ clearTimeout(timer);
3131
+ socket.destroy();
3132
+ resolve(roundMs(performance.now() - start));
3133
+ });
3134
+ socket.on("error", (err) => {
3135
+ clearTimeout(timer);
3136
+ socket.destroy();
3137
+ reject(err);
3138
+ });
3139
+ });
3140
+ }
3141
+ probeTls(hostname, address, port, timeoutMs) {
3142
+ return new Promise((resolve, reject) => {
3143
+ const start = performance.now();
3144
+ let settled = false;
3145
+ let socket = null;
3146
+ const finish = (callback) => {
3147
+ if (settled) return;
3148
+ settled = true;
3149
+ clearTimeout(timer);
3150
+ socket?.destroy();
3151
+ callback();
3152
+ };
3153
+ const timer = setTimeout(() => {
3154
+ finish(() => reject(/* @__PURE__ */ new Error(`TLS probe timed out after ${timeoutMs}ms`)));
3155
+ }, timeoutMs);
3156
+ socket = tls.connect({
3157
+ host: hostname,
3158
+ port,
3159
+ lookup: this.createPinnedLookup(address),
3160
+ ...net.isIP(hostname) === 0 ? { servername: hostname } : {}
3161
+ }, () => {
3162
+ finish(() => resolve(roundMs(performance.now() - start)));
3163
+ });
3164
+ socket.on("error", (err) => {
3165
+ finish(() => reject(err));
3166
+ });
3167
+ });
3168
+ }
3169
+ probeHttp(hostname, address, port, timeoutMs, useHttps) {
3170
+ return new Promise((resolve, reject) => {
3171
+ const start = performance.now();
3172
+ let settled = false;
3173
+ let request = null;
3174
+ const finish = (callback) => {
3175
+ if (settled) return;
3176
+ settled = true;
3177
+ clearTimeout(timer);
3178
+ request?.destroy();
3179
+ callback();
3180
+ };
3181
+ const timer = setTimeout(() => {
3182
+ finish(() => reject(/* @__PURE__ */ new Error(`HTTP probe timed out after ${timeoutMs}ms`)));
3183
+ }, timeoutMs);
3184
+ request = (useHttps ? https.request : http$1.request)({
3185
+ host: hostname,
3186
+ port,
3187
+ path: "/",
3188
+ method: "HEAD",
3189
+ lookup: this.createPinnedLookup(address),
3190
+ ...useHttps && net.isIP(hostname) === 0 ? { servername: hostname } : {}
3191
+ }, (response) => {
3192
+ response.resume();
3193
+ finish(() => resolve(roundMs(performance.now() - start)));
3194
+ });
3195
+ request.on("error", (error) => {
3196
+ finish(() => reject(error));
3197
+ });
3198
+ request.end();
3199
+ });
3200
+ }
3201
+ async handleNetworkTraceroute(args) {
3202
+ try {
3203
+ if (!isIcmpAvailable()) return R.text("ICMP traceroute not available on this platform (Windows: native API, Linux/macOS: requires root/CAP_NET_RAW)", true);
3204
+ const target = parseOptionalString(args.target, "target");
3205
+ if (!target) return R.text("target is required", true);
3206
+ const result = traceroute({
3207
+ target,
3208
+ maxHops: clamp(args.maxHops !== void 0 ? Number(args.maxHops) : 30, 1, 64),
3209
+ timeout: clamp(args.timeout !== void 0 ? Number(args.timeout) : 5e3, 100, 3e4),
3210
+ packetSize: clamp(args.packetSize !== void 0 ? Number(args.packetSize) : 32, 8, 65500)
3211
+ });
3212
+ return R.ok().json(result);
3213
+ } catch (err) {
3214
+ const message = err instanceof Error ? err.message : String(err);
3215
+ return R.fail(`Traceroute failed: ${message}`).json();
3216
+ }
3217
+ }
3218
+ async handleNetworkIcmpProbe(args) {
3219
+ try {
3220
+ if (!isIcmpAvailable()) return R.text("ICMP probe not available on this platform (Windows: native API, Linux/macOS: requires root/CAP_NET_RAW)", true);
3221
+ const target = parseOptionalString(args.target, "target");
3222
+ if (!target) return R.text("target is required", true);
3223
+ const ttl = clamp(args.ttl !== void 0 ? Number(args.ttl) : 128, 1, 255);
3224
+ const timeout = clamp(args.timeout !== void 0 ? Number(args.timeout) : 5e3, 100, 3e4);
3225
+ const result = icmpProbe({
3226
+ target,
3227
+ ttl,
3228
+ packetSize: clamp(args.packetSize !== void 0 ? Number(args.packetSize) : 32, 8, 65500),
3229
+ timeout
3230
+ });
3231
+ return R.ok().json(result);
3232
+ } catch (err) {
3233
+ const message = err instanceof Error ? err.message : String(err);
3234
+ return R.fail(`ICMP probe failed: ${message}`).json();
3235
+ }
3236
+ }
3237
+ };
3238
+ //#endregion
3239
+ //#region src/server/domains/network/handlers.impl.ts
3240
+ var AdvancedToolHandlers = class {
3241
+ collector;
3242
+ consoleMonitor;
3243
+ eventBus;
3244
+ performanceMonitor = null;
3245
+ detailedDataManager = DetailedDataManager.getInstance();
3246
+ core;
3247
+ perf;
3248
+ console_;
3249
+ replay;
3250
+ intercept;
3251
+ raw;
3252
+ constructor(collector, consoleMonitor, eventBus) {
3253
+ this.collector = collector;
3254
+ this.consoleMonitor = consoleMonitor;
3255
+ this.eventBus = eventBus;
3256
+ this.core = new CoreHandlers({
3257
+ collector,
3258
+ consoleMonitor,
3259
+ eventBus
3260
+ });
3261
+ this.perf = new PerformanceHandlers({
3262
+ collector,
3263
+ getPerformanceMonitor: () => this.getPerformanceMonitor()
3264
+ });
3265
+ this.console_ = new ConsoleHandlers({ consoleMonitor });
3266
+ this.replay = new ReplayHandlers({ consoleMonitor });
3267
+ this.intercept = new InterceptHandlers({
3268
+ consoleMonitor,
3269
+ eventBus
3270
+ });
3271
+ this.raw = new RawHandlers(eventBus);
3272
+ }
3273
+ getPerformanceMonitor() {
3274
+ if (!this.performanceMonitor) this.performanceMonitor = new PerformanceMonitor(this.collector);
3275
+ return this.performanceMonitor;
3276
+ }
3277
+ handleNetworkEnable = (args) => this.core.handleNetworkEnable(args);
3278
+ handleNetworkDisable = (args) => this.core.handleNetworkDisable(args);
3279
+ handleNetworkGetStatus = (args) => this.core.handleNetworkGetStatus(args);
3280
+ handleNetworkMonitor = (args) => this.core.handleNetworkMonitor(args);
3281
+ handleNetworkGetRequests = (args) => this.core.handleNetworkGetRequests(args);
3282
+ handleNetworkGetResponseBody = (args) => this.core.handleNetworkGetResponseBody(args);
3283
+ handleNetworkGetStats = (args) => this.core.handleNetworkGetStats(args);
3284
+ handlePerformanceGetMetrics = (args) => this.perf.handlePerformanceGetMetrics(args);
3285
+ handlePerformanceCoverage = (args) => this.perf.handlePerformanceCoverage(args);
3286
+ handlePerformanceStartCoverage = (args) => this.perf.handlePerformanceStartCoverage(args);
3287
+ handlePerformanceStopCoverage = (args) => this.perf.handlePerformanceStopCoverage(args);
3288
+ handlePerformanceTakeHeapSnapshot = (args) => this.perf.handlePerformanceTakeHeapSnapshot(args);
3289
+ handlePerformanceTraceStart = (args) => this.perf.handlePerformanceTraceStart(args);
3290
+ handlePerformanceTraceStop = (args) => this.perf.handlePerformanceTraceStop(args);
3291
+ handlePerformanceTraceDispatch = (args) => String(args["action"] ?? "") === "stop" ? this.perf.handlePerformanceTraceStop(args) : this.perf.handlePerformanceTraceStart(args);
3292
+ handleProfilerCpuStart = (args) => this.perf.handleProfilerCpuStart(args);
3293
+ handleProfilerCpuStop = (args) => this.perf.handleProfilerCpuStop(args);
3294
+ handleProfilerCpuDispatch = (args) => String(args["action"] ?? "") === "stop" ? this.perf.handleProfilerCpuStop(args) : this.perf.handleProfilerCpuStart(args);
3295
+ handleProfilerHeapSamplingStart = (args) => this.perf.handleProfilerHeapSamplingStart(args);
3296
+ handleProfilerHeapSamplingStop = (args) => this.perf.handleProfilerHeapSamplingStop(args);
3297
+ handleProfilerHeapSamplingDispatch = (args) => String(args["action"] ?? "") === "stop" ? this.perf.handleProfilerHeapSamplingStop(args) : this.perf.handleProfilerHeapSamplingStart(args);
3298
+ handleConsoleGetExceptions = (args) => this.console_.handleConsoleGetExceptions(args);
3299
+ handleConsoleInjectDispatch = (args) => {
3300
+ switch (String(args["type"] ?? "")) {
3301
+ case "xhr": return this.console_.handleConsoleInjectXhrInterceptor(args);
3302
+ case "fetch": return this.console_.handleConsoleInjectFetchInterceptor(args);
3303
+ case "function": return this.console_.handleConsoleInjectFunctionTracer(args);
3304
+ default: return this.console_.handleConsoleInjectScriptMonitor(args);
3305
+ }
3306
+ };
3307
+ handleConsoleBuffersDispatch = (args) => {
3308
+ return String(args["action"] ?? "") === "reset" ? this.console_.handleConsoleResetInjectedInterceptors(args) : this.console_.handleConsoleClearInjectedBuffers(args);
3309
+ };
3310
+ handleConsoleInjectScriptMonitor = (args) => this.console_.handleConsoleInjectScriptMonitor(args);
3311
+ handleConsoleInjectXhrInterceptor = (args) => this.console_.handleConsoleInjectXhrInterceptor(args);
3312
+ handleConsoleInjectFetchInterceptor = (args) => this.console_.handleConsoleInjectFetchInterceptor(args);
3313
+ handleConsoleClearInjectedBuffers = (args) => this.console_.handleConsoleClearInjectedBuffers(args);
3314
+ handleConsoleResetInjectedInterceptors = (args) => this.console_.handleConsoleResetInjectedInterceptors(args);
3315
+ handleConsoleInjectFunctionTracer = (args) => this.console_.handleConsoleInjectFunctionTracer(args);
3316
+ handleNetworkExtractAuth = (args) => this.replay.handleNetworkExtractAuth(args);
3317
+ handleNetworkExportHar = (args) => this.replay.handleNetworkExportHar(args);
3318
+ handleNetworkReplayRequest = (args) => this.replay.handleNetworkReplayRequest(args);
3319
+ handleNetworkInterceptResponse = (args) => this.intercept.handleNetworkInterceptResponse(args);
3320
+ handleNetworkInterceptList = (args) => this.intercept.handleNetworkInterceptList(args);
3321
+ handleNetworkInterceptDisable = (args) => this.intercept.handleNetworkInterceptDisable(args);
3322
+ handleNetworkInterceptDispatch = (args) => {
3323
+ const action = String(args["action"] ?? "");
3324
+ switch (action) {
3325
+ case "add": return this.intercept.handleNetworkInterceptResponse(args);
3326
+ case "list": return this.intercept.handleNetworkInterceptList(args);
3327
+ case "disable": return this.intercept.handleNetworkInterceptDisable(args);
3328
+ default: return Promise.resolve({
3329
+ content: [{
3330
+ type: "text",
3331
+ text: `Invalid action: "${action}". Expected one of: add, list, disable`
3332
+ }],
3333
+ isError: true
3334
+ });
3335
+ }
3336
+ };
3337
+ handleNetworkTraceroute = (args) => this.raw.handleNetworkTraceroute(args);
3338
+ handleNetworkIcmpProbe = (args) => this.raw.handleNetworkIcmpProbe(args);
3339
+ handleHttpRequestBuild = (args) => this.raw.handleHttpRequestBuild(args);
3340
+ handleHttpPlainRequest = (args) => this.raw.handleHttpPlainRequest(args);
3341
+ handleHttp2Probe = (args) => this.raw.handleHttp2Probe(args);
3342
+ handleHttp2FrameBuild = (args) => this.raw.handleHttp2FrameBuild(args);
3343
+ handleNetworkRttMeasure = (args) => this.raw.handleNetworkRttMeasure(args);
3344
+ };
3345
+ //#endregion
3346
+ export { AdvancedToolHandlers };