@jshookmcp/jshook 0.2.8 → 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-D5-bO9D8.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 +377 -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 +16 -16
  151. package/dist/ExtensionManager-CPTJhHFg.mjs +0 -2
  152. package/dist/ToolCatalog-Bq4V2sbJ.mjs +0 -67201
  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,3000 @@
1
+ import { t as logger } from "./logger-Dh_xb7_2.mjs";
2
+ import { B as DOM_WAIT_ELEMENT_TIMEOUT_MS, z as DOM_QUERY_DEFAULT_LIMIT } from "./constants-B0OANIBL.mjs";
3
+ import { t as getCodeCacheDir } from "./outputPaths-B1uGmrWZ.mjs";
4
+ import { t as PrerequisiteError } from "./PrerequisiteError-Dl33Svkz.mjs";
5
+ import { createRequire } from "node:module";
6
+ import { existsSync } from "fs";
7
+ import path, { join } from "path";
8
+ import crypto, { createHash } from "crypto";
9
+ import { dirname, join as join$1 } from "node:path";
10
+ import { promisify } from "util";
11
+ import { connect, executablePath, launch } from "rebrowser-puppeteer-core";
12
+ import fs, { readFile } from "fs/promises";
13
+ import { gunzip, gzip } from "zlib";
14
+ import { EventEmitter } from "node:events";
15
+ import { homedir } from "os";
16
+ import { setImmediate } from "node:timers/promises";
17
+ //#region src/modules/collector/CodeCache.ts
18
+ var CodeCache = class CodeCache {
19
+ cacheDir;
20
+ maxAge;
21
+ maxSize;
22
+ memoryCache = /* @__PURE__ */ new Map();
23
+ MAX_MEMORY_CACHE_SIZE = 100;
24
+ writesSinceCleanup = 0;
25
+ static CLEANUP_INTERVAL = 20;
26
+ constructor(options = {}) {
27
+ this.cacheDir = options.cacheDir || getCodeCacheDir();
28
+ this.maxAge = options.maxAge || 1440 * 60 * 1e3;
29
+ this.maxSize = options.maxSize || 100 * 1024 * 1024;
30
+ }
31
+ async init() {
32
+ try {
33
+ await fs.mkdir(this.cacheDir, { recursive: true });
34
+ logger.debug(`Cache directory initialized: ${this.cacheDir}`);
35
+ } catch (error) {
36
+ logger.error("Failed to initialize cache directory:", error);
37
+ }
38
+ }
39
+ generateKey(url, options) {
40
+ const data = JSON.stringify({
41
+ url,
42
+ options
43
+ });
44
+ return crypto.createHash("md5").update(data).digest("hex");
45
+ }
46
+ getCachePath(key) {
47
+ return path.join(this.cacheDir, `${key}.json`);
48
+ }
49
+ getDependenciesOrEmpty(dependencies) {
50
+ return dependencies ?? {
51
+ nodes: [],
52
+ edges: []
53
+ };
54
+ }
55
+ isExpired(entry) {
56
+ return Date.now() - entry.timestamp > this.maxAge;
57
+ }
58
+ async get(url, options) {
59
+ const key = this.generateKey(url, options);
60
+ if (this.memoryCache.has(key)) {
61
+ const entry = this.memoryCache.get(key);
62
+ if (!this.isExpired(entry)) {
63
+ logger.debug(`Cache hit (memory): ${url}`);
64
+ return {
65
+ files: entry.files,
66
+ dependencies: this.getDependenciesOrEmpty(entry.dependencies),
67
+ totalSize: entry.totalSize,
68
+ collectTime: entry.collectTime,
69
+ summaries: entry.summaries
70
+ };
71
+ } else this.memoryCache.delete(key);
72
+ }
73
+ try {
74
+ const cachePath = this.getCachePath(key);
75
+ const data = await fs.readFile(cachePath, "utf-8");
76
+ const entry = JSON.parse(data);
77
+ if (this.isExpired(entry)) {
78
+ logger.debug(`Cache expired: ${url}`);
79
+ await fs.unlink(cachePath);
80
+ return null;
81
+ }
82
+ this.memoryCache.set(key, entry);
83
+ logger.debug(`Cache hit (disk): ${url}`);
84
+ return {
85
+ files: entry.files,
86
+ dependencies: this.getDependenciesOrEmpty(entry.dependencies),
87
+ totalSize: entry.totalSize,
88
+ collectTime: entry.collectTime,
89
+ summaries: entry.summaries
90
+ };
91
+ } catch (err) {
92
+ logger.warn(`Cache read failed for ${url}: ${err instanceof Error ? err.message : String(err)}`);
93
+ return null;
94
+ }
95
+ }
96
+ async set(url, result, options) {
97
+ const key = this.generateKey(url, options);
98
+ const hash = crypto.createHash("md5").update(JSON.stringify(result.files)).digest("hex");
99
+ const entry = {
100
+ url,
101
+ files: result.files,
102
+ dependencies: result.dependencies,
103
+ totalSize: result.totalSize,
104
+ collectTime: result.collectTime,
105
+ summaries: result.summaries,
106
+ timestamp: Date.now(),
107
+ hash
108
+ };
109
+ this.memoryCache.set(key, entry);
110
+ if (this.memoryCache.size > this.MAX_MEMORY_CACHE_SIZE) {
111
+ const firstKey = this.memoryCache.keys().next().value;
112
+ if (firstKey) {
113
+ this.memoryCache.delete(firstKey);
114
+ logger.debug(`Memory cache evicted: ${firstKey}`);
115
+ }
116
+ }
117
+ try {
118
+ const cachePath = this.getCachePath(key);
119
+ await fs.mkdir(this.cacheDir, { recursive: true });
120
+ await fs.writeFile(cachePath, JSON.stringify(entry, null, 2), "utf-8");
121
+ logger.debug(`Cache saved: ${url} (${(result.totalSize / 1024).toFixed(2)} KB)`);
122
+ } catch (error) {
123
+ logger.error("Failed to save cache:", error);
124
+ }
125
+ if (++this.writesSinceCleanup >= CodeCache.CLEANUP_INTERVAL) {
126
+ this.writesSinceCleanup = 0;
127
+ this.cleanup();
128
+ }
129
+ }
130
+ async cleanup() {
131
+ try {
132
+ const files = await fs.readdir(this.cacheDir);
133
+ let totalSize = 0;
134
+ const entries = [];
135
+ for (const file of files) {
136
+ if (!file.endsWith(".json")) continue;
137
+ const filePath = path.join(this.cacheDir, file);
138
+ const stats = await fs.stat(filePath);
139
+ totalSize += stats.size;
140
+ entries.push({
141
+ file: filePath,
142
+ mtime: stats.mtime,
143
+ size: stats.size
144
+ });
145
+ }
146
+ if (totalSize > this.maxSize) {
147
+ entries.sort((a, b) => a.mtime.getTime() - b.mtime.getTime());
148
+ let removedSize = 0;
149
+ for (const entry of entries) {
150
+ if (totalSize - removedSize <= this.maxSize * .8) break;
151
+ await fs.unlink(entry.file);
152
+ removedSize += entry.size;
153
+ logger.debug(`Removed old cache: ${entry.file}`);
154
+ }
155
+ logger.info(`Cache cleanup: removed ${removedSize} bytes`);
156
+ }
157
+ } catch (error) {
158
+ logger.error("Failed to cleanup cache:", error);
159
+ }
160
+ }
161
+ async clear() {
162
+ try {
163
+ this.memoryCache.clear();
164
+ const files = await fs.readdir(this.cacheDir);
165
+ for (const file of files) if (file.endsWith(".json")) await fs.unlink(path.join(this.cacheDir, file));
166
+ logger.info("All cache cleared");
167
+ } catch (error) {
168
+ if (error.code === "ENOENT") return;
169
+ logger.error("Failed to clear cache:", error);
170
+ }
171
+ }
172
+ async getStats() {
173
+ try {
174
+ const files = await fs.readdir(this.cacheDir);
175
+ let totalSize = 0;
176
+ let diskEntries = 0;
177
+ for (const file of files) {
178
+ if (!file.endsWith(".json")) continue;
179
+ const filePath = path.join(this.cacheDir, file);
180
+ const stats = await fs.stat(filePath);
181
+ totalSize += stats.size;
182
+ diskEntries++;
183
+ }
184
+ return {
185
+ memoryEntries: this.memoryCache.size,
186
+ diskEntries,
187
+ totalSize
188
+ };
189
+ } catch (error) {
190
+ logger.error("Failed to get cache stats:", error);
191
+ return {
192
+ memoryEntries: this.memoryCache.size,
193
+ diskEntries: 0,
194
+ totalSize: 0
195
+ };
196
+ }
197
+ }
198
+ async warmup(urls) {
199
+ logger.info(`Warming up cache for ${urls.length} URLs...`);
200
+ for (const url of urls) await this.get(url);
201
+ logger.info("Cache warmup completed");
202
+ }
203
+ };
204
+ //#endregion
205
+ //#region src/modules/collector/SmartCodeCollector.ts
206
+ var SmartCodeCollector = class {
207
+ DEFAULT_MAX_TOTAL_SIZE = 512 * 1024;
208
+ DEFAULT_MAX_FILE_SIZE = 100 * 1024;
209
+ PREVIEW_LINES = 50;
210
+ async smartCollect(_page, files, options) {
211
+ logger.info(`Smart code collection mode: ${options.mode}`);
212
+ switch (options.mode) {
213
+ case "summary": return this.collectSummaries(files);
214
+ case "priority": return this.collectByPriority(files, options);
215
+ case "incremental": return this.collectIncremental(files, options);
216
+ default: return this.collectWithLimit(files, options);
217
+ }
218
+ }
219
+ async collectSummaries(files) {
220
+ logger.info("Generating code summaries...");
221
+ return files.map((file) => {
222
+ const preview = file.content.split("\n").slice(0, this.PREVIEW_LINES).join("\n");
223
+ return {
224
+ url: file.url,
225
+ size: file.size,
226
+ type: file.type,
227
+ hasEncryption: this.detectEncryption(file.content),
228
+ hasAPI: this.detectAPI(file.content),
229
+ hasObfuscation: this.detectObfuscation(file.content),
230
+ functions: this.extractFunctions(file.content),
231
+ imports: this.extractImports(file.content),
232
+ preview
233
+ };
234
+ });
235
+ }
236
+ collectByPriority(files, options) {
237
+ const maxTotalSize = options.maxTotalSize || this.DEFAULT_MAX_TOTAL_SIZE;
238
+ const maxFileSize = options.maxFileSize || this.DEFAULT_MAX_FILE_SIZE;
239
+ const scoredFiles = files.map((file) => ({
240
+ file,
241
+ score: this.calculatePriority(file, options.priorities || [])
242
+ }));
243
+ scoredFiles.sort((a, b) => b.score - a.score);
244
+ const result = [];
245
+ let currentSize = 0;
246
+ for (const { file } of scoredFiles) {
247
+ let content = file.content;
248
+ let truncated = false;
249
+ if (file.size > maxFileSize) {
250
+ content = content.substring(0, maxFileSize);
251
+ truncated = true;
252
+ }
253
+ if (currentSize + content.length > maxTotalSize) {
254
+ logger.warn(`Reached max total size limit (${maxTotalSize} bytes), stopping collection`);
255
+ break;
256
+ }
257
+ result.push({
258
+ ...file,
259
+ content,
260
+ size: content.length,
261
+ metadata: {
262
+ ...file.metadata,
263
+ truncated,
264
+ originalSize: file.size,
265
+ priorityScore: this.calculatePriority(file, options.priorities || [])
266
+ }
267
+ });
268
+ currentSize += content.length;
269
+ }
270
+ logger.info(`Collected ${result.length}/${files.length} files by priority (${(currentSize / 1024).toFixed(2)} KB)`);
271
+ return result;
272
+ }
273
+ collectIncremental(files, options) {
274
+ const includePatterns = options.includePatterns || [];
275
+ const excludePatterns = options.excludePatterns || [];
276
+ const filtered = files.filter((file) => {
277
+ if (excludePatterns.some((pattern) => new RegExp(pattern).test(file.url))) return false;
278
+ if (includePatterns.length === 0) return true;
279
+ return includePatterns.some((pattern) => new RegExp(pattern).test(file.url));
280
+ });
281
+ logger.info(`Incremental collection: ${filtered.length}/${files.length} files matched`);
282
+ return this.collectWithLimit(filtered, options);
283
+ }
284
+ collectWithLimit(files, options) {
285
+ const maxTotalSize = options.maxTotalSize || this.DEFAULT_MAX_TOTAL_SIZE;
286
+ const maxFileSize = options.maxFileSize || this.DEFAULT_MAX_FILE_SIZE;
287
+ const result = [];
288
+ let currentSize = 0;
289
+ for (const file of files) {
290
+ let content = file.content;
291
+ let truncated = false;
292
+ if (file.size > maxFileSize) {
293
+ content = content.substring(0, maxFileSize);
294
+ truncated = true;
295
+ }
296
+ if (currentSize + content.length > maxTotalSize) {
297
+ logger.warn(`Reached max total size limit, collected ${result.length}/${files.length} files`);
298
+ break;
299
+ }
300
+ result.push({
301
+ ...file,
302
+ content,
303
+ size: content.length,
304
+ metadata: {
305
+ ...file.metadata,
306
+ truncated,
307
+ originalSize: file.size
308
+ }
309
+ });
310
+ currentSize += content.length;
311
+ }
312
+ return result;
313
+ }
314
+ calculatePriority(file, priorities) {
315
+ let score = 0;
316
+ if (file.type === "inline") score += 10;
317
+ if (file.type === "external") score += 5;
318
+ for (let i = 0; i < priorities.length; i++) {
319
+ const pattern = priorities[i];
320
+ if (pattern && new RegExp(pattern).test(file.url)) score += (priorities.length - i) * 20;
321
+ }
322
+ if (this.detectEncryption(file.content)) score += 50;
323
+ if (this.detectAPI(file.content)) score += 30;
324
+ if (this.detectObfuscation(file.content)) score += 20;
325
+ if (file.size < 10 * 1024) score += 10;
326
+ else if (file.size > 500 * 1024) score -= 20;
327
+ return score;
328
+ }
329
+ detectEncryption(content) {
330
+ return [
331
+ /crypto|encrypt|decrypt|cipher|aes|rsa|md5|sha/i,
332
+ /CryptoJS|forge|sjcl/i,
333
+ /btoa|atob/i
334
+ ].some((pattern) => pattern.test(content));
335
+ }
336
+ detectAPI(content) {
337
+ return [
338
+ /fetch\s*\(/,
339
+ /XMLHttpRequest/,
340
+ /axios|request|ajax/i,
341
+ /\.get\(|\.post\(/
342
+ ].some((pattern) => pattern.test(content));
343
+ }
344
+ detectObfuscation(content) {
345
+ const lines = content.split("\n");
346
+ if (content.length / lines.length > 200) return true;
347
+ if (/\\x[0-9a-f]{2}/i.test(content)) return true;
348
+ if (/\\u[0-9a-f]{4}/i.test(content)) return true;
349
+ if (/eval\s*\(/i.test(content)) return true;
350
+ return false;
351
+ }
352
+ extractFunctions(content) {
353
+ const functions = [];
354
+ for (const pattern of [
355
+ /function\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g,
356
+ /const\s+([a-zA-Z_$][a-zA-Z0-9_$]*)\s*=\s*function/g,
357
+ /([a-zA-Z_$][a-zA-Z0-9_$]*)\s*:\s*function/g
358
+ ]) {
359
+ let match;
360
+ while ((match = pattern.exec(content)) !== null) if (match[1] && !functions.includes(match[1])) functions.push(match[1]);
361
+ }
362
+ return functions.slice(0, 20);
363
+ }
364
+ extractImports(content) {
365
+ const imports = [];
366
+ for (const pattern of [/import\s+.*?from\s+['"]([^'"]+)['"]/g, /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g]) {
367
+ let match;
368
+ while ((match = pattern.exec(content)) !== null) if (match[1] && !imports.includes(match[1])) imports.push(match[1]);
369
+ }
370
+ return imports;
371
+ }
372
+ };
373
+ //#endregion
374
+ //#region src/modules/collector/CodeCompressor.ts
375
+ const gzipAsync = promisify(gzip);
376
+ const gunzipAsync = promisify(gunzip);
377
+ var CodeCompressor = class {
378
+ DEFAULT_LEVEL = 6;
379
+ DEFAULT_CHUNK_SIZE = 100 * 1024;
380
+ DEFAULT_CONCURRENCY = 5;
381
+ DEFAULT_MAX_RETRIES = 3;
382
+ CACHE_MAX_SIZE = 100;
383
+ CACHE_TTL = 3600 * 1e3;
384
+ cache = /* @__PURE__ */ new Map();
385
+ stats = {
386
+ totalCompressed: 0,
387
+ totalOriginalSize: 0,
388
+ totalCompressedSize: 0,
389
+ averageRatio: 0,
390
+ cacheHits: 0,
391
+ cacheMisses: 0,
392
+ totalTime: 0
393
+ };
394
+ async compress(code, options = {}) {
395
+ const startTime = Date.now();
396
+ const level = options.level ?? this.DEFAULT_LEVEL;
397
+ const useCache = options.useCache ?? true;
398
+ const maxRetries = options.maxRetries ?? this.DEFAULT_MAX_RETRIES;
399
+ const cacheKey = this.generateCacheKey(code, level);
400
+ if (useCache && this.cache.has(cacheKey)) {
401
+ const cached = this.cache.get(cacheKey);
402
+ if (Date.now() - cached.timestamp < this.CACHE_TTL) {
403
+ this.stats.cacheHits++;
404
+ logger.debug(`Cache hit for compression (${code.length} bytes)`);
405
+ return {
406
+ compressed: cached.compressed,
407
+ originalSize: cached.originalSize,
408
+ compressedSize: cached.compressedSize,
409
+ compressionRatio: cached.compressionRatio,
410
+ level
411
+ };
412
+ } else this.cache.delete(cacheKey);
413
+ }
414
+ this.stats.cacheMisses++;
415
+ let lastError = null;
416
+ for (let attempt = 0; attempt < maxRetries; attempt++) try {
417
+ const buffer = Buffer.from(code, "utf-8");
418
+ const compressed = await gzipAsync(buffer, { level });
419
+ const base64 = compressed.toString("base64");
420
+ const originalSize = buffer.length;
421
+ const compressedSize = compressed.length;
422
+ const compressionRatio = (1 - compressedSize / originalSize) * 100;
423
+ const compressionTime = Date.now() - startTime;
424
+ this.stats.totalCompressed++;
425
+ this.stats.totalOriginalSize += originalSize;
426
+ this.stats.totalCompressedSize += compressedSize;
427
+ this.stats.averageRatio = (1 - this.stats.totalCompressedSize / this.stats.totalOriginalSize) * 100;
428
+ this.stats.totalTime += compressionTime;
429
+ const result = {
430
+ compressed: base64,
431
+ originalSize,
432
+ compressedSize,
433
+ compressionRatio,
434
+ level,
435
+ metadata: {
436
+ hash: cacheKey,
437
+ timestamp: Date.now(),
438
+ compressionTime
439
+ }
440
+ };
441
+ if (useCache) this.addToCache(cacheKey, {
442
+ compressed: base64,
443
+ originalSize,
444
+ compressedSize,
445
+ compressionRatio,
446
+ timestamp: Date.now()
447
+ });
448
+ logger.debug(`Compressed code: ${originalSize} -> ${compressedSize} bytes (${compressionRatio.toFixed(1)}% reduction, level ${level}, ${compressionTime}ms)`);
449
+ return result;
450
+ } catch (error) {
451
+ lastError = error;
452
+ logger.warn(`Compression attempt ${attempt + 1}/${maxRetries} failed:`, error);
453
+ if (attempt < maxRetries - 1) await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
454
+ }
455
+ logger.error("Failed to compress code after retries:", lastError);
456
+ throw lastError || /* @__PURE__ */ new Error("Compression failed");
457
+ }
458
+ async decompress(compressed, maxRetries = 3) {
459
+ let lastError = null;
460
+ for (let attempt = 0; attempt < maxRetries; attempt++) try {
461
+ return (await gunzipAsync(Buffer.from(compressed, "base64"))).toString("utf-8");
462
+ } catch (error) {
463
+ lastError = error;
464
+ logger.warn(`Decompression attempt ${attempt + 1}/${maxRetries} failed:`, error);
465
+ if (attempt < maxRetries - 1) await new Promise((resolve) => setTimeout(resolve, 100 * (attempt + 1)));
466
+ }
467
+ logger.error("Failed to decompress code after retries:", lastError);
468
+ throw lastError || /* @__PURE__ */ new Error("Decompression failed");
469
+ }
470
+ async compressBatch(files, options = {}) {
471
+ const concurrency = options.concurrency ?? this.DEFAULT_CONCURRENCY;
472
+ const results = [];
473
+ for (let i = 0; i < files.length; i += concurrency) {
474
+ const batch = files.slice(i, i + concurrency);
475
+ const batchResults = await Promise.all(batch.map(async (file) => {
476
+ try {
477
+ const result = await this.compress(file.content, options);
478
+ if (options.onFileProgress) options.onFileProgress(file.url, 100);
479
+ return {
480
+ url: file.url,
481
+ compressed: result.compressed,
482
+ originalSize: result.originalSize,
483
+ compressedSize: result.compressedSize,
484
+ compressionRatio: result.compressionRatio
485
+ };
486
+ } catch (error) {
487
+ logger.error(`Failed to compress ${file.url}:`, error);
488
+ return {
489
+ url: file.url,
490
+ compressed: Buffer.from(file.content).toString("base64"),
491
+ originalSize: file.content.length,
492
+ compressedSize: file.content.length,
493
+ compressionRatio: 0
494
+ };
495
+ }
496
+ }));
497
+ results.push(...batchResults);
498
+ if (options.onProgress) options.onProgress(results.length / files.length * 100);
499
+ }
500
+ const totalOriginal = results.reduce((sum, r) => sum + r.originalSize, 0);
501
+ const totalCompressed = results.reduce((sum, r) => sum + r.compressedSize, 0);
502
+ const totalRatio = totalOriginal > 0 ? (1 - totalCompressed / totalOriginal) * 100 : 0;
503
+ logger.info(`Batch compression: ${results.length} files, ${(totalOriginal / 1024).toFixed(2)} KB -> ${(totalCompressed / 1024).toFixed(2)} KB (${totalRatio.toFixed(1)}% reduction)`);
504
+ return results;
505
+ }
506
+ shouldCompress(code, threshold = 1024) {
507
+ return code.length > threshold;
508
+ }
509
+ selectCompressionLevel(size) {
510
+ if (size < 10 * 1024) return 1;
511
+ else if (size < 100 * 1024) return 6;
512
+ else if (size < 1024 * 1024) return 9;
513
+ else return 6;
514
+ }
515
+ async compressStream(code, options = {}) {
516
+ const chunkSize = options.chunkSize ?? this.DEFAULT_CHUNK_SIZE;
517
+ if (code.length <= chunkSize) return this.compress(code, options);
518
+ const startTime = Date.now();
519
+ const chunks = [];
520
+ for (let i = 0; i < code.length; i += chunkSize) {
521
+ const chunk = code.substring(i, i + chunkSize);
522
+ const compressed = await this.compress(chunk, {
523
+ ...options,
524
+ useCache: false
525
+ });
526
+ chunks.push(compressed.compressed);
527
+ if (options.onProgress) options.onProgress(i / code.length * 100);
528
+ }
529
+ const combined = JSON.stringify(chunks);
530
+ const finalCompressed = Buffer.from(combined).toString("base64");
531
+ const originalSize = code.length;
532
+ const compressedSize = finalCompressed.length;
533
+ const compressionRatio = (1 - compressedSize / originalSize) * 100;
534
+ const compressionTime = Date.now() - startTime;
535
+ logger.info(`Stream compression: ${chunks.length} chunks, ${(originalSize / 1024).toFixed(2)} KB -> ${(compressedSize / 1024).toFixed(2)} KB (${compressionRatio.toFixed(1)}% reduction, ${compressionTime}ms)`);
536
+ return {
537
+ compressed: finalCompressed,
538
+ originalSize,
539
+ compressedSize,
540
+ compressionRatio,
541
+ chunks: chunks.length,
542
+ metadata: {
543
+ hash: this.generateCacheKey(code, options.level ?? this.DEFAULT_LEVEL),
544
+ timestamp: Date.now(),
545
+ compressionTime
546
+ }
547
+ };
548
+ }
549
+ getStats() {
550
+ return { ...this.stats };
551
+ }
552
+ resetStats() {
553
+ this.stats = {
554
+ totalCompressed: 0,
555
+ totalOriginalSize: 0,
556
+ totalCompressedSize: 0,
557
+ averageRatio: 0,
558
+ cacheHits: 0,
559
+ cacheMisses: 0,
560
+ totalTime: 0
561
+ };
562
+ }
563
+ clearCache() {
564
+ this.cache.clear();
565
+ logger.info("Compression cache cleared");
566
+ }
567
+ getCacheSize() {
568
+ return this.cache.size;
569
+ }
570
+ generateCacheKey(code, level) {
571
+ return `${createHash("md5").update(code).digest("hex")}-${level}`;
572
+ }
573
+ addToCache(key, entry) {
574
+ if (this.cache.size >= this.CACHE_MAX_SIZE) {
575
+ const firstKey = this.cache.keys().next().value;
576
+ if (firstKey) this.cache.delete(firstKey);
577
+ }
578
+ this.cache.set(key, entry);
579
+ }
580
+ };
581
+ //#endregion
582
+ //#region src/modules/browser/AttachedTargetSession.ts
583
+ var AttachedTargetSession = class {
584
+ emitter = new EventEmitter();
585
+ pending = /* @__PURE__ */ new Map();
586
+ nextCommandId = 1;
587
+ detached = false;
588
+ constructor(parentSession, sessionId) {
589
+ this.parentSession = parentSession;
590
+ this.sessionId = sessionId;
591
+ this.parentSession.on("Target.receivedMessageFromTarget", this.handleReceivedMessage);
592
+ this.parentSession.on("Target.detachedFromTarget", this.handleDetachedFromTarget);
593
+ }
594
+ id() {
595
+ return this.sessionId;
596
+ }
597
+ on(event, listener) {
598
+ this.emitter.on(event, listener);
599
+ return this;
600
+ }
601
+ off(event, listener) {
602
+ this.emitter.off(event, listener);
603
+ return this;
604
+ }
605
+ async send(method, params = {}) {
606
+ if (this.detached) throw new Error(`CDP target session ${this.sessionId} is detached`);
607
+ const commandId = this.nextCommandId++;
608
+ const message = JSON.stringify({
609
+ id: commandId,
610
+ method,
611
+ params
612
+ });
613
+ return await new Promise((resolve, reject) => {
614
+ this.pending.set(commandId, {
615
+ resolve,
616
+ reject
617
+ });
618
+ this.parentSession.send("Target.sendMessageToTarget", {
619
+ sessionId: this.sessionId,
620
+ message
621
+ }).catch((error) => {
622
+ this.pending.delete(commandId);
623
+ reject(error);
624
+ });
625
+ });
626
+ }
627
+ async detach() {
628
+ if (this.detached) return;
629
+ try {
630
+ await this.parentSession.send("Target.detachFromTarget", { sessionId: this.sessionId });
631
+ } finally {
632
+ this.dispose(/* @__PURE__ */ new Error(`CDP target session ${this.sessionId} detached`));
633
+ }
634
+ }
635
+ handleReceivedMessage = (payload) => {
636
+ if (!this.matchesSession(payload)) return;
637
+ const rawMessage = this.readString(payload, "message");
638
+ if (!rawMessage) return;
639
+ let parsed;
640
+ try {
641
+ parsed = JSON.parse(rawMessage);
642
+ } catch (error) {
643
+ logger.debug(`[AttachedTargetSession] Failed to parse incoming target message for ${this.sessionId}: ${String(error)}`);
644
+ return;
645
+ }
646
+ if (!this.isRecord(parsed)) return;
647
+ const commandId = typeof parsed.id === "number" ? parsed.id : null;
648
+ if (commandId !== null) {
649
+ const pending = this.pending.get(commandId);
650
+ if (!pending) return;
651
+ this.pending.delete(commandId);
652
+ if (parsed.error) pending.reject(this.normalizeError(parsed.error));
653
+ else pending.resolve(parsed.result ?? null);
654
+ return;
655
+ }
656
+ const method = typeof parsed.method === "string" ? parsed.method : null;
657
+ if (!method) return;
658
+ this.emitter.emit(method, parsed.params ?? {});
659
+ };
660
+ handleDetachedFromTarget = (payload) => {
661
+ if (!this.matchesSession(payload)) return;
662
+ this.dispose(/* @__PURE__ */ new Error(`CDP target session ${this.sessionId} detached by browser`));
663
+ };
664
+ matchesSession(payload) {
665
+ return this.readString(payload, "sessionId") === this.sessionId;
666
+ }
667
+ dispose(reason) {
668
+ if (this.detached) return;
669
+ this.detached = true;
670
+ this.parentSession.off("Target.receivedMessageFromTarget", this.handleReceivedMessage);
671
+ this.parentSession.off("Target.detachedFromTarget", this.handleDetachedFromTarget);
672
+ for (const pending of this.pending.values()) pending.reject(reason);
673
+ this.pending.clear();
674
+ this.emitter.emit("disconnected", {
675
+ sessionId: this.sessionId,
676
+ reason: reason.message
677
+ });
678
+ this.emitter.removeAllListeners();
679
+ }
680
+ normalizeError(error) {
681
+ if (error instanceof Error) return error;
682
+ if (this.isRecord(error)) {
683
+ const message = typeof error.message === "string" && error.message || typeof error.data === "string" && error.data || JSON.stringify(error);
684
+ return new Error(message || "CDP target command failed");
685
+ }
686
+ return new Error(String(error));
687
+ }
688
+ isRecord(value) {
689
+ return typeof value === "object" && value !== null;
690
+ }
691
+ readString(payload, key) {
692
+ if (!this.isRecord(payload)) return null;
693
+ const value = payload[key];
694
+ return typeof value === "string" ? value : null;
695
+ }
696
+ };
697
+ //#endregion
698
+ //#region src/modules/browser/BrowserTargetSessionManager.ts
699
+ var BrowserTargetSessionManager = class {
700
+ browserSession = null;
701
+ attachedTargetSession = null;
702
+ attachedTargetInfo = null;
703
+ autoAttachEnabled = false;
704
+ constructor(getBrowser) {
705
+ this.getBrowser = getBrowser;
706
+ }
707
+ async listTargets(filters = {}) {
708
+ const session = await this.ensureBrowserSession();
709
+ if (filters.discoverOOPIF !== false && !this.autoAttachEnabled) try {
710
+ await session.send("Target.setAutoAttach", {
711
+ autoAttach: true,
712
+ waitForDebuggerOnStart: false,
713
+ flatten: true
714
+ });
715
+ await session.send("Target.setDiscoverTargets", { discover: true });
716
+ this.autoAttachEnabled = true;
717
+ } catch {}
718
+ const response = await session.send("Target.getTargets");
719
+ return (Array.isArray(response.targetInfos) ? response.targetInfos.map((target) => this.normalizeTargetInfo(target)).filter((target) => target !== null) : []).filter((target) => this.matchesFilters(target, filters));
720
+ }
721
+ async attach(targetId) {
722
+ const current = this.attachedTargetInfo;
723
+ if (current?.targetId === targetId && this.attachedTargetSession) return current;
724
+ const target = (await this.listTargets()).find((entry) => entry.targetId === targetId);
725
+ if (!target) throw new Error(`CDP target not found: ${targetId}`);
726
+ await this.detach();
727
+ const session = await this.ensureBrowserSession();
728
+ const response = await session.send("Target.attachToTarget", {
729
+ targetId,
730
+ flatten: true
731
+ });
732
+ if (!response.sessionId) throw new Error(`Target.attachToTarget did not return sessionId for ${targetId}`);
733
+ this.attachedTargetSession = new AttachedTargetSession(session, response.sessionId);
734
+ this.attachedTargetInfo = target;
735
+ return target;
736
+ }
737
+ async detach() {
738
+ if (!this.attachedTargetSession) {
739
+ this.attachedTargetInfo = null;
740
+ return false;
741
+ }
742
+ const session = this.attachedTargetSession;
743
+ this.attachedTargetSession = null;
744
+ this.attachedTargetInfo = null;
745
+ await session.detach();
746
+ return true;
747
+ }
748
+ getAttachedTargetSession() {
749
+ return this.attachedTargetSession;
750
+ }
751
+ getAttachedTargetInfo() {
752
+ return this.attachedTargetInfo;
753
+ }
754
+ async evaluate(expression, options = {}) {
755
+ const response = await this.requireAttachedTargetSession().send("Runtime.evaluate", {
756
+ expression,
757
+ returnByValue: options.returnByValue ?? true,
758
+ awaitPromise: options.awaitPromise ?? true
759
+ });
760
+ if (response.exceptionDetails) {
761
+ const details = response.exceptionDetails;
762
+ throw new Error(details.exception?.description || details.text || "Runtime.evaluate failed in attached target");
763
+ }
764
+ return options.returnByValue === false ? response.result ?? null : response.result?.value ?? null;
765
+ }
766
+ async addScriptToEvaluateOnNewDocument(source) {
767
+ return await this.requireAttachedTargetSession().send("Page.addScriptToEvaluateOnNewDocument", { source });
768
+ }
769
+ async dispose() {
770
+ await this.detach();
771
+ this.autoAttachEnabled = false;
772
+ if (this.browserSession) try {
773
+ await this.browserSession.detach();
774
+ } catch {} finally {
775
+ this.browserSession = null;
776
+ }
777
+ }
778
+ requireAttachedTargetSession() {
779
+ if (!this.attachedTargetSession) throw new Error("No CDP target is currently attached");
780
+ return this.attachedTargetSession;
781
+ }
782
+ async ensureBrowserSession() {
783
+ if (this.browserSession) return this.browserSession;
784
+ const browser = this.getBrowser();
785
+ if (!browser) throw new Error("Browser not connected");
786
+ this.browserSession = await browser.target().createCDPSession();
787
+ return this.browserSession;
788
+ }
789
+ matchesFilters(target, filters) {
790
+ if (filters.type && target.type !== filters.type) return false;
791
+ if (filters.types && filters.types.length > 0 && !filters.types.includes(target.type)) return false;
792
+ if (filters.targetId && target.targetId !== filters.targetId) return false;
793
+ if (filters.urlPattern && !target.url.includes(filters.urlPattern)) return false;
794
+ if (filters.titlePattern && !target.title.includes(filters.titlePattern)) return false;
795
+ if (filters.attachedOnly && !target.attached) return false;
796
+ return true;
797
+ }
798
+ normalizeTargetInfo(target) {
799
+ const targetId = typeof target.targetId === "string" ? target.targetId : null;
800
+ const type = typeof target.type === "string" ? target.type : null;
801
+ const title = typeof target.title === "string" ? target.title : "";
802
+ const url = typeof target.url === "string" ? target.url : "";
803
+ const attached = typeof target.attached === "boolean" ? target.attached : false;
804
+ if (!targetId || !type) return null;
805
+ return {
806
+ targetId,
807
+ type,
808
+ title,
809
+ url,
810
+ attached,
811
+ openerId: typeof target.openerId === "string" ? target.openerId : void 0,
812
+ canAccessOpener: typeof target.canAccessOpener === "boolean" ? target.canAccessOpener : void 0,
813
+ openerFrameId: typeof target.openerFrameId === "string" ? target.openerFrameId : void 0,
814
+ browserContextId: typeof target.browserContextId === "string" ? target.browserContextId : void 0,
815
+ subtype: typeof target.subtype === "string" ? target.subtype : void 0
816
+ };
817
+ }
818
+ };
819
+ //#endregion
820
+ //#region src/utils/browserExecutable.ts
821
+ /**
822
+ * Browser executable resolution policy:
823
+ * - Never scan host-installed browsers.
824
+ * - Only honor explicit overrides from environment variables.
825
+ * - Otherwise let Puppeteer handle browser resolution internally.
826
+ */
827
+ const ENV_KEYS = [
828
+ "CHROME_PATH",
829
+ "PUPPETEER_EXECUTABLE_PATH",
830
+ "BROWSER_EXECUTABLE_PATH"
831
+ ];
832
+ let cachedBrowserPath = null;
833
+ function resolveFromEnvironment() {
834
+ for (const key of ENV_KEYS) {
835
+ const candidate = process.env[key]?.trim();
836
+ if (candidate && existsSync(candidate)) return candidate;
837
+ }
838
+ }
839
+ function resolveFromPuppeteer() {
840
+ try {
841
+ const candidate = executablePath("chrome");
842
+ if (candidate && existsSync(candidate)) return candidate;
843
+ } catch {}
844
+ }
845
+ /**
846
+ * Resolve explicit browser executable path.
847
+ *
848
+ * Returns undefined when no explicit path is configured so callers can
849
+ * fall back to Puppeteer's managed browser behavior.
850
+ */
851
+ function findBrowserExecutable() {
852
+ if (cachedBrowserPath !== null) {
853
+ if (!cachedBrowserPath || existsSync(cachedBrowserPath)) return cachedBrowserPath;
854
+ cachedBrowserPath = null;
855
+ }
856
+ cachedBrowserPath = resolveFromEnvironment() ?? resolveFromPuppeteer();
857
+ return cachedBrowserPath;
858
+ }
859
+ //#endregion
860
+ //#region src/modules/collector/PageScriptCollectors.ts
861
+ async function setupWebWorkerTracking(page) {
862
+ await page.evaluateOnNewDocument(() => {
863
+ const workerWindow = window;
864
+ const originalWorker = workerWindow.Worker;
865
+ if (typeof originalWorker !== "function") return;
866
+ const workerUrls = Array.isArray(workerWindow.__workerUrls) ? workerWindow.__workerUrls : [];
867
+ workerWindow.Worker = new Proxy(originalWorker, { construct(target, args, newTarget) {
868
+ const worker = Reflect.construct(target, args, newTarget);
869
+ const [scriptURL] = args;
870
+ if (typeof scriptURL === "string") {
871
+ workerUrls.push(scriptURL);
872
+ workerWindow.__workerUrls = workerUrls;
873
+ } else if (scriptURL instanceof URL) {
874
+ workerUrls.push(scriptURL.toString());
875
+ workerWindow.__workerUrls = workerUrls;
876
+ }
877
+ return worker;
878
+ } });
879
+ });
880
+ }
881
+ async function collectInlineScripts(page, maxSingleSize, maxFilesPerCollect) {
882
+ const scripts = await page.evaluate((limit) => {
883
+ return Array.from(document.querySelectorAll("script")).filter((script) => !script.src && script.textContent).map((script, index) => {
884
+ let content = script.textContent || "";
885
+ const originalSize = content.length;
886
+ let truncated = false;
887
+ if (content.length > limit) {
888
+ content = content.substring(0, limit);
889
+ truncated = true;
890
+ }
891
+ return {
892
+ url: `inline-script-${index}`,
893
+ content,
894
+ size: content.length,
895
+ type: "inline",
896
+ metadata: {
897
+ scriptType: script.type || "text/javascript",
898
+ async: script.async,
899
+ defer: script.defer,
900
+ integrity: script.integrity || void 0,
901
+ truncated,
902
+ originalSize: truncated ? originalSize : void 0
903
+ }
904
+ };
905
+ });
906
+ }, maxSingleSize);
907
+ const limitedScripts = scripts.slice(0, maxFilesPerCollect);
908
+ if (scripts.length > limitedScripts.length) logger.warn(`Found ${scripts.length} inline scripts, limiting to ${maxFilesPerCollect}`);
909
+ const truncatedCount = limitedScripts.filter((s) => s.metadata?.truncated).length;
910
+ if (truncatedCount > 0) logger.warn(`${truncatedCount} inline scripts were truncated due to size limits`);
911
+ logger.debug(`Collected ${limitedScripts.length} inline scripts`);
912
+ return limitedScripts;
913
+ }
914
+ async function collectServiceWorkers(page, shouldCollectUrl = () => true) {
915
+ try {
916
+ const serviceWorkers = await page.evaluate(async () => {
917
+ if (!("serviceWorker" in navigator)) return [];
918
+ const registrations = await navigator.serviceWorker.getRegistrations();
919
+ const workers = [];
920
+ for (const registration of registrations) {
921
+ const worker = registration.active || registration.installing || registration.waiting;
922
+ if (worker && worker.scriptURL) workers.push({
923
+ url: worker.scriptURL,
924
+ scope: registration.scope,
925
+ state: worker.state
926
+ });
927
+ }
928
+ return workers;
929
+ });
930
+ const files = [];
931
+ for (const worker of serviceWorkers) {
932
+ if (!shouldCollectUrl(worker.url)) continue;
933
+ try {
934
+ const content = await page.evaluate(async (url) => {
935
+ return await (await fetch(url)).text();
936
+ }, worker.url);
937
+ if (content) {
938
+ files.push({
939
+ url: worker.url,
940
+ content,
941
+ size: content.length,
942
+ type: "service-worker"
943
+ });
944
+ logger.debug(`Collected Service Worker: ${worker.url}`);
945
+ }
946
+ } catch (error) {
947
+ logger.warn(`Failed to collect Service Worker: ${worker.url}`, error);
948
+ }
949
+ }
950
+ return files;
951
+ } catch (error) {
952
+ logger.warn("Service Worker collection failed", error);
953
+ return [];
954
+ }
955
+ }
956
+ async function collectWebWorkers(page, shouldCollectUrl = () => true) {
957
+ try {
958
+ const workerUrls = await page.evaluate(() => {
959
+ const workerWindow = window;
960
+ return Array.isArray(workerWindow.__workerUrls) ? workerWindow.__workerUrls : [];
961
+ });
962
+ const files = [];
963
+ for (const url of workerUrls) try {
964
+ const absoluteUrl = new URL(url, page.url()).href;
965
+ if (!shouldCollectUrl(absoluteUrl)) continue;
966
+ const content = await page.evaluate(async (workerUrl) => {
967
+ return await (await fetch(workerUrl)).text();
968
+ }, absoluteUrl);
969
+ if (content) {
970
+ files.push({
971
+ url: absoluteUrl,
972
+ content,
973
+ size: content.length,
974
+ type: "web-worker"
975
+ });
976
+ logger.debug(`Collected Web Worker: ${absoluteUrl}`);
977
+ }
978
+ } catch (error) {
979
+ logger.warn(`Failed to collect Web Worker: ${url}`, error);
980
+ }
981
+ return files;
982
+ } catch (error) {
983
+ logger.warn("Web Worker collection failed", error);
984
+ return [];
985
+ }
986
+ }
987
+ function analyzeDependencies(files) {
988
+ const nodes = [];
989
+ const edges = [];
990
+ files.forEach((file) => {
991
+ nodes.push({
992
+ id: file.url,
993
+ url: file.url,
994
+ type: file.type
995
+ });
996
+ });
997
+ files.forEach((file) => {
998
+ extractDependencies(file.content).forEach((dep) => {
999
+ const targetFile = files.find((f) => f.url.includes(dep) || f.url.endsWith(dep) || f.url.endsWith(`${dep}.js`));
1000
+ if (targetFile) edges.push({
1001
+ from: file.url,
1002
+ to: targetFile.url,
1003
+ type: "import"
1004
+ });
1005
+ });
1006
+ });
1007
+ logger.debug(`Dependency graph: ${nodes.length} nodes, ${edges.length} edges`);
1008
+ return {
1009
+ nodes,
1010
+ edges
1011
+ };
1012
+ }
1013
+ function extractDependencies(code) {
1014
+ const dependencies = [];
1015
+ const importRegex = /import\s+.*?\s+from\s+['"]([^'"]+)['"]/g;
1016
+ let match;
1017
+ while ((match = importRegex.exec(code)) !== null) if (match[1]) dependencies.push(match[1]);
1018
+ const requireRegex = /require\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1019
+ while ((match = requireRegex.exec(code)) !== null) if (match[1]) dependencies.push(match[1]);
1020
+ const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
1021
+ while ((match = dynamicImportRegex.exec(code)) !== null) if (match[1]) dependencies.push(match[1]);
1022
+ return [...new Set(dependencies)];
1023
+ }
1024
+ function calculatePriorityScore(file) {
1025
+ let score = 0;
1026
+ if (file.type === "inline") score += 10;
1027
+ else if (file.type === "external") score += 5;
1028
+ if (file.size < 10 * 1024) score += 15;
1029
+ else if (file.size < 50 * 1024) score += 10;
1030
+ else if (file.size > 200 * 1024) score -= 10;
1031
+ const url = file.url.toLowerCase();
1032
+ if (url.includes("main") || url.includes("index") || url.includes("app")) score += 20;
1033
+ if (url.includes("crypto") || url.includes("encrypt") || url.includes("sign")) score += 30;
1034
+ if (url.includes("api") || url.includes("request") || url.includes("ajax")) score += 25;
1035
+ if (url.includes("core") || url.includes("common") || url.includes("util")) score += 15;
1036
+ if (url.includes("vendor") || url.includes("lib") || url.includes("jquery") || url.includes("react")) score -= 20;
1037
+ if (url.includes("node_modules") || url.includes("bundle")) score -= 30;
1038
+ return score;
1039
+ }
1040
+ //#endregion
1041
+ //#region src/modules/collector/CodeCollectorCollectInternal.ts
1042
+ function isRecord(value) {
1043
+ return typeof value === "object" && value !== null;
1044
+ }
1045
+ function toRecord(value) {
1046
+ return isRecord(value) ? value : void 0;
1047
+ }
1048
+ function isCDPResponseReceivedParams(value) {
1049
+ if (!isRecord(value) || !isRecord(value.response)) return false;
1050
+ return typeof value.response.url === "string" && typeof value.requestId === "string";
1051
+ }
1052
+ function isCodeSummary(value) {
1053
+ if (!isRecord(value)) return false;
1054
+ return typeof value.url === "string" && typeof value.size === "number" && typeof value.type === "string" && typeof value.hasEncryption === "boolean" && typeof value.hasAPI === "boolean" && typeof value.hasObfuscation === "boolean" && Array.isArray(value.functions) && Array.isArray(value.imports) && typeof value.preview === "string";
1055
+ }
1056
+ function isCodeFile(value) {
1057
+ if (!isRecord(value)) return false;
1058
+ return typeof value.url === "string" && typeof value.content === "string" && typeof value.size === "number" && typeof value.type === "string";
1059
+ }
1060
+ function assertCollectorInternals(value) {
1061
+ if (!isRecord(value)) throw new Error("Invalid collector context");
1062
+ if (typeof value.init !== "function" || typeof value.applyAntiDetection !== "function" || typeof value.shouldCollectUrl !== "function") throw new Error("Invalid collector context");
1063
+ }
1064
+ async function collectInnerImpl(self, options) {
1065
+ assertCollectorInternals(self);
1066
+ const startTime = Date.now();
1067
+ logger.info(`Collecting code from: ${options.url}`);
1068
+ const cacheOptions = toRecord(options);
1069
+ if (self.cacheEnabled) {
1070
+ const cached = await self.cache.get(options.url, cacheOptions);
1071
+ if (cached) {
1072
+ logger.info(` Cache hit for: ${options.url}`);
1073
+ return cached;
1074
+ }
1075
+ }
1076
+ await self.init();
1077
+ if (!self.browser) throw new Error("Browser not initialized");
1078
+ const page = await self.browser.newPage();
1079
+ try {
1080
+ const timeoutMs = options.timeout ?? self.config.timeout ?? 3e4;
1081
+ page.setDefaultTimeout(timeoutMs);
1082
+ await page.setUserAgent(self.userAgent);
1083
+ await self.applyAntiDetection(page);
1084
+ if (options.includeWebWorker !== false) await setupWebWorkerTracking(page);
1085
+ const files = [];
1086
+ const appendFilesWithinLimit = (incoming, label) => {
1087
+ const remaining = self.MAX_FILES_PER_COLLECT - files.length;
1088
+ if (remaining <= 0) {
1089
+ logger.warn(`Reached max files limit (${self.MAX_FILES_PER_COLLECT}), skipping ${label}`);
1090
+ return;
1091
+ }
1092
+ if (incoming.length > remaining) logger.warn(`Collected ${incoming.length} ${label}, limiting to remaining ${remaining} files`);
1093
+ files.push(...incoming.slice(0, remaining));
1094
+ };
1095
+ self.cdpSession = await page.createCDPSession();
1096
+ await self.cdpSession.send("Network.enable");
1097
+ await self.cdpSession.send("Runtime.enable");
1098
+ self.cdpListeners.responseReceived = async (params) => {
1099
+ if (!isCDPResponseReceivedParams(params)) return;
1100
+ const { response, requestId, type } = params;
1101
+ const url = response.url;
1102
+ if (files.length >= self.MAX_FILES_PER_COLLECT) {
1103
+ if (files.length === self.MAX_FILES_PER_COLLECT) logger.warn(`Reached max files limit (${self.MAX_FILES_PER_COLLECT}), will skip remaining files`);
1104
+ return;
1105
+ }
1106
+ self.cleanupCollectedUrls();
1107
+ if (type === "Script" || response.mimeType?.includes("javascript") || url.endsWith(".js")) {
1108
+ if (options.includeExternal === false) return;
1109
+ if (!self.shouldCollectUrl(url, options.filterRules)) return;
1110
+ try {
1111
+ const responseBody = await self.cdpSession.send("Network.getResponseBody", { requestId });
1112
+ if (typeof responseBody.body !== "string") return;
1113
+ const content = responseBody.base64Encoded ? Buffer.from(responseBody.body, "base64").toString("utf-8") : responseBody.body;
1114
+ const contentSize = content.length;
1115
+ let finalContent = content;
1116
+ let truncated = false;
1117
+ if (contentSize > self.MAX_SINGLE_FILE_SIZE) {
1118
+ finalContent = content.substring(0, self.MAX_SINGLE_FILE_SIZE);
1119
+ truncated = true;
1120
+ logger.warn(`[CDP] Large file truncated: ${url} (${(contentSize / 1024).toFixed(2)} KB -> ${(self.MAX_SINGLE_FILE_SIZE / 1024).toFixed(2)} KB)`);
1121
+ }
1122
+ if (!self.collectedUrls.has(url)) {
1123
+ self.collectedUrls.add(url);
1124
+ const file = {
1125
+ url,
1126
+ content: finalContent,
1127
+ size: finalContent.length,
1128
+ type: "external",
1129
+ metadata: truncated ? {
1130
+ truncated: true,
1131
+ originalSize: contentSize,
1132
+ truncatedSize: finalContent.length
1133
+ } : void 0
1134
+ };
1135
+ const fileCountBeforeAppend = files.length;
1136
+ appendFilesWithinLimit([file], "external scripts");
1137
+ if (files.length > fileCountBeforeAppend) {
1138
+ self.collectedFilesCache.set(url, file);
1139
+ logger.debug(`[CDP] Collected (${files.length}/${self.MAX_FILES_PER_COLLECT}): ${url} (${(finalContent.length / 1024).toFixed(2)} KB)${truncated ? " [TRUNCATED]" : ""}`);
1140
+ }
1141
+ }
1142
+ } catch (error) {
1143
+ logger.warn(`[CDP] Failed to get response body for: ${url}`, error);
1144
+ }
1145
+ }
1146
+ };
1147
+ self.cdpSession.on("Network.responseReceived", self.cdpListeners.responseReceived);
1148
+ logger.info(`Navigating to: ${options.url}`);
1149
+ await page.goto(options.url, {
1150
+ waitUntil: "networkidle2",
1151
+ timeout: options.timeout || self.config.timeout
1152
+ });
1153
+ if (options.includeInline !== false) {
1154
+ logger.info("Collecting inline scripts...");
1155
+ appendFilesWithinLimit(await collectInlineScripts(page, self.MAX_SINGLE_FILE_SIZE, self.MAX_FILES_PER_COLLECT), "inline scripts");
1156
+ }
1157
+ if (options.includeServiceWorker !== false) {
1158
+ logger.info("Collecting Service Workers...");
1159
+ appendFilesWithinLimit(await collectServiceWorkers(page, (url) => self.shouldCollectUrl(url, options.filterRules)), "service workers");
1160
+ }
1161
+ if (options.includeWebWorker !== false) {
1162
+ logger.info("Collecting Web Workers...");
1163
+ appendFilesWithinLimit(await collectWebWorkers(page, (url) => self.shouldCollectUrl(url, options.filterRules)), "web workers");
1164
+ }
1165
+ if (options.includeDynamic) {
1166
+ logger.info("Waiting for dynamic scripts...");
1167
+ await new Promise((resolve) => setTimeout(resolve, 3e3));
1168
+ }
1169
+ if (self.cdpSession) {
1170
+ if (self.cdpListeners.responseReceived) self.cdpSession.off("Network.responseReceived", self.cdpListeners.responseReceived);
1171
+ await self.cdpSession.detach();
1172
+ self.cdpSession = null;
1173
+ self.cdpListeners = {};
1174
+ }
1175
+ const collectTime = Date.now() - startTime;
1176
+ const truncatedFiles = files.filter((f) => f.metadata?.truncated);
1177
+ if (truncatedFiles.length > 0) {
1178
+ logger.warn(`${truncatedFiles.length} files were truncated due to size limits`);
1179
+ truncatedFiles.forEach((f) => {
1180
+ const originalSize = typeof f.metadata?.originalSize === "number" ? f.metadata.originalSize : f.size;
1181
+ logger.warn(` - ${f.url}: ${(originalSize / 1024).toFixed(2)} KB -> ${(f.size / 1024).toFixed(2)} KB`);
1182
+ });
1183
+ }
1184
+ let processedFiles = files;
1185
+ if (options.smartMode && options.smartMode !== "full") try {
1186
+ logger.info(` Applying smart collection mode: ${options.smartMode}`);
1187
+ const smartOptions = {
1188
+ mode: options.smartMode,
1189
+ maxTotalSize: options.maxTotalSize,
1190
+ maxFileSize: options.maxFileSize,
1191
+ priorities: options.priorities
1192
+ };
1193
+ const smartResult = await self.smartCollector.smartCollect(page, files, smartOptions);
1194
+ if (options.smartMode === "summary") {
1195
+ logger.info(` Returning ${smartResult.length} code summaries`);
1196
+ if (Array.isArray(smartResult) && smartResult.every((item) => isCodeSummary(item))) return {
1197
+ files: [],
1198
+ summaries: smartResult,
1199
+ dependencies: {
1200
+ nodes: [],
1201
+ edges: []
1202
+ },
1203
+ totalSize: 0,
1204
+ collectTime: Date.now() - startTime
1205
+ };
1206
+ }
1207
+ if (Array.isArray(smartResult) && smartResult.every((item) => isCodeFile(item))) processedFiles = smartResult;
1208
+ else {
1209
+ logger.warn("Smart collection returned unexpected type, using original files");
1210
+ processedFiles = files;
1211
+ }
1212
+ } catch (error) {
1213
+ logger.error("Smart collection failed, using original files:", error);
1214
+ processedFiles = files;
1215
+ }
1216
+ if (options.compress) try {
1217
+ logger.info(`Compressing ${processedFiles.length} files with enhanced compressor...`);
1218
+ const filesToCompress = processedFiles.filter((file) => self.compressor.shouldCompress(file.content)).map((file) => ({
1219
+ url: file.url,
1220
+ content: file.content
1221
+ }));
1222
+ if (filesToCompress.length === 0) logger.info("No files need compression (all below threshold)");
1223
+ else {
1224
+ const compressedResults = await self.compressor.compressBatch(filesToCompress, {
1225
+ level: void 0,
1226
+ useCache: true,
1227
+ maxRetries: 3,
1228
+ concurrency: 5,
1229
+ onProgress: (progress) => {
1230
+ if (progress % 25 === 0) logger.debug(`Compression progress: ${progress.toFixed(0)}%`);
1231
+ }
1232
+ });
1233
+ const compressedMap = new Map(compressedResults.map((r) => [r.url, r]));
1234
+ for (const file of processedFiles) {
1235
+ const compressed = compressedMap.get(file.url);
1236
+ if (compressed) file.metadata = {
1237
+ ...file.metadata,
1238
+ compressed: true,
1239
+ originalSize: compressed.originalSize,
1240
+ compressedSize: compressed.compressedSize,
1241
+ compressionRatio: compressed.compressionRatio
1242
+ };
1243
+ }
1244
+ const stats = self.compressor.getStats();
1245
+ logger.info(` Compressed ${compressedResults.length}/${processedFiles.length} files`);
1246
+ logger.info(` Compression stats: ${(stats.totalOriginalSize / 1024).toFixed(2)} KB -> ${(stats.totalCompressedSize / 1024).toFixed(2)} KB (${stats.averageRatio.toFixed(1)}% reduction)`);
1247
+ logger.info(` Cache: ${stats.cacheHits} hits, ${stats.cacheMisses} misses (${stats.cacheHits > 0 ? (stats.cacheHits / (stats.cacheHits + stats.cacheMisses) * 100).toFixed(1) : 0}% hit rate)`);
1248
+ }
1249
+ } catch (error) {
1250
+ logger.error("Compression failed:", error);
1251
+ }
1252
+ const dependencies = analyzeDependencies(processedFiles);
1253
+ const totalSize = processedFiles.reduce((sum, file) => sum + file.size, 0);
1254
+ logger.success(`Collected ${processedFiles.length} files (${(totalSize / 1024).toFixed(2)} KB) in ${collectTime}ms`);
1255
+ const result = {
1256
+ files: processedFiles,
1257
+ dependencies,
1258
+ totalSize,
1259
+ collectTime
1260
+ };
1261
+ if (self.cacheEnabled) {
1262
+ await self.cache.set(options.url, result, cacheOptions);
1263
+ logger.debug(` Saved to cache: ${options.url}`);
1264
+ }
1265
+ return result;
1266
+ } catch (error) {
1267
+ logger.error("Code collection failed", error);
1268
+ throw error;
1269
+ } finally {
1270
+ if (self.cdpSession) {
1271
+ try {
1272
+ if (self.cdpListeners.responseReceived) self.cdpSession.off("Network.responseReceived", self.cdpListeners.responseReceived);
1273
+ await self.cdpSession.detach();
1274
+ } catch {}
1275
+ self.cdpSession = null;
1276
+ self.cdpListeners = {};
1277
+ }
1278
+ await page.close();
1279
+ }
1280
+ }
1281
+ //#endregion
1282
+ //#region src/modules/collector/CodeCollectorUtilsInternal.ts
1283
+ function shouldCollectUrlImpl(url, filterRules) {
1284
+ if (!filterRules || filterRules.length === 0) return true;
1285
+ for (const rule of filterRules) if (new RegExp(rule.replace(/\*/g, ".*")).test(url)) return true;
1286
+ return false;
1287
+ }
1288
+ async function navigateWithRetryImpl(page, url, options, maxRetries = 3) {
1289
+ let lastError = null;
1290
+ for (let i = 0; i < maxRetries; i++) try {
1291
+ await page.goto(url, options);
1292
+ return;
1293
+ } catch (error) {
1294
+ lastError = error;
1295
+ logger.warn(`Navigation attempt ${i + 1}/${maxRetries} failed: ${error}`);
1296
+ if (i < maxRetries - 1) await new Promise((resolve) => setTimeout(resolve, 1e3 * (i + 1)));
1297
+ }
1298
+ throw lastError || /* @__PURE__ */ new Error("Navigation failed after retries");
1299
+ }
1300
+ async function getPerformanceMetricsImpl(page) {
1301
+ try {
1302
+ return await page.evaluate(() => {
1303
+ const perf = performance.getEntriesByType("navigation")[0];
1304
+ return {
1305
+ domContentLoaded: perf.domContentLoadedEventEnd - perf.domContentLoadedEventStart,
1306
+ loadComplete: perf.loadEventEnd - perf.loadEventStart,
1307
+ domInteractive: perf.domInteractive - perf.fetchStart,
1308
+ totalTime: perf.loadEventEnd - perf.fetchStart
1309
+ };
1310
+ });
1311
+ } catch (error) {
1312
+ logger.warn("Failed to get performance metrics", error);
1313
+ return {};
1314
+ }
1315
+ }
1316
+ async function collectPageMetadataImpl(page) {
1317
+ try {
1318
+ return await page.evaluate(() => {
1319
+ return {
1320
+ title: document.title,
1321
+ url: window.location.href,
1322
+ userAgent: navigator.userAgent,
1323
+ viewport: {
1324
+ width: window.innerWidth,
1325
+ height: window.innerHeight
1326
+ },
1327
+ cookies: document.cookie,
1328
+ localStorage: Object.keys(localStorage).length,
1329
+ sessionStorage: Object.keys(sessionStorage).length
1330
+ };
1331
+ });
1332
+ } catch (error) {
1333
+ logger.warn("Failed to collect page metadata", error);
1334
+ return {};
1335
+ }
1336
+ }
1337
+ //#endregion
1338
+ //#region src/modules/collector/playwright-cdp-fallback.ts
1339
+ let electronCompatPatched = false;
1340
+ function resolvePlaywrightInternalPaths() {
1341
+ const packageRoot = dirname(createRequire(import.meta.url).resolve("playwright-core/package.json"));
1342
+ return {
1343
+ playwrightServerPath: join$1(packageRoot, "lib/server/playwright.js"),
1344
+ crBrowserPath: join$1(packageRoot, "lib/server/chromium/crBrowser.js")
1345
+ };
1346
+ }
1347
+ function ensureElectronDownloadBehaviorCompatPatch() {
1348
+ if (electronCompatPatched) return;
1349
+ const require = createRequire(import.meta.url);
1350
+ const { playwrightServerPath, crBrowserPath } = resolvePlaywrightInternalPaths();
1351
+ const { createPlaywright } = require(playwrightServerPath);
1352
+ createPlaywright({
1353
+ sdkLanguage: "javascript",
1354
+ isInternalPlaywright: true
1355
+ });
1356
+ const { CRBrowserContext } = require(crBrowserPath);
1357
+ if (CRBrowserContext.prototype.__jshookElectronCompatPatched) {
1358
+ electronCompatPatched = true;
1359
+ return;
1360
+ }
1361
+ const originalInitialize = CRBrowserContext.prototype._initialize;
1362
+ CRBrowserContext.prototype._initialize = async function patchedInitialize(...args) {
1363
+ try {
1364
+ return await originalInitialize.apply(this, args);
1365
+ } catch (error) {
1366
+ const message = error instanceof Error ? error.message : String(error);
1367
+ if (message.includes("Browser.setDownloadBehavior") && message.includes("Browser context management is not supported")) {
1368
+ logger.warn("[playwright-cdp-fallback] Swallowed Browser.setDownloadBehavior for legacy Electron CDP endpoint.");
1369
+ return;
1370
+ }
1371
+ throw error;
1372
+ }
1373
+ };
1374
+ Object.defineProperty(CRBrowserContext.prototype, "__jshookElectronCompatPatched", {
1375
+ value: true,
1376
+ configurable: false,
1377
+ enumerable: false,
1378
+ writable: false
1379
+ });
1380
+ electronCompatPatched = true;
1381
+ }
1382
+ function normalizePlaywrightConnectEndpoint(endpoint) {
1383
+ return endpoint;
1384
+ }
1385
+ function mapWaitUntil(waitUntil) {
1386
+ if (waitUntil === "networkidle0" || waitUntil === "networkidle2") return "networkidle";
1387
+ return waitUntil;
1388
+ }
1389
+ function getDefaultContext(browser) {
1390
+ const context = browser.contexts()[0];
1391
+ if (!context) throw new Error("Connected Playwright CDP browser exposes no default context. Cannot create or resolve pages.");
1392
+ return context;
1393
+ }
1394
+ function createPageAdapter(playwrightPage, pageCache) {
1395
+ const cached = pageCache.get(playwrightPage);
1396
+ if (cached) return cached;
1397
+ const proxiedPage = new Proxy({
1398
+ async goto(url, options) {
1399
+ return await playwrightPage.goto(url, {
1400
+ ...options,
1401
+ waitUntil: mapWaitUntil(options?.waitUntil)
1402
+ });
1403
+ },
1404
+ async reload(options) {
1405
+ return await playwrightPage.reload({
1406
+ ...options,
1407
+ waitUntil: mapWaitUntil(options?.waitUntil)
1408
+ });
1409
+ },
1410
+ async waitForNavigation(options) {
1411
+ const mappedWaitUntil = mapWaitUntil(options?.waitUntil);
1412
+ if (typeof playwrightPage.waitForNavigation === "function") return await playwrightPage.waitForNavigation({
1413
+ ...options,
1414
+ waitUntil: mappedWaitUntil
1415
+ });
1416
+ return await playwrightPage.waitForLoadState(mappedWaitUntil ?? "load", { timeout: options?.timeout });
1417
+ },
1418
+ async select(selector, ...values) {
1419
+ return await playwrightPage.selectOption(selector, values);
1420
+ },
1421
+ async evaluateOnNewDocument(pageFunction, ...args) {
1422
+ return await playwrightPage.addInitScript(pageFunction, ...[...args]);
1423
+ },
1424
+ async createCDPSession() {
1425
+ const context = playwrightPage.context();
1426
+ if (typeof context.newCDPSession !== "function") throw new Error("Playwright BrowserContext does not expose newCDPSession() for the attached page.");
1427
+ return await context.newCDPSession(playwrightPage);
1428
+ },
1429
+ async setUserAgent(_userAgent) {
1430
+ logger.debug("[playwright-cdp-fallback] Ignoring page.setUserAgent() for attached Playwright CDP page.");
1431
+ }
1432
+ }, { get(target, prop, receiver) {
1433
+ if (Reflect.has(target, prop)) {
1434
+ const value = Reflect.get(target, prop, receiver);
1435
+ return typeof value === "function" ? value.bind(target) : value;
1436
+ }
1437
+ const value = Reflect.get(playwrightPage, prop);
1438
+ return typeof value === "function" ? value.bind(playwrightPage) : value;
1439
+ } });
1440
+ pageCache.set(playwrightPage, proxiedPage);
1441
+ return proxiedPage;
1442
+ }
1443
+ function createTargetAdapter(playwrightPage, pageCache) {
1444
+ return {
1445
+ type: () => "page",
1446
+ url: () => playwrightPage.url(),
1447
+ page: async () => createPageAdapter(playwrightPage, pageCache)
1448
+ };
1449
+ }
1450
+ function createBrowserAdapter(playwrightBrowser) {
1451
+ const pageCache = /* @__PURE__ */ new WeakMap();
1452
+ return new Proxy({
1453
+ targets() {
1454
+ return playwrightBrowser.contexts().flatMap((context) => context.pages()).map((page) => createTargetAdapter(page, pageCache));
1455
+ },
1456
+ async pages() {
1457
+ return playwrightBrowser.contexts().flatMap((context) => context.pages()).map((page) => createPageAdapter(page, pageCache));
1458
+ },
1459
+ async newPage() {
1460
+ return createPageAdapter(await getDefaultContext(playwrightBrowser).newPage(), pageCache);
1461
+ },
1462
+ async disconnect() {
1463
+ await playwrightBrowser.close();
1464
+ },
1465
+ async close() {
1466
+ await playwrightBrowser.close();
1467
+ }
1468
+ }, { get(target, prop, receiver) {
1469
+ if (Reflect.has(target, prop)) {
1470
+ const value = Reflect.get(target, prop, receiver);
1471
+ return typeof value === "function" ? value.bind(target) : value;
1472
+ }
1473
+ const value = Reflect.get(playwrightBrowser, prop);
1474
+ return typeof value === "function" ? value.bind(playwrightBrowser) : value;
1475
+ } });
1476
+ }
1477
+ async function connectPlaywrightCdpFallback(endpoint, timeoutMs) {
1478
+ ensureElectronDownloadBehaviorCompatPatch();
1479
+ const { chromium } = await import("playwright-core");
1480
+ return createBrowserAdapter(await chromium.connectOverCDP(normalizePlaywrightConnectEndpoint(endpoint), { timeout: timeoutMs }));
1481
+ }
1482
+ //#endregion
1483
+ //#region src/modules/collector/CodeCollectorConnectionInternal.ts
1484
+ function resolveDefaultChromeUserDataDir(channel = "stable") {
1485
+ const home = homedir();
1486
+ if (process.platform === "win32") {
1487
+ const localAppData = process.env.LOCALAPPDATA ?? join(home, "AppData", "Local");
1488
+ switch (channel) {
1489
+ case "beta": return join(localAppData, "Google", "Chrome Beta", "User Data");
1490
+ case "dev": return join(localAppData, "Google", "Chrome Dev", "User Data");
1491
+ case "canary": return join(localAppData, "Google", "Chrome SxS", "User Data");
1492
+ default: return join(localAppData, "Google", "Chrome", "User Data");
1493
+ }
1494
+ }
1495
+ if (process.platform === "darwin") {
1496
+ const appSupport = join(home, "Library", "Application Support");
1497
+ switch (channel) {
1498
+ case "beta": return join(appSupport, "Google", "Chrome Beta");
1499
+ case "dev": return join(appSupport, "Google", "Chrome Dev");
1500
+ case "canary": return join(appSupport, "Google", "Chrome Canary");
1501
+ default: return join(appSupport, "Google", "Chrome");
1502
+ }
1503
+ }
1504
+ const configHome = process.env.XDG_CONFIG_HOME ?? join(home, ".config");
1505
+ switch (channel) {
1506
+ case "beta": return join(configHome, "google-chrome-beta");
1507
+ case "dev": return join(configHome, "google-chrome-unstable");
1508
+ case "canary": return join(configHome, "google-chrome-canary");
1509
+ default: return join(configHome, "google-chrome");
1510
+ }
1511
+ }
1512
+ async function resolveAutoConnectWsEndpointImpl(options) {
1513
+ const channel = options.channel ?? "stable";
1514
+ const devToolsActivePortPath = join(options.userDataDir ?? resolveDefaultChromeUserDataDir(channel), "DevToolsActivePort");
1515
+ let fileContent;
1516
+ try {
1517
+ fileContent = await readFile(devToolsActivePortPath, "utf8");
1518
+ } catch (error) {
1519
+ throw new Error(`Could not read DevToolsActivePort from "${devToolsActivePortPath}". Check if Chrome is running from this profile and remote debugging is enabled at chrome://inspect/#remote-debugging.`, { cause: error });
1520
+ }
1521
+ const [rawPort, rawPath] = fileContent.split(/\r?\n/u).map((line) => line.trim()).filter(Boolean);
1522
+ if (!rawPort || !rawPath) throw new Error(`Invalid DevToolsActivePort contents found in "${devToolsActivePortPath}".`);
1523
+ const port = Number.parseInt(rawPort, 10);
1524
+ if (!Number.isInteger(port) || port <= 0 || port > 65535) throw new Error(`Invalid remote debugging port "${rawPort}" in "${devToolsActivePortPath}".`);
1525
+ return `ws://127.0.0.1:${port}${rawPath}`;
1526
+ }
1527
+ async function resolveConnectOptionsImpl(endpointOrOptions) {
1528
+ if (typeof endpointOrOptions === "string") {
1529
+ const endpoint = endpointOrOptions.trim();
1530
+ if (!endpoint) throw new Error("Connection endpoint cannot be empty.");
1531
+ return endpoint.startsWith("ws://") || endpoint.startsWith("wss://") ? { browserWSEndpoint: endpoint } : { browserURL: endpoint };
1532
+ }
1533
+ if (endpointOrOptions.wsEndpoint) return { browserWSEndpoint: endpointOrOptions.wsEndpoint };
1534
+ if (endpointOrOptions.browserURL) return { browserURL: endpointOrOptions.browserURL };
1535
+ if (endpointOrOptions.autoConnect || endpointOrOptions.userDataDir || endpointOrOptions.channel) return { browserWSEndpoint: await resolveAutoConnectWsEndpointImpl(endpointOrOptions) };
1536
+ throw new Error("browserURL, wsEndpoint, autoConnect, userDataDir, or channel is required to connect to an existing browser.");
1537
+ }
1538
+ function isAutoConnectRequest(endpointOrOptions) {
1539
+ return typeof endpointOrOptions !== "string" && Boolean(endpointOrOptions.autoConnect || endpointOrOptions.userDataDir || endpointOrOptions.channel);
1540
+ }
1541
+ function getUnknownErrorMessage(error) {
1542
+ if (error instanceof Error) return error.message;
1543
+ if (typeof error === "object" && error !== null) {
1544
+ const directMessage = "message" in error && typeof error.message === "string" ? error.message.trim() : "";
1545
+ if (directMessage) return directMessage;
1546
+ const nestedError = "error" in error ? error.error : void 0;
1547
+ if (nestedError instanceof Error && nestedError.message) return nestedError.message;
1548
+ if (typeof nestedError === "object" && nestedError !== null) {
1549
+ const nestedMessage = "message" in nestedError && typeof nestedError.message === "string" ? nestedError.message.trim() : "";
1550
+ if (nestedMessage) return nestedMessage;
1551
+ }
1552
+ const serialized = JSON.stringify(error);
1553
+ if (serialized && serialized !== "{}") return serialized;
1554
+ }
1555
+ return String(error);
1556
+ }
1557
+ function normalizeConnectError(error, target, endpointOrOptions) {
1558
+ const message = getUnknownErrorMessage(error);
1559
+ if (isAutoConnectRequest(endpointOrOptions) && /ECONNREFUSED/i.test(message)) return /* @__PURE__ */ new Error(`Failed to connect to existing browser: ${message}. Chrome is not currently listening at ${target}. DevToolsActivePort may be stale after a browser restart. Re-open Chrome, confirm remote debugging is enabled at chrome://inspect/#remote-debugging, click Allow if prompted, and retry.`);
1560
+ return error instanceof Error ? error : /* @__PURE__ */ new Error(`Failed to connect to existing browser: ${message}`);
1561
+ }
1562
+ function buildConnectTimeoutError(target, endpointOrOptions, timeoutMs) {
1563
+ const baseMessage = `Timed out after ${timeoutMs}ms while connecting to existing browser: ${target}. The CDP handshake did not complete in time.`;
1564
+ if (isAutoConnectRequest(endpointOrOptions)) return /* @__PURE__ */ new Error(`${baseMessage} If Chrome prompted for remote debugging approval, click Allow in Chrome and then retry the tool call.`);
1565
+ return /* @__PURE__ */ new Error(`${baseMessage} Verify that the browser debugging endpoint is reachable and retry.`);
1566
+ }
1567
+ function shouldAttemptPlaywrightFallback(error) {
1568
+ const message = getUnknownErrorMessage(error);
1569
+ if (/ECONNREFUSED|ENOTFOUND|404|stale/i.test(message)) return false;
1570
+ return /timed out|handshake|Protocol error|Target closed|ECONNRESET|socket hang up|WebSocket/i.test(message);
1571
+ }
1572
+ async function connectWithPlaywrightFallbackImpl(connectOptions, primaryError, timeoutMs) {
1573
+ const endpoint = connectOptions.browserWSEndpoint ?? connectOptions.browserURL;
1574
+ if (!endpoint) throw primaryError instanceof Error ? primaryError : new Error(String(primaryError));
1575
+ logger.warn(`[connect-fallback] Rebrowser connect failed. Falling back to Playwright CDP compatibility mode for ${endpoint}.`);
1576
+ try {
1577
+ return await connectPlaywrightCdpFallback(endpoint, timeoutMs);
1578
+ } catch (fallbackError) {
1579
+ const primaryMessage = getUnknownErrorMessage(primaryError);
1580
+ const fallbackMessage = getUnknownErrorMessage(fallbackError);
1581
+ throw new Error(`Failed to connect to existing browser via both rebrowser-puppeteer and Playwright CDP compatibility fallback. Primary error: ${primaryMessage}. Fallback error: ${fallbackMessage}.`, { cause: fallbackError });
1582
+ }
1583
+ }
1584
+ async function connectWithTimeoutImpl(connectOptions, target, endpointOrOptions, timeoutMs, connectAttemptRef) {
1585
+ const attemptId = ++connectAttemptRef.current;
1586
+ try {
1587
+ return await new Promise((resolve, reject) => {
1588
+ let settled = false;
1589
+ const timer = setTimeout(() => {
1590
+ settled = true;
1591
+ if (connectAttemptRef.current === attemptId) connectAttemptRef.current += 1;
1592
+ reject(buildConnectTimeoutError(target, endpointOrOptions, timeoutMs));
1593
+ }, timeoutMs);
1594
+ connect({
1595
+ ...connectOptions,
1596
+ defaultViewport: null
1597
+ }).then(async (browser) => {
1598
+ if (settled || connectAttemptRef.current !== attemptId) {
1599
+ try {
1600
+ await browser.disconnect();
1601
+ } catch {}
1602
+ return;
1603
+ }
1604
+ settled = true;
1605
+ clearTimeout(timer);
1606
+ resolve(browser);
1607
+ }).catch((error) => {
1608
+ if (settled || connectAttemptRef.current !== attemptId) return;
1609
+ settled = true;
1610
+ clearTimeout(timer);
1611
+ reject(normalizeConnectError(error, target, endpointOrOptions));
1612
+ });
1613
+ });
1614
+ } catch (error) {
1615
+ if (!shouldAttemptPlaywrightFallback(error)) throw error;
1616
+ return await connectWithPlaywrightFallbackImpl(connectOptions, error, timeoutMs);
1617
+ }
1618
+ }
1619
+ //#endregion
1620
+ //#region src/modules/collector/CodeCollectorFileQueryInternal.ts
1621
+ function getCollectedFilesSummaryImpl(collectedFilesCache) {
1622
+ const summaries = Array.from(collectedFilesCache.values()).map((file) => ({
1623
+ url: file.url,
1624
+ size: file.size,
1625
+ type: file.type,
1626
+ truncated: typeof file.metadata?.truncated === "boolean" ? file.metadata.truncated : void 0,
1627
+ originalSize: typeof file.metadata?.originalSize === "number" ? file.metadata.originalSize : void 0
1628
+ }));
1629
+ logger.info(`Returning summary of ${summaries.length} collected files`);
1630
+ return summaries;
1631
+ }
1632
+ function getFileByUrlImpl(collectedFilesCache, url) {
1633
+ const file = collectedFilesCache.get(url);
1634
+ if (file) {
1635
+ logger.info(`Returning file: ${url} (${(file.size / 1024).toFixed(2)} KB)`);
1636
+ return file;
1637
+ }
1638
+ logger.warn(`File not found: ${url}`);
1639
+ return null;
1640
+ }
1641
+ function getFilesByPatternImpl(collectedFilesCache, pattern, limit, maxTotalSize) {
1642
+ const regex = new RegExp(pattern);
1643
+ const matched = [];
1644
+ for (const file of collectedFilesCache.values()) if (regex.test(file.url)) matched.push(file);
1645
+ const returned = [];
1646
+ let totalSize = 0;
1647
+ let truncated = false;
1648
+ for (let i = 0; i < matched.length && i < limit; i++) {
1649
+ const file = matched[i];
1650
+ if (file && totalSize + file.size <= maxTotalSize) {
1651
+ returned.push(file);
1652
+ totalSize += file.size;
1653
+ } else {
1654
+ truncated = true;
1655
+ break;
1656
+ }
1657
+ }
1658
+ if (truncated || matched.length > limit) logger.warn(`Pattern "${pattern}" matched ${matched.length} files, returning ${returned.length} (limited by size/count)`);
1659
+ logger.info(` Pattern "${pattern}": matched ${matched.length}, returning ${returned.length} files (${(totalSize / 1024).toFixed(2)} KB)`);
1660
+ return {
1661
+ files: returned,
1662
+ totalSize,
1663
+ matched: matched.length,
1664
+ returned: returned.length,
1665
+ truncated
1666
+ };
1667
+ }
1668
+ function getTopPriorityFilesImpl(collectedFilesCache, topN, maxTotalSize) {
1669
+ const allFiles = Array.from(collectedFilesCache.values());
1670
+ const scoredFiles = allFiles.map((file) => ({
1671
+ file,
1672
+ score: calculatePriorityScore(file)
1673
+ }));
1674
+ scoredFiles.sort((a, b) => b.score - a.score);
1675
+ const selected = [];
1676
+ let totalSize = 0;
1677
+ for (let i = 0; i < Math.min(topN, scoredFiles.length); i++) {
1678
+ const item = scoredFiles[i];
1679
+ if (item?.file && totalSize + item.file.size <= maxTotalSize) {
1680
+ selected.push(item.file);
1681
+ totalSize += item.file.size;
1682
+ } else break;
1683
+ }
1684
+ logger.info(`Returning top ${selected.length}/${allFiles.length} priority files (${(totalSize / 1024).toFixed(2)} KB)`);
1685
+ return {
1686
+ files: selected,
1687
+ totalSize,
1688
+ totalFiles: allFiles.length
1689
+ };
1690
+ }
1691
+ //#endregion
1692
+ //#region src/modules/collector/CodeCollector.ts
1693
+ var CodeCollector = class CodeCollector {
1694
+ config;
1695
+ browser = null;
1696
+ collectedUrls = /* @__PURE__ */ new Set();
1697
+ initPromise = null;
1698
+ collectLock = null;
1699
+ connectAttemptRef = { current: 0 };
1700
+ MAX_COLLECTED_URLS;
1701
+ MAX_FILES_PER_COLLECT;
1702
+ MAX_RESPONSE_SIZE;
1703
+ MAX_SINGLE_FILE_SIZE;
1704
+ CONNECT_TIMEOUT_MS;
1705
+ viewport;
1706
+ userAgent;
1707
+ collectedFilesCache = /* @__PURE__ */ new Map();
1708
+ cache;
1709
+ cacheEnabled = true;
1710
+ smartCollector;
1711
+ compressor;
1712
+ cdpSession = null;
1713
+ browserTargetSessionManager = null;
1714
+ cdpListeners = {};
1715
+ activePageIndex = null;
1716
+ currentHeadless = null;
1717
+ explicitlyClosed = false;
1718
+ connectedToExistingBrowser = false;
1719
+ /** PID of the Chrome child process launched by puppeteer, used for force-kill fallback. */
1720
+ chromePid = null;
1721
+ static BROWSER_CLOSE_TIMEOUT_MS = 5e3;
1722
+ constructor(config) {
1723
+ this.config = config;
1724
+ this.MAX_COLLECTED_URLS = config.maxCollectedUrls ?? 1e4;
1725
+ this.MAX_FILES_PER_COLLECT = config.maxFilesPerCollect ?? 200;
1726
+ this.MAX_RESPONSE_SIZE = config.maxTotalContentSize ?? 512 * 1024;
1727
+ this.MAX_SINGLE_FILE_SIZE = config.maxSingleFileSize ?? 200 * 1024;
1728
+ this.CONNECT_TIMEOUT_MS = Number(process.env.JSHOOK_CONNECT_TIMEOUT_MS) || 6e4;
1729
+ this.viewport = config.viewport ?? {
1730
+ width: 1920,
1731
+ height: 1080
1732
+ };
1733
+ this.userAgent = config.userAgent ?? "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36";
1734
+ this.cache = new CodeCache();
1735
+ this.smartCollector = new SmartCodeCollector();
1736
+ this.compressor = new CodeCompressor();
1737
+ logger.info(` CodeCollector limits: maxCollect=${this.MAX_FILES_PER_COLLECT} files, maxResponse=${(this.MAX_RESPONSE_SIZE / 1024).toFixed(0)}KB, maxSingle=${(this.MAX_SINGLE_FILE_SIZE / 1024).toFixed(0)}KB`);
1738
+ logger.info(` Strategy: Collect ALL files -> Cache -> Return summary/partial data to fit MCP limits`);
1739
+ }
1740
+ setCacheEnabled(enabled) {
1741
+ this.cacheEnabled = enabled;
1742
+ logger.info(`Code cache ${enabled ? "enabled" : "disabled"}`);
1743
+ }
1744
+ async clearFileCache() {
1745
+ await this.cache.clear();
1746
+ }
1747
+ async getFileCacheStats() {
1748
+ return await this.cache.getStats();
1749
+ }
1750
+ async clearAllData() {
1751
+ logger.info("Clearing all collected data...");
1752
+ await this.cache.clear();
1753
+ this.compressor.clearCache();
1754
+ this.compressor.resetStats();
1755
+ this.collectedUrls.clear();
1756
+ this.collectedFilesCache.clear();
1757
+ logger.success("All data cleared");
1758
+ }
1759
+ async getAllStats() {
1760
+ return {
1761
+ cache: await this.cache.getStats(),
1762
+ compression: {
1763
+ ...this.compressor.getStats(),
1764
+ cacheSize: this.compressor.getCacheSize()
1765
+ },
1766
+ collector: {
1767
+ collectedUrls: this.collectedUrls.size,
1768
+ maxCollectedUrls: this.MAX_COLLECTED_URLS
1769
+ }
1770
+ };
1771
+ }
1772
+ getCache() {
1773
+ return this.cache;
1774
+ }
1775
+ getCompressor() {
1776
+ return this.compressor;
1777
+ }
1778
+ cleanupCollectedUrls() {
1779
+ if (this.collectedUrls.size > this.MAX_COLLECTED_URLS) {
1780
+ logger.warn(`Collected URLs exceeded ${this.MAX_COLLECTED_URLS}, clearing...`);
1781
+ const urls = Array.from(this.collectedUrls);
1782
+ this.collectedUrls.clear();
1783
+ urls.slice(-Math.floor(this.MAX_COLLECTED_URLS / 2)).forEach((url) => this.collectedUrls.add(url));
1784
+ }
1785
+ }
1786
+ async init(headless) {
1787
+ if (this.browser) return;
1788
+ this.explicitlyClosed = false;
1789
+ if (this.initPromise) return this.initPromise;
1790
+ this.initPromise = this.initInner(headless);
1791
+ try {
1792
+ await this.initPromise;
1793
+ } finally {
1794
+ this.initPromise = null;
1795
+ }
1796
+ }
1797
+ async initInner(headless) {
1798
+ const useHeadless = headless ?? this.config.headless;
1799
+ const executablePath = this.resolveExecutablePath();
1800
+ const launchOptions = {
1801
+ headless: useHeadless,
1802
+ args: [
1803
+ ...this.config.args || [],
1804
+ "--no-sandbox",
1805
+ "--disable-setuid-sandbox",
1806
+ "--disable-dev-shm-usage",
1807
+ "--disable-blink-features=AutomationControlled",
1808
+ "--disable-extensions",
1809
+ "--disable-component-extensions-with-background-pages",
1810
+ "--disable-web-security",
1811
+ "--disable-features=IsolateOrigins,site-per-process",
1812
+ `--window-size=${this.viewport.width},${this.viewport.height}`,
1813
+ "--ignore-certificate-errors"
1814
+ ],
1815
+ defaultViewport: this.viewport,
1816
+ protocolTimeout: 6e4
1817
+ };
1818
+ if (executablePath) launchOptions.executablePath = executablePath;
1819
+ logger.info("Initializing browser with anti-detection...");
1820
+ this.browser = await launch(launchOptions);
1821
+ this.connectedToExistingBrowser = false;
1822
+ this.chromePid = this.browser.process()?.pid ?? null;
1823
+ if (this.chromePid) logger.debug(`Chrome child process PID: ${this.chromePid}`);
1824
+ this.currentHeadless = useHeadless === void 0 ? true : useHeadless !== false;
1825
+ this.browser.on("disconnected", () => {
1826
+ logger.warn("Browser disconnected");
1827
+ this.browser = null;
1828
+ this.currentHeadless = null;
1829
+ this.connectedToExistingBrowser = false;
1830
+ this.chromePid = null;
1831
+ this.browserTargetSessionManager?.dispose();
1832
+ this.browserTargetSessionManager = null;
1833
+ if (this.cdpSession) {
1834
+ this.cdpSession = null;
1835
+ this.cdpListeners = {};
1836
+ }
1837
+ });
1838
+ logger.success("Browser initialized with enhanced anti-detection");
1839
+ }
1840
+ resolveExecutablePath() {
1841
+ const configuredPath = this.config.executablePath?.trim();
1842
+ if (configuredPath) {
1843
+ if (existsSync(configuredPath)) return configuredPath;
1844
+ throw new Error(`Configured browser executable was not found: ${configuredPath}. Set a valid executablePath or configure CHROME_PATH / PUPPETEER_EXECUTABLE_PATH / BROWSER_EXECUTABLE_PATH.`);
1845
+ }
1846
+ const detectedPath = findBrowserExecutable();
1847
+ if (detectedPath) return detectedPath;
1848
+ logger.info("No explicit browser executable configured. Falling back to Puppeteer-managed browser resolution.");
1849
+ }
1850
+ async close() {
1851
+ await this.clearAllData();
1852
+ this.explicitlyClosed = true;
1853
+ this.activePageIndex = null;
1854
+ const browser = this.browser;
1855
+ const disconnectOnly = this.connectedToExistingBrowser;
1856
+ const pid = this.chromePid;
1857
+ this.browser = null;
1858
+ this.currentHeadless = null;
1859
+ this.connectedToExistingBrowser = false;
1860
+ this.chromePid = null;
1861
+ await this.browserTargetSessionManager?.dispose();
1862
+ this.browserTargetSessionManager = null;
1863
+ if (this.cdpSession) {
1864
+ this.cdpSession = null;
1865
+ this.cdpListeners = {};
1866
+ }
1867
+ if (browser) if (disconnectOnly) await browser.disconnect();
1868
+ else await this.closeBrowserWithForceKill(browser, pid);
1869
+ logger.info("Browser closed and all data cleared");
1870
+ }
1871
+ /**
1872
+ * Close browser with a timeout guard. If browser.close() hangs or fails,
1873
+ * force-kill the Chrome child process by PID to prevent zombie processes.
1874
+ */
1875
+ async closeBrowserWithForceKill(browser, pid) {
1876
+ try {
1877
+ await Promise.race([browser.close(), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("browser.close() timed out")), CodeCollector.BROWSER_CLOSE_TIMEOUT_MS))]);
1878
+ } catch (error) {
1879
+ logger.warn("browser.close() failed or timed out, attempting force-kill:", error);
1880
+ CodeCollector.forceKillPid(pid);
1881
+ }
1882
+ }
1883
+ /** Force-kill a process by PID. Safe to call with null/invalid PIDs. */
1884
+ static forceKillPid(pid) {
1885
+ if (!pid) return;
1886
+ try {
1887
+ process.kill(pid, "SIGKILL");
1888
+ logger.info(`Force-killed Chrome process PID ${pid}`);
1889
+ } catch (error) {
1890
+ if (error.code !== "ESRCH") logger.warn(`Failed to force-kill Chrome PID ${pid}:`, error);
1891
+ }
1892
+ }
1893
+ /** Get the tracked Chrome child process PID (null if not launched or already closed). */
1894
+ getChromePid() {
1895
+ return this.chromePid;
1896
+ }
1897
+ getPageTargets() {
1898
+ if (!this.browser) return [];
1899
+ return this.browser.targets().filter((target) => target.type() === "page");
1900
+ }
1901
+ async resolvePageTargetHandle(target, timeoutMs = 5e3) {
1902
+ const page = await Promise.race([target.page(), new Promise((_, reject) => {
1903
+ setTimeout(() => {
1904
+ reject(new PrerequisiteError(`Timed out after ${timeoutMs}ms while resolving a Puppeteer Page handle from the attached Chrome target.`));
1905
+ }, timeoutMs);
1906
+ })]);
1907
+ if (!page) throw new PrerequisiteError("Attached browser target does not expose a Puppeteer Page handle in the current Chrome remote debugging mode.");
1908
+ return page;
1909
+ }
1910
+ isExistingBrowserConnection() {
1911
+ return this.connectedToExistingBrowser;
1912
+ }
1913
+ async getActivePage() {
1914
+ if (!this.browser) {
1915
+ if (this.explicitlyClosed) throw new PrerequisiteError("Browser was explicitly closed. Call browser_launch or browser_attach first.");
1916
+ try {
1917
+ await this.init();
1918
+ } catch (error) {
1919
+ throw new PrerequisiteError(`Browser not available: ${error instanceof Error ? error.message : String(error)}`);
1920
+ }
1921
+ }
1922
+ const pageTargets = this.getPageTargets();
1923
+ if (pageTargets.length === 0) return await this.browser.newPage();
1924
+ if (this.activePageIndex !== null && this.activePageIndex < pageTargets.length) return await this.resolvePageTargetHandle(pageTargets[this.activePageIndex]);
1925
+ const lastTarget = pageTargets[pageTargets.length - 1];
1926
+ if (!lastTarget) throw new Error("Failed to get active page");
1927
+ return await this.resolvePageTargetHandle(lastTarget);
1928
+ }
1929
+ async listPages() {
1930
+ if (!this.browser) return [];
1931
+ return this.getPageTargets().map((target, index) => ({
1932
+ index,
1933
+ url: target.url(),
1934
+ title: ""
1935
+ }));
1936
+ }
1937
+ async listResolvedPages(timeoutMs = 1500) {
1938
+ if (!this.browser) return [];
1939
+ const targets = this.getPageTargets();
1940
+ return (await Promise.all(targets.map(async (target, index) => {
1941
+ try {
1942
+ const page = await this.resolvePageTargetHandle(target, timeoutMs);
1943
+ let title = "";
1944
+ try {
1945
+ title = await Promise.race([page.title(), new Promise((resolve) => {
1946
+ setTimeout(() => resolve(""), timeoutMs);
1947
+ })]);
1948
+ } catch {
1949
+ title = "";
1950
+ }
1951
+ return {
1952
+ index,
1953
+ url: target.url(),
1954
+ title,
1955
+ page
1956
+ };
1957
+ } catch {
1958
+ return null;
1959
+ }
1960
+ }))).filter((page) => page !== null);
1961
+ }
1962
+ async selectPage(index) {
1963
+ if (!this.browser) throw new Error("Browser not connected");
1964
+ const pages = await this.listPages();
1965
+ if (index < 0 || index >= pages.length) throw new Error(`Page index ${index} out of range (0-${pages.length - 1})`);
1966
+ this.activePageIndex = index;
1967
+ logger.info(`Active page set to index ${index}: ${pages[index].url}`);
1968
+ }
1969
+ async createPage(url) {
1970
+ if (!this.browser) await this.init();
1971
+ const page = await this.browser.newPage();
1972
+ await page.setUserAgent(this.userAgent);
1973
+ await this.applyAntiDetection(page);
1974
+ if (url) await page.goto(url, {
1975
+ waitUntil: "networkidle2",
1976
+ timeout: this.config.timeout
1977
+ });
1978
+ logger.info(`New page created${url ? `: ${url}` : ""}`);
1979
+ return page;
1980
+ }
1981
+ async applyAntiDetection(page) {
1982
+ await page.evaluateOnNewDocument(() => {
1983
+ Object.defineProperty(navigator, "webdriver", { get: () => false });
1984
+ Object.defineProperty(navigator, "plugins", { get: () => [
1985
+ 1,
1986
+ 2,
1987
+ 3,
1988
+ 4,
1989
+ 5
1990
+ ] });
1991
+ Object.defineProperty(navigator, "languages", { get: () => ["en-US", "en"] });
1992
+ const win = window;
1993
+ if (!win.chrome) win.chrome = {
1994
+ runtime: {},
1995
+ loadTimes: function() {},
1996
+ csi: function() {},
1997
+ app: {}
1998
+ };
1999
+ const originalQuery = window.navigator.permissions.query;
2000
+ window.navigator.permissions.query = (parameters) => {
2001
+ if (parameters.name === "notifications") return Promise.resolve({ state: "denied" });
2002
+ return originalQuery(parameters);
2003
+ };
2004
+ });
2005
+ }
2006
+ async getStatus() {
2007
+ if (!this.browser) return {
2008
+ running: false,
2009
+ pagesCount: 0
2010
+ };
2011
+ try {
2012
+ const version = await this.browser.version();
2013
+ return {
2014
+ running: true,
2015
+ pagesCount: this.getPageTargets().length,
2016
+ version,
2017
+ effectiveHeadless: this.currentHeadless ?? void 0
2018
+ };
2019
+ } catch (error) {
2020
+ logger.debug("Browser not running or disconnected:", error);
2021
+ return {
2022
+ running: false,
2023
+ pagesCount: 0
2024
+ };
2025
+ }
2026
+ }
2027
+ async collect(options) {
2028
+ while (this.collectLock) try {
2029
+ await this.collectLock;
2030
+ } catch {}
2031
+ let resolve;
2032
+ let reject;
2033
+ this.collectLock = new Promise((res, rej) => {
2034
+ resolve = res;
2035
+ reject = rej;
2036
+ });
2037
+ try {
2038
+ const result = await this.collectInner(options);
2039
+ resolve(result);
2040
+ return result;
2041
+ } catch (e) {
2042
+ reject(e);
2043
+ throw e;
2044
+ } finally {
2045
+ this.collectLock = null;
2046
+ }
2047
+ }
2048
+ async collectInner(options) {
2049
+ return collectInnerImpl(this, options);
2050
+ }
2051
+ shouldCollectUrl(url, filterRules) {
2052
+ return shouldCollectUrlImpl(url, filterRules);
2053
+ }
2054
+ async navigateWithRetry(page, url, options, maxRetries = 3) {
2055
+ return navigateWithRetryImpl(page, url, options, maxRetries);
2056
+ }
2057
+ async getPerformanceMetrics(page) {
2058
+ return getPerformanceMetricsImpl(page);
2059
+ }
2060
+ async collectPageMetadata(page) {
2061
+ return collectPageMetadataImpl(page);
2062
+ }
2063
+ async resolveConnectOptions(endpointOrOptions) {
2064
+ return resolveConnectOptionsImpl(endpointOrOptions);
2065
+ }
2066
+ async connectWithTimeout(connectOptions, target, endpointOrOptions) {
2067
+ return connectWithTimeoutImpl(connectOptions, target, endpointOrOptions, this.CONNECT_TIMEOUT_MS, this.connectAttemptRef);
2068
+ }
2069
+ async connect(endpointOrOptions) {
2070
+ this.explicitlyClosed = false;
2071
+ await this.browserTargetSessionManager?.dispose();
2072
+ this.browserTargetSessionManager = null;
2073
+ if (this.browser) {
2074
+ try {
2075
+ await this.browser.disconnect();
2076
+ } catch {}
2077
+ this.browser = null;
2078
+ this.currentHeadless = null;
2079
+ }
2080
+ this.activePageIndex = null;
2081
+ const connectOptions = await this.resolveConnectOptions(endpointOrOptions);
2082
+ const target = connectOptions.browserWSEndpoint ?? connectOptions.browserURL ?? "auto-detected Chrome debugging endpoint";
2083
+ logger.info(`Connecting to existing browser: ${target}`);
2084
+ this.browser = await this.connectWithTimeout(connectOptions, target, endpointOrOptions);
2085
+ this.connectedToExistingBrowser = true;
2086
+ this.browser.on("disconnected", () => {
2087
+ logger.warn("Browser disconnected");
2088
+ this.browser = null;
2089
+ this.currentHeadless = null;
2090
+ this.connectedToExistingBrowser = false;
2091
+ this.browserTargetSessionManager?.dispose();
2092
+ this.browserTargetSessionManager = null;
2093
+ if (this.cdpSession) {
2094
+ this.cdpSession = null;
2095
+ this.cdpListeners = {};
2096
+ }
2097
+ });
2098
+ logger.success("Connected to existing browser successfully");
2099
+ }
2100
+ getBrowser() {
2101
+ return this.browser;
2102
+ }
2103
+ getBrowserTargetSessionManager() {
2104
+ if (!this.browserTargetSessionManager) this.browserTargetSessionManager = new BrowserTargetSessionManager(() => this.browser);
2105
+ return this.browserTargetSessionManager;
2106
+ }
2107
+ async listCdpTargets(filters) {
2108
+ return await this.getBrowserTargetSessionManager().listTargets(filters);
2109
+ }
2110
+ async attachCdpTarget(targetId) {
2111
+ return await this.getBrowserTargetSessionManager().attach(targetId);
2112
+ }
2113
+ async detachCdpTarget() {
2114
+ return await this.getBrowserTargetSessionManager().detach();
2115
+ }
2116
+ getAttachedTargetSession() {
2117
+ return this.browserTargetSessionManager?.getAttachedTargetSession() ?? null;
2118
+ }
2119
+ getAttachedTargetInfo() {
2120
+ return this.browserTargetSessionManager?.getAttachedTargetInfo() ?? null;
2121
+ }
2122
+ getCollectionStats() {
2123
+ return {
2124
+ totalCollected: this.collectedUrls.size,
2125
+ uniqueUrls: this.collectedUrls.size
2126
+ };
2127
+ }
2128
+ clearCache() {
2129
+ this.collectedUrls.clear();
2130
+ logger.info("Collection cache cleared");
2131
+ }
2132
+ getCollectedFilesSummary() {
2133
+ return getCollectedFilesSummaryImpl(this.collectedFilesCache);
2134
+ }
2135
+ getFileByUrl(url) {
2136
+ return getFileByUrlImpl(this.collectedFilesCache, url);
2137
+ }
2138
+ getFilesByPattern(pattern, limit = 20, maxTotalSize = this.MAX_RESPONSE_SIZE) {
2139
+ return getFilesByPatternImpl(this.collectedFilesCache, pattern, limit, maxTotalSize);
2140
+ }
2141
+ getTopPriorityFiles(topN = 10, maxTotalSize = this.MAX_RESPONSE_SIZE) {
2142
+ return getTopPriorityFilesImpl(this.collectedFilesCache, topN, maxTotalSize);
2143
+ }
2144
+ clearCollectedFilesCache() {
2145
+ const count = this.collectedFilesCache.size;
2146
+ this.collectedFilesCache.clear();
2147
+ logger.info(`Cleared collected files cache (${count} files)`);
2148
+ }
2149
+ };
2150
+ //#endregion
2151
+ //#region src/modules/collector/DOMInspector.evaluations.ts
2152
+ const serializeForEvaluation = (value) => value === void 0 ? "undefined" : JSON.stringify(value);
2153
+ const SHADOW_DOM_WALKER_SCRIPT = `
2154
+ const walkShadowRoots = () => {
2155
+ const roots = [document];
2156
+ const queue = [document];
2157
+ let shadowRootCount = 0;
2158
+ while (queue.length > 0) {
2159
+ const root = queue.shift();
2160
+ if (!root) continue;
2161
+ for (const element of Array.from(root.querySelectorAll('*'))) {
2162
+ const shadowRoot = element.shadowRoot;
2163
+ if (shadowRoot) {
2164
+ roots.push(shadowRoot);
2165
+ queue.push(shadowRoot);
2166
+ shadowRootCount += 1;
2167
+ }
2168
+ }
2169
+ }
2170
+ return { roots, shadowRootCount };
2171
+ };
2172
+ `.trim();
2173
+ function buildQueryAllEvaluation(selector, limit) {
2174
+ return `
2175
+ ${SHADOW_DOM_WALKER_SCRIPT}
2176
+ const selector = ${serializeForEvaluation(selector)};
2177
+ const maxLimit = ${serializeForEvaluation(limit)};
2178
+ const { roots, shadowRootCount } = walkShadowRoots();
2179
+ const seen = new Set();
2180
+ const results = [];
2181
+ let totalMatches = 0;
2182
+ for (const root of roots) {
2183
+ const nodeList = Array.from(root.querySelectorAll(selector));
2184
+ totalMatches += nodeList.length;
2185
+ for (const element of nodeList) {
2186
+ if (seen.has(element)) continue;
2187
+ seen.add(element);
2188
+ const attributes = {};
2189
+ for (const attr of Array.from(element.attributes)) {
2190
+ attributes[attr.name] = attr.value;
2191
+ }
2192
+ const rect = element.getBoundingClientRect();
2193
+ const style = window.getComputedStyle(element);
2194
+ const textContent = element.textContent?.trim() || '';
2195
+ results.push({
2196
+ found: true,
2197
+ nodeName: element.nodeName,
2198
+ attributes,
2199
+ textContent: textContent.length > 500 ? textContent.substring(0, 500) + '...[truncated]' : textContent,
2200
+ boundingBox: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
2201
+ visible: style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0',
2202
+ });
2203
+ if (results.length >= maxLimit) break;
2204
+ }
2205
+ if (results.length >= maxLimit) break;
2206
+ }
2207
+ if (totalMatches > maxLimit) {
2208
+ console.warn('[DOMInspector] Found ' + totalMatches + ' elements for "' + selector + '", limiting to ' + maxLimit);
2209
+ }
2210
+ return { elements: results, diagnostics: { readyState: document.readyState, shadowRootCount } };
2211
+ `.trim();
2212
+ }
2213
+ function buildFindClickableEvaluation(filterText) {
2214
+ return `
2215
+ ${SHADOW_DOM_WALKER_SCRIPT}
2216
+ const filter = ${serializeForEvaluation(filterText)};
2217
+ const normalizedFilter = filter?.toLowerCase();
2218
+ const { roots, shadowRootCount } = walkShadowRoots();
2219
+ const results = [];
2220
+ const seen = new Set();
2221
+ const appendClickable = (element, type, fallbackSelector) => {
2222
+ if (seen.has(element)) return;
2223
+ seen.add(element);
2224
+ const text = element.textContent?.trim() || (element.value ?? '').trim() || '';
2225
+ if (normalizedFilter && !text.toLowerCase().includes(normalizedFilter)) return;
2226
+ const rect = element.getBoundingClientRect();
2227
+ const style = window.getComputedStyle(element);
2228
+ let selector = fallbackSelector;
2229
+ if (element.id) selector = '#' + element.id;
2230
+ else if (element.className) selector = fallbackSelector + '.' + element.className.split(' ')[0];
2231
+ results.push({
2232
+ selector,
2233
+ text,
2234
+ type,
2235
+ visible:
2236
+ style.display !== 'none' &&
2237
+ style.visibility !== 'hidden' &&
2238
+ style.opacity !== '0' &&
2239
+ rect.width > 0 &&
2240
+ rect.height > 0,
2241
+ boundingBox: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
2242
+ });
2243
+ };
2244
+ for (const root of roots) {
2245
+ root
2246
+ .querySelectorAll('button, input[type="button"], input[type="submit"], input[type="reset"]')
2247
+ .forEach((button) => appendClickable(button, 'button', button.tagName.toLowerCase()));
2248
+ root.querySelectorAll('a[href]').forEach((link) => appendClickable(link, 'link', 'a'));
2249
+ }
2250
+ return { elements: results, diagnostics: { readyState: document.readyState, shadowRootCount } };
2251
+ `.trim();
2252
+ }
2253
+ function querySelectorEvaluation(selector) {
2254
+ const element = document.querySelector(selector);
2255
+ if (!element) return { found: false };
2256
+ const attributes = {};
2257
+ for (const attr of Array.from(element.attributes)) attributes[attr.name] = attr.value;
2258
+ const rect = element.getBoundingClientRect();
2259
+ const style = window.getComputedStyle(element);
2260
+ return {
2261
+ found: true,
2262
+ nodeName: element.nodeName,
2263
+ attributes,
2264
+ textContent: element.textContent?.trim() || "",
2265
+ boundingBox: {
2266
+ x: rect.x,
2267
+ y: rect.y,
2268
+ width: rect.width,
2269
+ height: rect.height
2270
+ },
2271
+ visible: style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0"
2272
+ };
2273
+ }
2274
+ function getStructureEvaluation(depth, withText) {
2275
+ const buildTree = (node, currentDepth) => {
2276
+ if (currentDepth > depth) return null;
2277
+ const result = {
2278
+ tag: node.tagName,
2279
+ id: node.id || void 0,
2280
+ class: node.className || void 0
2281
+ };
2282
+ if (withText && node.childNodes.length === 1 && node.childNodes[0]?.nodeType === 3) result.text = node.textContent?.trim();
2283
+ const children = Array.from(node.children).map((child) => buildTree(child, currentDepth + 1)).filter((child) => child !== null);
2284
+ if (children.length > 0) result.children = children;
2285
+ return result;
2286
+ };
2287
+ return buildTree(document.body, 0);
2288
+ }
2289
+ function getComputedStyleEvaluation(selector) {
2290
+ const element = document.querySelector(selector);
2291
+ if (!element) return null;
2292
+ const computed = window.getComputedStyle(element);
2293
+ const result = {};
2294
+ for (const prop of [
2295
+ "display",
2296
+ "visibility",
2297
+ "opacity",
2298
+ "position",
2299
+ "zIndex",
2300
+ "width",
2301
+ "height",
2302
+ "top",
2303
+ "left",
2304
+ "right",
2305
+ "bottom",
2306
+ "color",
2307
+ "backgroundColor",
2308
+ "fontSize",
2309
+ "fontFamily",
2310
+ "border",
2311
+ "padding",
2312
+ "margin",
2313
+ "overflow"
2314
+ ]) result[prop] = computed.getPropertyValue(prop);
2315
+ return result;
2316
+ }
2317
+ function observeDOMChangesEvaluation(opts) {
2318
+ const targetNode = opts.selector ? document.querySelector(opts.selector) : document.body;
2319
+ if (!targetNode) {
2320
+ console.error("Target node not found for MutationObserver");
2321
+ return;
2322
+ }
2323
+ const observer = new MutationObserver((mutations) => {
2324
+ mutations.forEach((mutation) => {
2325
+ console.warn("[DOM Change]", {
2326
+ type: mutation.type,
2327
+ target: mutation.target,
2328
+ addedNodes: mutation.addedNodes.length,
2329
+ removedNodes: mutation.removedNodes.length,
2330
+ attributeName: mutation.attributeName
2331
+ });
2332
+ });
2333
+ });
2334
+ observer.observe(targetNode, {
2335
+ childList: opts.childList !== false,
2336
+ attributes: opts.attributes !== false,
2337
+ characterData: opts.characterData !== false,
2338
+ subtree: opts.subtree !== false
2339
+ });
2340
+ window.__domObserver = observer;
2341
+ }
2342
+ function stopObservingDOMEvaluation() {
2343
+ const typedWindow = window;
2344
+ if (typedWindow.__domObserver) {
2345
+ typedWindow.__domObserver.disconnect();
2346
+ delete typedWindow.__domObserver;
2347
+ }
2348
+ }
2349
+ function findByTextEvaluation(searchText, tagName) {
2350
+ const xpath = tagName ? `//${tagName}[contains(text(), "${searchText}")]` : `//*[contains(text(), "${searchText}")]`;
2351
+ const result = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
2352
+ const matchedElements = [];
2353
+ for (let i = 0; i < Math.min(result.snapshotLength, 100); i++) {
2354
+ const element = result.snapshotItem(i);
2355
+ if (!element) continue;
2356
+ const rect = element.getBoundingClientRect();
2357
+ const style = window.getComputedStyle(element);
2358
+ let selector = element.tagName.toLowerCase();
2359
+ if (element.id) selector = `#${element.id}`;
2360
+ else if (element.className) {
2361
+ const classes = element.className.split(" ").filter(Boolean);
2362
+ if (classes.length > 0) selector = `${element.tagName.toLowerCase()}.${classes[0]}`;
2363
+ }
2364
+ matchedElements.push({
2365
+ found: true,
2366
+ nodeName: element.tagName,
2367
+ textContent: element.textContent?.trim(),
2368
+ selector,
2369
+ boundingBox: {
2370
+ x: rect.x,
2371
+ y: rect.y,
2372
+ width: rect.width,
2373
+ height: rect.height
2374
+ },
2375
+ visible: style.display !== "none" && style.visibility !== "hidden" && style.opacity !== "0"
2376
+ });
2377
+ }
2378
+ return matchedElements;
2379
+ }
2380
+ function getXPathEvaluation(selector) {
2381
+ const element = document.querySelector(selector);
2382
+ if (!element) return null;
2383
+ const parts = [];
2384
+ let current = element;
2385
+ while (current && current !== document.body && current !== document.documentElement) {
2386
+ if (current.id) {
2387
+ parts.unshift(`//*[@id="${current.id}"]`);
2388
+ return parts.join("");
2389
+ }
2390
+ let ix = 0;
2391
+ const siblings = current.parentElement?.children;
2392
+ if (siblings) for (let i = 0; i < siblings.length; i++) {
2393
+ const sibling = siblings[i];
2394
+ if (!sibling) continue;
2395
+ if (sibling === current) break;
2396
+ if (sibling.tagName === current.tagName) ix += 1;
2397
+ }
2398
+ parts.unshift(`/${current.tagName.toLowerCase()}[${ix + 1}]`);
2399
+ current = current.parentElement;
2400
+ }
2401
+ return "/html/body" + parts.join("");
2402
+ }
2403
+ function isInViewportEvaluation(selector) {
2404
+ const element = document.querySelector(selector);
2405
+ if (!element) return false;
2406
+ const rect = element.getBoundingClientRect();
2407
+ return rect.top >= 0 && rect.left >= 0 && rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) && rect.right <= (window.innerWidth || document.documentElement.clientWidth);
2408
+ }
2409
+ //#endregion
2410
+ //#region src/modules/collector/DOMInspector.ts
2411
+ var DOMInspector = class {
2412
+ cdpSession = null;
2413
+ constructor(collector) {
2414
+ this.collector = collector;
2415
+ }
2416
+ async waitForReadyState(page, timeoutMs = 3e3) {
2417
+ const deadline = Date.now() + timeoutMs;
2418
+ let waitedForReadyState = false;
2419
+ let readyState = "unknown";
2420
+ while (Date.now() <= deadline) {
2421
+ readyState = await page.evaluate(() => document.readyState).catch(() => "unknown");
2422
+ if (readyState === "complete") break;
2423
+ waitedForReadyState = true;
2424
+ await new Promise((resolve) => setTimeout(resolve, 100));
2425
+ }
2426
+ return {
2427
+ readyState,
2428
+ waitedForReadyState,
2429
+ frameCount: typeof page.frames === "function" ? page.frames().length : 1
2430
+ };
2431
+ }
2432
+ async querySelector(selector, _getAttributes = true) {
2433
+ try {
2434
+ const elementInfo = await (await this.collector.getActivePage()).evaluate(querySelectorEvaluation, selector);
2435
+ logger.info(`querySelector: ${selector} - ${elementInfo.found ? "found" : "not found"}`);
2436
+ return elementInfo;
2437
+ } catch (error) {
2438
+ logger.error(`querySelector failed for ${selector}:`, error);
2439
+ return { found: false };
2440
+ }
2441
+ }
2442
+ async querySelectorAll(selector, limit = DOM_QUERY_DEFAULT_LIMIT) {
2443
+ try {
2444
+ const page = await this.collector.getActivePage();
2445
+ const readyStateStatus = await this.waitForReadyState(page);
2446
+ const runQuery = async () => page.evaluate(new Function(buildQueryAllEvaluation(selector, limit)));
2447
+ let result = await runQuery();
2448
+ let retried = false;
2449
+ if (result.elements.length === 0 && result.diagnostics.readyState === "complete") {
2450
+ retried = true;
2451
+ await new Promise((resolve) => setTimeout(resolve, 500));
2452
+ result = await runQuery();
2453
+ }
2454
+ const diagnostics = {
2455
+ readyState: result.diagnostics.readyState ?? readyStateStatus.readyState,
2456
+ frameCount: readyStateStatus.frameCount,
2457
+ shadowRootCount: result.diagnostics.shadowRootCount ?? 0,
2458
+ retried,
2459
+ waitedForReadyState: readyStateStatus.waitedForReadyState
2460
+ };
2461
+ logger.info(`querySelectorAll: ${selector} - found ${result.elements.length} elements (limit: ${limit}, readyState: ${diagnostics.readyState}, shadowRoots: ${diagnostics.shadowRootCount}, retried: ${retried})`);
2462
+ return {
2463
+ elements: result.elements,
2464
+ diagnostics
2465
+ };
2466
+ } catch (error) {
2467
+ logger.error(`querySelectorAll failed for ${selector}:`, error);
2468
+ return {
2469
+ elements: [],
2470
+ diagnostics: {
2471
+ readyState: "error",
2472
+ frameCount: 0,
2473
+ shadowRootCount: 0,
2474
+ retried: false,
2475
+ waitedForReadyState: false
2476
+ }
2477
+ };
2478
+ }
2479
+ }
2480
+ async getStructure(maxDepth = 3, includeText = true) {
2481
+ try {
2482
+ const structure = await (await this.collector.getActivePage()).evaluate(getStructureEvaluation, maxDepth, includeText);
2483
+ logger.info("DOM structure retrieved");
2484
+ return structure;
2485
+ } catch (error) {
2486
+ logger.error("getStructure failed:", error);
2487
+ return null;
2488
+ }
2489
+ }
2490
+ async findClickable(filterText) {
2491
+ try {
2492
+ const page = await this.collector.getActivePage();
2493
+ const readyStateStatus = await this.waitForReadyState(page);
2494
+ const runQuery = async () => page.evaluate(new Function(buildFindClickableEvaluation(filterText)));
2495
+ let result = await runQuery();
2496
+ let retried = false;
2497
+ if (result.elements.length === 0 && result.diagnostics.readyState === "complete") {
2498
+ retried = true;
2499
+ await new Promise((resolve) => setTimeout(resolve, 500));
2500
+ result = await runQuery();
2501
+ }
2502
+ const diagnostics = {
2503
+ readyState: result.diagnostics.readyState ?? readyStateStatus.readyState,
2504
+ frameCount: readyStateStatus.frameCount,
2505
+ shadowRootCount: result.diagnostics.shadowRootCount ?? 0,
2506
+ retried,
2507
+ waitedForReadyState: readyStateStatus.waitedForReadyState
2508
+ };
2509
+ logger.info(`findClickable: found ${result.elements.length} elements${filterText ? ` (filtered by: ${filterText})` : ""} (readyState: ${diagnostics.readyState}, shadowRoots: ${diagnostics.shadowRootCount}, retried: ${retried})`);
2510
+ return {
2511
+ elements: result.elements,
2512
+ diagnostics
2513
+ };
2514
+ } catch (error) {
2515
+ logger.error("findClickable failed:", error);
2516
+ return {
2517
+ elements: [],
2518
+ diagnostics: {
2519
+ readyState: "error",
2520
+ frameCount: 0,
2521
+ shadowRootCount: 0,
2522
+ retried: false,
2523
+ waitedForReadyState: false
2524
+ }
2525
+ };
2526
+ }
2527
+ }
2528
+ async getComputedStyle(selector) {
2529
+ try {
2530
+ const styles = await (await this.collector.getActivePage()).evaluate(getComputedStyleEvaluation, selector);
2531
+ logger.info(`getComputedStyle: ${selector} - ${styles ? "found" : "not found"}`);
2532
+ return styles;
2533
+ } catch (error) {
2534
+ logger.error(`getComputedStyle failed for ${selector}:`, error);
2535
+ return null;
2536
+ }
2537
+ }
2538
+ async waitForElement(selector, timeout = DOM_WAIT_ELEMENT_TIMEOUT_MS) {
2539
+ try {
2540
+ await (await this.collector.getActivePage()).waitForSelector(selector, { timeout });
2541
+ return await this.querySelector(selector);
2542
+ } catch (error) {
2543
+ logger.error(`waitForElement timeout for ${selector}:`, error);
2544
+ return null;
2545
+ }
2546
+ }
2547
+ async observeDOMChanges(options = {}) {
2548
+ await (await this.collector.getActivePage()).evaluate(observeDOMChangesEvaluation, options);
2549
+ logger.info("DOM change observer started");
2550
+ }
2551
+ async stopObservingDOM() {
2552
+ await (await this.collector.getActivePage()).evaluate(stopObservingDOMEvaluation);
2553
+ logger.info("DOM change observer stopped");
2554
+ }
2555
+ async findByText(text, tag) {
2556
+ try {
2557
+ const elements = await (await this.collector.getActivePage()).evaluate(findByTextEvaluation, text, tag);
2558
+ logger.info(`findByText: "${text}" - found ${elements.length} elements`);
2559
+ return elements;
2560
+ } catch (error) {
2561
+ logger.error(`findByText failed for "${text}":`, error);
2562
+ return [];
2563
+ }
2564
+ }
2565
+ async getXPath(selector) {
2566
+ try {
2567
+ const xpath = await (await this.collector.getActivePage()).evaluate(getXPathEvaluation, selector);
2568
+ logger.info(`getXPath: ${selector} -> ${xpath}`);
2569
+ return xpath;
2570
+ } catch (error) {
2571
+ logger.error(`getXPath failed for ${selector}:`, error);
2572
+ return null;
2573
+ }
2574
+ }
2575
+ async isInViewport(selector) {
2576
+ try {
2577
+ const inViewport = await (await this.collector.getActivePage()).evaluate(isInViewportEvaluation, selector);
2578
+ logger.info(`isInViewport: ${selector} - ${inViewport}`);
2579
+ return inViewport;
2580
+ } catch (error) {
2581
+ logger.error(`isInViewport failed for ${selector}:`, error);
2582
+ return false;
2583
+ }
2584
+ }
2585
+ async close() {
2586
+ if (this.cdpSession) {
2587
+ await this.cdpSession.detach();
2588
+ this.cdpSession = null;
2589
+ logger.info("DOM Inspector CDP session closed");
2590
+ }
2591
+ }
2592
+ };
2593
+ //#endregion
2594
+ //#region src/modules/debugger/ScriptManager.impl.extract-function-tree.ts
2595
+ const getErrorMessage = (error) => error instanceof Error ? error.message : String(error);
2596
+ const asRecord = (value) => value !== null && typeof value === "object" ? value : null;
2597
+ const asCallable = (value) => typeof value === "function" ? value : null;
2598
+ const resolveCallableExport = (moduleValue, namedExport) => {
2599
+ const moduleRecord = asRecord(moduleValue);
2600
+ const defaultExport = moduleRecord?.default;
2601
+ return asCallable(asRecord(defaultExport)?.default) ?? asCallable(defaultExport) ?? asCallable(moduleRecord?.[namedExport]) ?? asCallable(moduleValue);
2602
+ };
2603
+ async function extractFunctionTreeCore(ctx, scriptId, functionName, options = {}) {
2604
+ const { maxDepth = 3, maxSize = 500, includeComments = true } = options;
2605
+ const script = await ctx.getScriptSource(scriptId);
2606
+ if (!script?.source) throw new Error(`Script not found: ${scriptId}`);
2607
+ let parser;
2608
+ let traverse;
2609
+ let generate;
2610
+ let t;
2611
+ try {
2612
+ parser = await import("@babel/parser");
2613
+ const traverseCandidate = resolveCallableExport(await import("@babel/traverse"), "traverse");
2614
+ if (typeof traverseCandidate !== "function") throw new Error("Invalid @babel/traverse export shape");
2615
+ traverse = traverseCandidate;
2616
+ const generateCandidate = resolveCallableExport(await import("@babel/generator"), "generate");
2617
+ if (typeof generateCandidate !== "function") throw new Error("Invalid @babel/generator export shape");
2618
+ generate = generateCandidate;
2619
+ t = await import("@babel/types");
2620
+ } catch (error) {
2621
+ throw new Error(`Failed to load Babel dependencies. Please install: npm install @babel/parser @babel/traverse @babel/generator @babel/types\nError: ${getErrorMessage(error)}`, { cause: error });
2622
+ }
2623
+ let ast;
2624
+ try {
2625
+ ast = parser.parse(script.source, {
2626
+ sourceType: "unambiguous",
2627
+ plugins: ["jsx", "typescript"]
2628
+ });
2629
+ } catch (error) {
2630
+ throw new Error(`Failed to parse script ${scriptId}: ${getErrorMessage(error)}`, { cause: error });
2631
+ }
2632
+ const allFunctions = /* @__PURE__ */ new Map();
2633
+ const callGraph = {};
2634
+ const extractDependencies = (path) => {
2635
+ const deps = /* @__PURE__ */ new Set();
2636
+ path.traverse({ CallExpression(callPath) {
2637
+ if (t.isIdentifier(callPath.node.callee)) deps.add(callPath.node.callee.name);
2638
+ } });
2639
+ return Array.from(deps);
2640
+ };
2641
+ traverse(ast, {
2642
+ FunctionDeclaration(path) {
2643
+ const name = path.node.id?.name;
2644
+ if (!name) return;
2645
+ const funcCode = generate(path.node, { comments: includeComments }).code;
2646
+ const deps = extractDependencies(path);
2647
+ allFunctions.set(name, {
2648
+ name,
2649
+ code: funcCode,
2650
+ startLine: path.node.loc?.start.line || 0,
2651
+ endLine: path.node.loc?.end.line || 0,
2652
+ dependencies: deps,
2653
+ size: funcCode.length
2654
+ });
2655
+ callGraph[name] = deps;
2656
+ },
2657
+ VariableDeclarator(path) {
2658
+ if (t.isIdentifier(path.node.id) && (t.isFunctionExpression(path.node.init) || t.isArrowFunctionExpression(path.node.init))) {
2659
+ const name = path.node.id.name;
2660
+ const funcCode = generate(path.node, { comments: includeComments }).code;
2661
+ const deps = extractDependencies(path);
2662
+ allFunctions.set(name, {
2663
+ name,
2664
+ code: funcCode,
2665
+ startLine: path.node.loc?.start.line || 0,
2666
+ endLine: path.node.loc?.end.line || 0,
2667
+ dependencies: deps,
2668
+ size: funcCode.length
2669
+ });
2670
+ callGraph[name] = deps;
2671
+ }
2672
+ }
2673
+ });
2674
+ const extracted = /* @__PURE__ */ new Set();
2675
+ const toExtract = [functionName];
2676
+ let currentDepth = 0;
2677
+ while (toExtract.length > 0 && currentDepth < maxDepth) {
2678
+ const current = toExtract.shift();
2679
+ if (extracted.has(current)) continue;
2680
+ const func = allFunctions.get(current);
2681
+ if (!func) continue;
2682
+ extracted.add(current);
2683
+ for (const dep of func.dependencies) if (!extracted.has(dep) && allFunctions.has(dep)) toExtract.push(dep);
2684
+ currentDepth++;
2685
+ }
2686
+ const functions = Array.from(extracted).map((name) => allFunctions.get(name)).filter(Boolean);
2687
+ const code = functions.map((f) => f.code).join("\n\n");
2688
+ const totalSize = code.length;
2689
+ if (totalSize > maxSize * 1024) logger.warn(`Extracted code size (${(totalSize / 1024).toFixed(2)}KB) exceeds limit (${maxSize}KB)`);
2690
+ logger.info(`extractFunctionTree: ${functionName} - extracted ${functions.length} functions (${(totalSize / 1024).toFixed(2)}KB)`);
2691
+ return {
2692
+ mainFunction: functionName,
2693
+ code,
2694
+ functions,
2695
+ callGraph,
2696
+ totalSize,
2697
+ extractedCount: functions.length
2698
+ };
2699
+ }
2700
+ //#endregion
2701
+ //#region src/modules/debugger/ScriptManager.impl.class.ts
2702
+ var ScriptManager = class ScriptManager {
2703
+ static SOURCE_LOAD_BATCH_SIZE = 8;
2704
+ static SEARCH_LINE_YIELD_INTERVAL = 250;
2705
+ static SEARCH_SCRIPT_YIELD_INTERVAL = 10;
2706
+ cdpSession = null;
2707
+ scripts = /* @__PURE__ */ new Map();
2708
+ scriptsByUrl = /* @__PURE__ */ new Map();
2709
+ initialized = false;
2710
+ initPromise;
2711
+ keywordIndex = /* @__PURE__ */ new Map();
2712
+ scriptChunks = /* @__PURE__ */ new Map();
2713
+ CHUNK_SIZE = 100 * 1024;
2714
+ constructor(collector) {
2715
+ this.collector = collector;
2716
+ }
2717
+ async init() {
2718
+ if (this.initialized) return;
2719
+ if (this.initPromise) return this.initPromise;
2720
+ this.initPromise = this.doInit();
2721
+ try {
2722
+ return await this.initPromise;
2723
+ } finally {
2724
+ this.initPromise = void 0;
2725
+ }
2726
+ }
2727
+ async doInit() {
2728
+ const page = await this.collector.getActivePage();
2729
+ this.cdpSession = await page.createCDPSession();
2730
+ await this.cdpSession.send("Debugger.enable");
2731
+ this.cdpSession.on("Debugger.scriptParsed", (params) => {
2732
+ const scriptInfo = {
2733
+ scriptId: params.scriptId,
2734
+ url: params.url,
2735
+ startLine: params.startLine,
2736
+ startColumn: params.startColumn,
2737
+ endLine: params.endLine,
2738
+ endColumn: params.endColumn,
2739
+ sourceLength: params.length
2740
+ };
2741
+ this.scripts.set(params.scriptId, scriptInfo);
2742
+ if (params.url) {
2743
+ if (!this.scriptsByUrl.has(params.url)) this.scriptsByUrl.set(params.url, []);
2744
+ this.scriptsByUrl.get(params.url).push(scriptInfo);
2745
+ }
2746
+ logger.debug(`Script parsed: ${params.url || "inline"} (${params.scriptId})`);
2747
+ });
2748
+ this.initialized = true;
2749
+ logger.info("ScriptManager initialized");
2750
+ }
2751
+ async loadScriptSourceInternal(script) {
2752
+ if (script.source) return true;
2753
+ try {
2754
+ const { scriptSource } = await this.cdpSession.send("Debugger.getScriptSource", { scriptId: script.scriptId });
2755
+ script.source = scriptSource;
2756
+ script.sourceLength = scriptSource.length;
2757
+ this.buildKeywordIndex(script.scriptId, script.url, scriptSource);
2758
+ this.chunkScript(script.scriptId, scriptSource);
2759
+ return true;
2760
+ } catch (error) {
2761
+ logger.warn(`Failed to get source for script ${script.scriptId}:`, error);
2762
+ return false;
2763
+ }
2764
+ }
2765
+ async enable() {
2766
+ return this.init();
2767
+ }
2768
+ async getAllScripts(includeSource = false, maxScripts = 1e3) {
2769
+ if (!this.cdpSession) await this.init();
2770
+ const scripts = Array.from(this.scripts.values());
2771
+ if (scripts.length > maxScripts) logger.warn(`Found ${scripts.length} scripts, limiting to ${maxScripts}. Increase maxScripts parameter if needed.`);
2772
+ const limitedScripts = scripts.slice(0, maxScripts);
2773
+ if (includeSource) {
2774
+ logger.warn(`Loading source code for ${limitedScripts.length} scripts. This may use significant memory.`);
2775
+ let loadedCount = 0;
2776
+ let failedCount = 0;
2777
+ const missingScripts = limitedScripts.filter((script) => !script.source);
2778
+ for (let batchStart = 0; batchStart < missingScripts.length; batchStart += ScriptManager.SOURCE_LOAD_BATCH_SIZE) {
2779
+ const batch = missingScripts.slice(batchStart, batchStart + ScriptManager.SOURCE_LOAD_BATCH_SIZE);
2780
+ const settled = await Promise.allSettled(batch.map(async (script) => {
2781
+ if (await this.loadScriptSourceInternal(script)) {
2782
+ loadedCount++;
2783
+ if (loadedCount % 10 === 0) logger.debug(`Loaded ${loadedCount}/${limitedScripts.length} scripts...`);
2784
+ } else failedCount++;
2785
+ }));
2786
+ for (const result of settled) if (result.status === "rejected") failedCount++;
2787
+ await setImmediate();
2788
+ }
2789
+ logger.info(`getAllScripts: ${limitedScripts.length} scripts (loaded: ${loadedCount}, failed: ${failedCount})`);
2790
+ } else logger.info(`getAllScripts: ${limitedScripts.length} scripts (source not included)`);
2791
+ return limitedScripts;
2792
+ }
2793
+ async getScriptSource(scriptId, url) {
2794
+ if (!scriptId && !url) throw new Error("Either scriptId or url parameter must be provided");
2795
+ if (!this.cdpSession) await this.init();
2796
+ let targetScript;
2797
+ if (scriptId) targetScript = this.scripts.get(scriptId);
2798
+ else if (url) {
2799
+ const urlPattern = url.replace(/\*/g, ".*");
2800
+ const regex = new RegExp(urlPattern);
2801
+ for (const [scriptUrl, scripts] of this.scriptsByUrl.entries()) if (regex.test(scriptUrl)) {
2802
+ targetScript = scripts[0];
2803
+ break;
2804
+ }
2805
+ }
2806
+ if (!targetScript) {
2807
+ logger.warn(`Script not found: ${scriptId || url}`);
2808
+ return null;
2809
+ }
2810
+ if (!targetScript.source) {
2811
+ if (!await this.loadScriptSourceInternal(targetScript)) {
2812
+ logger.error(`Failed to get script source for ${targetScript.scriptId}`);
2813
+ return null;
2814
+ }
2815
+ }
2816
+ logger.info(`getScriptSource: ${targetScript.url || "inline"} (${targetScript.sourceLength} bytes)`);
2817
+ return targetScript;
2818
+ }
2819
+ async findScriptsByUrl(urlPattern) {
2820
+ if (!this.cdpSession) await this.init();
2821
+ const pattern = urlPattern.replace(/\*/g, ".*");
2822
+ const regex = new RegExp(pattern);
2823
+ const results = [];
2824
+ for (const [url, scripts] of this.scriptsByUrl.entries()) if (regex.test(url)) results.push(...scripts);
2825
+ logger.info(`findScriptsByUrl: ${urlPattern} - found ${results.length} scripts`);
2826
+ return results;
2827
+ }
2828
+ clearCache() {
2829
+ this.clear();
2830
+ }
2831
+ async searchInScripts(keyword, options = {}) {
2832
+ if (!this.cdpSession) await this.init();
2833
+ const { isRegex = false, caseSensitive = false, contextLines = 3, maxMatches = 100 } = options;
2834
+ const searchRegex = isRegex ? new RegExp(keyword, caseSensitive ? "g" : "gi") : new RegExp(keyword.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), caseSensitive ? "g" : "gi");
2835
+ const matches = [];
2836
+ const scripts = await this.getAllScripts(true, 500);
2837
+ for (const [scriptIndex, script] of scripts.entries()) {
2838
+ if (!script.source) continue;
2839
+ if (matches.length >= maxMatches) break;
2840
+ const lines = script.source.split("\n");
2841
+ for (let i = 0; i < lines.length; i++) {
2842
+ const line = lines[i];
2843
+ if (!line) continue;
2844
+ const lineMatches = Array.from(line.matchAll(searchRegex));
2845
+ for (const match of lineMatches) {
2846
+ if (matches.length >= maxMatches) break;
2847
+ const startLine = Math.max(0, i - contextLines);
2848
+ const endLine = Math.min(lines.length - 1, i + contextLines);
2849
+ let context = lines.slice(startLine, endLine + 1).join("\n");
2850
+ if (context.length > 2e3) {
2851
+ const matchIndex = match.index || 0;
2852
+ const snippetStart = Math.max(0, matchIndex - 100);
2853
+ const snippetEnd = Math.min(line.length, matchIndex + 100);
2854
+ context = (snippetStart > 0 ? "..." : "") + line.substring(snippetStart, snippetEnd) + (snippetEnd < line.length ? "..." : "");
2855
+ }
2856
+ matches.push({
2857
+ scriptId: script.scriptId,
2858
+ url: script.url || "inline",
2859
+ line: i + 1,
2860
+ column: match.index || 0,
2861
+ matchText: match[0],
2862
+ context
2863
+ });
2864
+ }
2865
+ if ((i + 1) % ScriptManager.SEARCH_LINE_YIELD_INTERVAL === 0) await setImmediate();
2866
+ }
2867
+ if ((scriptIndex + 1) % ScriptManager.SEARCH_SCRIPT_YIELD_INTERVAL === 0) await setImmediate();
2868
+ }
2869
+ logger.info(`searchInScripts: "${keyword}" - found ${matches.length} matches`);
2870
+ return {
2871
+ keyword,
2872
+ totalMatches: matches.length,
2873
+ matches
2874
+ };
2875
+ }
2876
+ async extractFunctionTree(scriptId, functionName, options = {}) {
2877
+ return extractFunctionTreeCore(this, scriptId, functionName, options);
2878
+ }
2879
+ clear() {
2880
+ this.scripts.clear();
2881
+ this.scriptsByUrl.clear();
2882
+ this.keywordIndex.clear();
2883
+ this.scriptChunks.clear();
2884
+ logger.info(" ScriptManager cleared - ready for new website");
2885
+ }
2886
+ async close() {
2887
+ this.initPromise = void 0;
2888
+ this.clear();
2889
+ if (this.cdpSession) {
2890
+ try {
2891
+ await this.cdpSession.send("Debugger.disable");
2892
+ await this.cdpSession.detach();
2893
+ logger.info("CDP session closed");
2894
+ } catch (error) {
2895
+ logger.warn("Failed to close CDP session:", error);
2896
+ }
2897
+ this.cdpSession = null;
2898
+ }
2899
+ this.initialized = false;
2900
+ logger.info(" ScriptManager closed");
2901
+ }
2902
+ getStats() {
2903
+ let totalChunks = 0;
2904
+ for (const chunks of this.scriptChunks.values()) totalChunks += chunks.length;
2905
+ return {
2906
+ totalScripts: this.scripts.size,
2907
+ totalUrls: this.scriptsByUrl.size,
2908
+ indexedKeywords: this.keywordIndex.size,
2909
+ totalChunks
2910
+ };
2911
+ }
2912
+ buildKeywordIndex(scriptId, url, content) {
2913
+ const lines = content.split("\n");
2914
+ const keywordRegex = /\b[a-zA-Z_$][a-zA-Z0-9_$]{2,}\b/g;
2915
+ for (let i = 0; i < lines.length; i++) {
2916
+ const line = lines[i];
2917
+ if (!line) continue;
2918
+ const matches = Array.from(line.matchAll(keywordRegex));
2919
+ for (const match of matches) {
2920
+ const keyword = match[0].toLowerCase();
2921
+ const startLine = Math.max(0, i - 3);
2922
+ const endLine = Math.min(lines.length - 1, i + 3);
2923
+ let context = lines.slice(startLine, endLine + 1).join("\n");
2924
+ if (context.length > 1e3) {
2925
+ const matchIndex = match.index || 0;
2926
+ const snippetStart = Math.max(0, matchIndex - 50);
2927
+ const snippetEnd = Math.min(line.length, matchIndex + 50);
2928
+ context = (snippetStart > 0 ? "..." : "") + line.substring(snippetStart, snippetEnd) + (snippetEnd < line.length ? "..." : "");
2929
+ }
2930
+ const entry = {
2931
+ scriptId,
2932
+ url,
2933
+ line: i + 1,
2934
+ column: match.index || 0,
2935
+ context
2936
+ };
2937
+ if (!this.keywordIndex.has(keyword)) this.keywordIndex.set(keyword, []);
2938
+ this.keywordIndex.get(keyword).push(entry);
2939
+ }
2940
+ }
2941
+ logger.debug(` Indexed ${this.keywordIndex.size} keywords for ${url}`);
2942
+ }
2943
+ chunkScript(scriptId, content) {
2944
+ const chunks = [];
2945
+ let offset = 0;
2946
+ let chunkIndex = 0;
2947
+ while (offset < content.length) {
2948
+ const chunk = content.substring(offset, offset + this.CHUNK_SIZE);
2949
+ chunks.push({
2950
+ scriptId,
2951
+ chunkIndex,
2952
+ content: chunk,
2953
+ size: chunk.length
2954
+ });
2955
+ offset += this.CHUNK_SIZE;
2956
+ chunkIndex++;
2957
+ }
2958
+ this.scriptChunks.set(scriptId, chunks);
2959
+ logger.debug(` Chunked script ${scriptId} into ${chunks.length} chunks`);
2960
+ }
2961
+ getScriptChunk(scriptId, chunkIndex) {
2962
+ const chunks = this.scriptChunks.get(scriptId);
2963
+ if (!chunks || chunkIndex >= chunks.length) return null;
2964
+ const chunk = chunks[chunkIndex];
2965
+ return chunk ? chunk.content : null;
2966
+ }
2967
+ async searchInScriptsEnhanced(keyword, options = {}) {
2968
+ const { isRegex = false, caseSensitive = false, maxMatches = 100 } = options;
2969
+ const searchTerm = caseSensitive ? keyword : keyword.toLowerCase();
2970
+ const matches = [];
2971
+ if (!isRegex) {
2972
+ for (const [indexedKeyword, entries] of this.keywordIndex.entries()) {
2973
+ if (indexedKeyword.includes(searchTerm)) for (const entry of entries) {
2974
+ matches.push({
2975
+ scriptId: entry.scriptId,
2976
+ url: entry.url,
2977
+ line: entry.line,
2978
+ column: entry.column,
2979
+ matchText: indexedKeyword,
2980
+ context: entry.context
2981
+ });
2982
+ if (matches.length >= maxMatches) break;
2983
+ }
2984
+ if (matches.length >= maxMatches) break;
2985
+ }
2986
+ logger.info(` Enhanced search (indexed) found ${matches.length} matches for "${keyword}"`);
2987
+ return {
2988
+ keyword,
2989
+ totalMatches: matches.length,
2990
+ matches,
2991
+ searchMethod: "indexed"
2992
+ };
2993
+ } else return {
2994
+ ...await this.searchInScripts(keyword, options),
2995
+ searchMethod: "regex"
2996
+ };
2997
+ }
2998
+ };
2999
+ //#endregion
3000
+ export { connectPlaywrightCdpFallback as i, DOMInspector as n, CodeCollector as r, ScriptManager as t };