openchrome-mcp 1.7.16 → 1.8.4

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 (169) hide show
  1. package/dist/cdp/client.d.ts +36 -1
  2. package/dist/cdp/client.d.ts.map +1 -1
  3. package/dist/cdp/client.js +212 -8
  4. package/dist/cdp/client.js.map +1 -1
  5. package/dist/cdp/tab-health-monitor.d.ts +63 -0
  6. package/dist/cdp/tab-health-monitor.d.ts.map +1 -0
  7. package/dist/cdp/tab-health-monitor.js +131 -0
  8. package/dist/cdp/tab-health-monitor.js.map +1 -0
  9. package/dist/chrome/launcher.d.ts +11 -0
  10. package/dist/chrome/launcher.d.ts.map +1 -1
  11. package/dist/chrome/launcher.js +17 -0
  12. package/dist/chrome/launcher.js.map +1 -1
  13. package/dist/chrome/pool.d.ts +17 -0
  14. package/dist/chrome/pool.d.ts.map +1 -1
  15. package/dist/chrome/pool.js +85 -2
  16. package/dist/chrome/pool.js.map +1 -1
  17. package/dist/chrome/process-watchdog.d.ts +63 -0
  18. package/dist/chrome/process-watchdog.d.ts.map +1 -0
  19. package/dist/chrome/process-watchdog.js +121 -0
  20. package/dist/chrome/process-watchdog.js.map +1 -0
  21. package/dist/cli/index.js +0 -0
  22. package/dist/config/defaults.d.ts +59 -0
  23. package/dist/config/defaults.d.ts.map +1 -1
  24. package/dist/config/defaults.js +62 -1
  25. package/dist/config/defaults.js.map +1 -1
  26. package/dist/config/tool-tiers.d.ts.map +1 -1
  27. package/dist/config/tool-tiers.js +3 -0
  28. package/dist/config/tool-tiers.js.map +1 -1
  29. package/dist/dom/dom-serializer.js +1 -1
  30. package/dist/dom/dom-serializer.js.map +1 -1
  31. package/dist/index.js +111 -0
  32. package/dist/index.js.map +1 -1
  33. package/dist/journal/task-journal.d.ts +79 -0
  34. package/dist/journal/task-journal.d.ts.map +1 -0
  35. package/dist/journal/task-journal.js +246 -0
  36. package/dist/journal/task-journal.js.map +1 -0
  37. package/dist/mcp-server.d.ts +3 -0
  38. package/dist/mcp-server.d.ts.map +1 -1
  39. package/dist/mcp-server.js +129 -7
  40. package/dist/mcp-server.js.map +1 -1
  41. package/dist/self-healing.d.ts +14 -0
  42. package/dist/self-healing.d.ts.map +1 -0
  43. package/dist/self-healing.js +17 -0
  44. package/dist/self-healing.js.map +1 -0
  45. package/dist/session-manager.d.ts +14 -3
  46. package/dist/session-manager.d.ts.map +1 -1
  47. package/dist/session-manager.js +98 -27
  48. package/dist/session-manager.js.map +1 -1
  49. package/dist/session-state-persistence.d.ts +92 -0
  50. package/dist/session-state-persistence.d.ts.map +1 -0
  51. package/dist/session-state-persistence.js +206 -0
  52. package/dist/session-state-persistence.js.map +1 -0
  53. package/dist/storage-state/storage-state-manager.d.ts +1 -1
  54. package/dist/storage-state/storage-state-manager.d.ts.map +1 -1
  55. package/dist/storage-state/storage-state-manager.js +40 -16
  56. package/dist/storage-state/storage-state-manager.js.map +1 -1
  57. package/dist/tools/click-element.d.ts.map +1 -1
  58. package/dist/tools/click-element.js +59 -2
  59. package/dist/tools/click-element.js.map +1 -1
  60. package/dist/tools/find.d.ts.map +1 -1
  61. package/dist/tools/find.js +27 -0
  62. package/dist/tools/find.js.map +1 -1
  63. package/dist/tools/index.d.ts.map +1 -1
  64. package/dist/tools/index.js +8 -0
  65. package/dist/tools/index.js.map +1 -1
  66. package/dist/tools/interact.d.ts.map +1 -1
  67. package/dist/tools/interact.js +93 -2
  68. package/dist/tools/interact.js.map +1 -1
  69. package/dist/tools/journal.d.ts +8 -0
  70. package/dist/tools/journal.d.ts.map +1 -0
  71. package/dist/tools/journal.js +112 -0
  72. package/dist/tools/journal.js.map +1 -0
  73. package/dist/tools/list-profiles.d.ts.map +1 -1
  74. package/dist/tools/list-profiles.js +2 -1
  75. package/dist/tools/list-profiles.js.map +1 -1
  76. package/dist/tools/navigate.d.ts.map +1 -1
  77. package/dist/tools/navigate.js +42 -7
  78. package/dist/tools/navigate.js.map +1 -1
  79. package/dist/tools/profile-status.d.ts.map +1 -1
  80. package/dist/tools/profile-status.js +18 -0
  81. package/dist/tools/profile-status.js.map +1 -1
  82. package/dist/tools/session-resume.d.ts +42 -0
  83. package/dist/tools/session-resume.d.ts.map +1 -0
  84. package/dist/tools/session-resume.js +248 -0
  85. package/dist/tools/session-resume.js.map +1 -0
  86. package/dist/tools/session-snapshot.d.ts +35 -0
  87. package/dist/tools/session-snapshot.d.ts.map +1 -0
  88. package/dist/tools/session-snapshot.js +232 -0
  89. package/dist/tools/session-snapshot.js.map +1 -0
  90. package/dist/tools/tabs-create.d.ts.map +1 -1
  91. package/dist/tools/tabs-create.js +13 -2
  92. package/dist/tools/tabs-create.js.map +1 -1
  93. package/dist/tools/wait-and-click.d.ts.map +1 -1
  94. package/dist/tools/wait-and-click.js +47 -4
  95. package/dist/tools/wait-and-click.js.map +1 -1
  96. package/dist/types/session.d.ts +2 -0
  97. package/dist/types/session.d.ts.map +1 -1
  98. package/dist/utils/ax-element-resolver.d.ts +96 -0
  99. package/dist/utils/ax-element-resolver.d.ts.map +1 -0
  100. package/dist/utils/ax-element-resolver.js +256 -0
  101. package/dist/utils/ax-element-resolver.js.map +1 -0
  102. package/dist/utils/element-discovery.d.ts.map +1 -1
  103. package/dist/utils/element-discovery.js +4 -0
  104. package/dist/utils/element-discovery.js.map +1 -1
  105. package/dist/utils/ralph/circuit-breaker.d.ts +99 -0
  106. package/dist/utils/ralph/circuit-breaker.d.ts.map +1 -0
  107. package/dist/utils/ralph/circuit-breaker.js +234 -0
  108. package/dist/utils/ralph/circuit-breaker.js.map +1 -0
  109. package/dist/utils/ralph/hitl-escalation.d.ts +51 -0
  110. package/dist/utils/ralph/hitl-escalation.d.ts.map +1 -0
  111. package/dist/utils/ralph/hitl-escalation.js +114 -0
  112. package/dist/utils/ralph/hitl-escalation.js.map +1 -0
  113. package/dist/utils/ralph/outcome-classifier.d.ts +44 -0
  114. package/dist/utils/ralph/outcome-classifier.d.ts.map +1 -0
  115. package/dist/utils/ralph/outcome-classifier.js +123 -0
  116. package/dist/utils/ralph/outcome-classifier.js.map +1 -0
  117. package/dist/utils/ralph/ralph-engine.d.ts +50 -0
  118. package/dist/utils/ralph/ralph-engine.d.ts.map +1 -0
  119. package/dist/utils/ralph/ralph-engine.js +341 -0
  120. package/dist/utils/ralph/ralph-engine.js.map +1 -0
  121. package/dist/utils/ralph/strategy-learner.d.ts +40 -0
  122. package/dist/utils/ralph/strategy-learner.d.ts.map +1 -0
  123. package/dist/utils/ralph/strategy-learner.js +80 -0
  124. package/dist/utils/ralph/strategy-learner.js.map +1 -0
  125. package/dist/utils/ralph/timeout-budget.d.ts +43 -0
  126. package/dist/utils/ralph/timeout-budget.d.ts.map +1 -0
  127. package/dist/utils/ralph/timeout-budget.js +80 -0
  128. package/dist/utils/ralph/timeout-budget.js.map +1 -0
  129. package/dist/watchdog/event-loop-monitor.d.ts +58 -0
  130. package/dist/watchdog/event-loop-monitor.d.ts.map +1 -0
  131. package/dist/watchdog/event-loop-monitor.js +85 -0
  132. package/dist/watchdog/event-loop-monitor.js.map +1 -0
  133. package/dist/watchdog/health-endpoint.d.ts +44 -0
  134. package/dist/watchdog/health-endpoint.d.ts.map +1 -0
  135. package/dist/watchdog/health-endpoint.js +119 -0
  136. package/dist/watchdog/health-endpoint.js.map +1 -0
  137. package/package.json +3 -2
  138. package/dist/chrome/sqlite-cookie-copy.d.ts +0 -46
  139. package/dist/chrome/sqlite-cookie-copy.d.ts.map +0 -1
  140. package/dist/chrome/sqlite-cookie-copy.js +0 -151
  141. package/dist/chrome/sqlite-cookie-copy.js.map +0 -1
  142. package/dist/config/config-recovery.d.ts +0 -69
  143. package/dist/config/config-recovery.d.ts.map +0 -1
  144. package/dist/config/config-recovery.js +0 -302
  145. package/dist/config/config-recovery.js.map +0 -1
  146. package/dist/config/session-isolator.d.ts +0 -76
  147. package/dist/config/session-isolator.d.ts.map +0 -1
  148. package/dist/config/session-isolator.js +0 -268
  149. package/dist/config/session-isolator.js.map +0 -1
  150. package/dist/tools/selector-query.d.ts +0 -6
  151. package/dist/tools/selector-query.d.ts.map +0 -1
  152. package/dist/tools/selector-query.js +0 -214
  153. package/dist/tools/selector-query.js.map +0 -1
  154. package/dist/tools/worker-create.d.ts +0 -7
  155. package/dist/tools/worker-create.d.ts.map +0 -1
  156. package/dist/tools/worker-create.js +0 -62
  157. package/dist/tools/worker-create.js.map +0 -1
  158. package/dist/tools/worker-delete.d.ts +0 -6
  159. package/dist/tools/worker-delete.d.ts.map +0 -1
  160. package/dist/tools/worker-delete.js +0 -80
  161. package/dist/tools/worker-delete.js.map +0 -1
  162. package/dist/tools/worker-list.d.ts +0 -6
  163. package/dist/tools/worker-list.d.ts.map +0 -1
  164. package/dist/tools/worker-list.js +0 -67
  165. package/dist/tools/worker-list.js.map +0 -1
  166. package/dist/tools/xpath-query.d.ts +0 -6
  167. package/dist/tools/xpath-query.d.ts.map +0 -1
  168. package/dist/tools/xpath-query.js +0 -225
  169. package/dist/tools/xpath-query.js.map +0 -1
@@ -30,6 +30,7 @@ export declare class CDPClient {
30
30
  private targetDestroyedListeners;
31
31
  private reconnectAttempts;
32
32
  private consecutiveHeartbeatFailures;
33
+ private consecutiveHeartbeatSuccesses;
33
34
  private checkConnectionInFlight;
34
35
  private autoLaunch;
35
36
  private cookieSourceCache;
@@ -40,6 +41,12 @@ export declare class CDPClient {
40
41
  private pendingConnect;
41
42
  /** Timestamp of last successful connection verification (heartbeat or active probe). */
42
43
  private lastVerifiedAt;
44
+ private heartbeatMode;
45
+ private lastCommandAt;
46
+ private heartbeatModeTimer;
47
+ private reconnectCount;
48
+ private pingLatencies;
49
+ private static readonly MAX_PING_SAMPLES;
43
50
  private static readonly COOKIE_CACHE_TTL;
44
51
  constructor(options?: CDPClientOptions);
45
52
  /**
@@ -78,6 +85,33 @@ export declare class CDPClient {
78
85
  * Stop heartbeat monitoring
79
86
  */
80
87
  private stopHeartbeat;
88
+ /**
89
+ * Set heartbeat mode. Restarts the heartbeat timer with the new interval.
90
+ */
91
+ setHeartbeatMode(mode: 'idle' | 'active' | 'heavy' | 'recovery'): void;
92
+ /**
93
+ * Get effective heartbeat interval based on current mode.
94
+ */
95
+ private getEffectiveHeartbeatInterval;
96
+ /**
97
+ * Record that a command was executed (for idle detection).
98
+ */
99
+ recordCommandActivity(): void;
100
+ /**
101
+ * Get current heartbeat mode.
102
+ */
103
+ getHeartbeatMode(): 'idle' | 'active' | 'heavy' | 'recovery';
104
+ /**
105
+ * Get connection health metrics.
106
+ */
107
+ getConnectionMetrics(): {
108
+ msSinceLastVerified: number;
109
+ reconnectCount: number;
110
+ avgPingLatencyMs: number;
111
+ heartbeatMode: string;
112
+ consecutiveSuccesses: number;
113
+ lastVerifiedAt: number;
114
+ };
81
115
  /**
82
116
  * Check connection health.
83
117
  * Sends an active CDP probe (Browser.getVersion) to detect half-open WebSocket
@@ -206,7 +240,8 @@ export declare class CDPClient {
206
240
  */
207
241
  getCDPSession(page: Page): Promise<CDPSession>;
208
242
  /**
209
- * Execute CDP command on a page
243
+ * Execute CDP command on a page.
244
+ * Wrapped with per-call timeout to prevent hung renderers from blocking indefinitely.
210
245
  */
211
246
  send<T = unknown>(page: Page, method: string, params?: Record<string, unknown>): Promise<T>;
212
247
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/cdp/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAkB,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAiC9F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAE3F,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,aAAa,GAAG,kBAAkB,CAAC;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAGD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,wBAAwB,CAAmD;IACnF,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,4BAA4B,CAAK;IACzC,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,iBAAiB,CAAmE;IAC5F,OAAO,CAAC,eAAe,CAAyE;IAChG,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,mBAAmB,CAAkD;IAC7E,wFAAwF;IACxF,OAAO,CAAC,cAAc,CAA8B;IACpD,wFAAwF;IACxF,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;gBAEtC,OAAO,GAAE,gBAAqB;IAU1C;;OAEG;IACH,kBAAkB,IAAI,eAAe;IAIrC;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAIvE;;OAEG;IACH,wBAAwB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAO1E;;OAEG;IACH,0BAA0B,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI;IAInF;;OAEG;IACH,6BAA6B,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI;IAOtF;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAsBzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;IACH,OAAO,CAAC,cAAc;IA4BtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;;;OAIG;YACW,eAAe;IAiD7B;;OAEG;YACW,gBAAgB;IAwE9B;;OAEG;YACW,eAAe;IAyI7B;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA4D9B;;;;;;;;OAQG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA0CrC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAmBjC;;OAEG;IACH,UAAU,IAAI,OAAO;IAQrB,MAAM,CAAC,QAAQ,CAAC,gBAAgB;;;MAAoB;IAEpD;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,cAAc,CAAC;IAOrD;;OAEG;IACG,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjE;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;;;;;;;OAQG;IACG,6BAA6B,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA0BlF;;;;OAIG;YACW,gCAAgC;IA6G9C;;;;OAIG;IACG,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IA6GhF;;;;;OAKG;IACG,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,EAAE,gBAAgB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgF1G;;;;;;;;;OASG;IACG,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAa,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IA4I1G;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAmI7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAKjC;;;;OAIG;IACG,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAsB7C;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IA+B/D;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IAapD;;OAEG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,CAAC,CAAC;IAKb;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAItB;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAShD;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS;CAG1E;AAKD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAKlE;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAqC;IAEpD;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS;IAShE;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIxC;;OAEG;IACH,MAAM,IAAI,SAAS,EAAE;IAIrB;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CASrC;AAKD,wBAAgB,mBAAmB,IAAI,gBAAgB,CAKtD"}
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/cdp/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAkB,EAAE,OAAO,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAsC9F,MAAM,WAAW,gBAAgB;IAC/B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,oEAAoE;IACpE,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED,MAAM,MAAM,eAAe,GAAG,cAAc,GAAG,YAAY,GAAG,WAAW,GAAG,cAAc,CAAC;AAE3F,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,GAAG,cAAc,GAAG,cAAc,GAAG,aAAa,GAAG,kBAAkB,CAAC;IACzF,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAcD,qBAAa,SAAS;IACpB,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,QAAQ,CAAsC;IACtD,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,oBAAoB,CAAS;IACrC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,mBAAmB,CAAS;IACpC,OAAO,CAAC,cAAc,CAA+B;IACrD,OAAO,CAAC,eAAe,CAAmC;IAC1D,OAAO,CAAC,cAAc,CAA4C;IAClE,OAAO,CAAC,wBAAwB,CAAmD;IACnF,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,4BAA4B,CAAK;IACzC,OAAO,CAAC,6BAA6B,CAAK;IAC1C,OAAO,CAAC,uBAAuB,CAAS;IACxC,OAAO,CAAC,UAAU,CAAU;IAC5B,OAAO,CAAC,iBAAiB,CAAmE;IAC5F,OAAO,CAAC,eAAe,CAAyE;IAChG,OAAO,CAAC,aAAa,CAAgC;IACrD,OAAO,CAAC,mBAAmB,CAAkD;IAC7E,wFAAwF;IACxF,OAAO,CAAC,cAAc,CAA8B;IACpD,wFAAwF;IACxF,OAAO,CAAC,cAAc,CAAK;IAG3B,OAAO,CAAC,aAAa,CAAsD;IAC3E,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,kBAAkB,CAA+B;IAGzD,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAM;IAE9C,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,gBAAgB,CAAU;gBAEtC,OAAO,GAAE,gBAAqB;IAU1C;;OAEG;IACH,kBAAkB,IAAI,eAAe;IAIrC;;OAEG;IACH,qBAAqB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAIvE;;OAEG;IACH,wBAAwB,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,eAAe,KAAK,IAAI,GAAG,IAAI;IAO1E;;OAEG;IACH,0BAA0B,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI;IAInF;;OAEG;IACH,6BAA6B,CAAC,QAAQ,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,IAAI,KAAK,IAAI,GAAG,IAAI;IAOtF;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAsBzB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAU3B;;OAEG;IACH,OAAO,CAAC,cAAc;IAoCtB;;OAEG;IACH,OAAO,CAAC,aAAa;IAOrB;;OAEG;IACH,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU,GAAG,IAAI;IA2BtE;;OAEG;IACH,OAAO,CAAC,6BAA6B;IASrC;;OAEG;IACH,qBAAqB,IAAI,IAAI;IAO7B;;OAEG;IACH,gBAAgB,IAAI,MAAM,GAAG,QAAQ,GAAG,OAAO,GAAG,UAAU;IAI5D;;OAEG;IACH,oBAAoB,IAAI;QACtB,mBAAmB,EAAE,MAAM,CAAC;QAC5B,cAAc,EAAE,MAAM,CAAC;QACvB,gBAAgB,EAAE,MAAM,CAAC;QACzB,aAAa,EAAE,MAAM,CAAC;QACtB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,cAAc,EAAE,MAAM,CAAC;KACxB;IAeD;;;;OAIG;YACW,eAAe;IAyD7B;;OAEG;YACW,gBAAgB;IA2F9B;;OAEG;YACW,eAAe;IAyI7B;;;;;OAKG;IACG,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC;IA4D9B;;;;;;;;OAQG;IACG,cAAc,IAAI,OAAO,CAAC,IAAI,CAAC;IA8CrC;;OAEG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAwBjC;;OAEG;IACH,UAAU,IAAI,OAAO;IAQrB,MAAM,CAAC,QAAQ,CAAC,gBAAgB;;;MAAoB;IAEpD;;;OAGG;IACG,oBAAoB,IAAI,OAAO,CAAC,cAAc,CAAC;IAOrD;;OAEG;IACG,mBAAmB,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAUjE;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;;OAGG;IACH,OAAO,CAAC,gBAAgB;IAqCxB;;;;;;;;OAQG;IACG,6BAA6B,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC;IA0BlF;;;;OAIG;YACW,gCAAgC;IA6G9C;;;;OAIG;IACG,iBAAiB,CAAC,cAAc,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IA6GhF;;;;;OAKG;IACG,UAAU,CAAC,GAAG,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,GAAG,IAAI,EAAE,gBAAgB,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAgF1G;;;;;;;;;OASG;IACG,mBAAmB,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAa,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,IAAI,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAwL1G;;;OAGG;IACH,OAAO,CAAC,qBAAqB;IAwK7B;;OAEG;IACG,QAAQ,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;IAKjC;;;;OAIG;IACG,oBAAoB,IAAI,OAAO,CAAC,MAAM,CAAC;IAsB7C;;OAEG;IACG,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,GAAG,IAAI,CAAC;IA+B/D;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC;IAapD;;;OAGG;IACG,IAAI,CAAC,CAAC,GAAG,OAAO,EACpB,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC/B,OAAO,CAAC,CAAC,CAAC;IASb;;OAEG;IACH,UAAU,IAAI,MAAM,EAAE;IAItB;;OAEG;IACH,UAAU,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAIhD;;OAEG;IACG,SAAS,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAS1C;;OAEG;IACG,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAShD;;OAEG;IACH,WAAW,IAAI,OAAO;IAItB;;OAEG;IACH,OAAO,IAAI,MAAM;IAIjB;;OAEG;IACH,MAAM,CAAC,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS;CAG1E;AAKD,wBAAgB,YAAY,CAAC,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS,CAKlE;AAED;;GAEG;AACH,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,OAAO,CAAqC;IAEpD;;OAEG;IACH,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,gBAAgB,GAAG,SAAS;IAShE;;OAEG;IACH,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS;IAIxC;;OAEG;IACH,MAAM,IAAI,SAAS,EAAE;IAIrB;;OAEG;IACG,aAAa,IAAI,OAAO,CAAC,IAAI,CAAC;CASrC;AAKD,wBAAgB,mBAAmB,IAAI,gBAAgB,CAKtD"}
@@ -49,6 +49,18 @@ const smart_goto_1 = require("../utils/smart-goto");
49
49
  const puppeteer_helpers_1 = require("../utils/puppeteer-helpers");
50
50
  const ref_id_manager_1 = require("../utils/ref-id-manager");
51
51
  const defaults_1 = require("../config/defaults");
52
+ const with_timeout_1 = require("../utils/with-timeout");
53
+ function parseEnvInt(name, fallback) {
54
+ const raw = process.env[name];
55
+ if (raw === undefined)
56
+ return fallback;
57
+ const parsed = Number(raw);
58
+ if (!Number.isFinite(parsed) || parsed < 0) {
59
+ console.error(`[CDPClient] Invalid value for ${name}="${raw}", using default ${fallback}`);
60
+ return fallback;
61
+ }
62
+ return parsed;
63
+ }
52
64
  class CDPClient {
53
65
  browser = null;
54
66
  sessions = new Map();
@@ -62,6 +74,7 @@ class CDPClient {
62
74
  targetDestroyedListeners = [];
63
75
  reconnectAttempts = 0;
64
76
  consecutiveHeartbeatFailures = 0;
77
+ consecutiveHeartbeatSuccesses = 0;
65
78
  checkConnectionInFlight = false;
66
79
  autoLaunch;
67
80
  cookieSourceCache = new Map();
@@ -72,13 +85,21 @@ class CDPClient {
72
85
  pendingConnect = null;
73
86
  /** Timestamp of last successful connection verification (heartbeat or active probe). */
74
87
  lastVerifiedAt = 0;
88
+ // Adaptive heartbeat state
89
+ heartbeatMode = 'active';
90
+ lastCommandAt = 0;
91
+ heartbeatModeTimer = null;
92
+ // Connection health metrics
93
+ reconnectCount = 0;
94
+ pingLatencies = []; // rolling window
95
+ static MAX_PING_SAMPLES = 60; // ~5 min at 5s interval
75
96
  static COOKIE_CACHE_TTL = 300000; // 5 minutes
76
97
  constructor(options = {}) {
77
98
  const globalConfig = (0, global_1.getGlobalConfig)();
78
99
  this.port = options.port || globalConfig.port;
79
- this.maxReconnectAttempts = options.maxReconnectAttempts || 3;
80
- this.reconnectDelayMs = options.reconnectDelayMs || 1000;
81
- this.heartbeatIntervalMs = options.heartbeatIntervalMs || 5000;
100
+ this.maxReconnectAttempts = options.maxReconnectAttempts ?? parseEnvInt('OPENCHROME_MAX_RECONNECT_ATTEMPTS', defaults_1.DEFAULT_MAX_RECONNECT_ATTEMPTS);
101
+ this.reconnectDelayMs = options.reconnectDelayMs ?? parseEnvInt('OPENCHROME_RECONNECT_DELAY_MS', defaults_1.DEFAULT_RECONNECT_DELAY_MS);
102
+ this.heartbeatIntervalMs = options.heartbeatIntervalMs ?? parseEnvInt('OPENCHROME_HEARTBEAT_INTERVAL_MS', defaults_1.DEFAULT_HEARTBEAT_INTERVAL_MS);
82
103
  // Use explicit option if provided, otherwise use global config
83
104
  this.autoLaunch = options.autoLaunch !== undefined ? options.autoLaunch : globalConfig.autoLaunch;
84
105
  }
@@ -181,8 +202,15 @@ class CDPClient {
181
202
  });
182
203
  return;
183
204
  }
205
+ // Check for idle transition: no commands for 5 minutes → switch to idle mode
206
+ if ((this.heartbeatMode === 'active' || this.heartbeatMode === 'heavy')
207
+ && this.lastCommandAt > 0
208
+ && now - this.lastCommandAt > 300000) {
209
+ this.setHeartbeatMode('idle');
210
+ return; // setHeartbeatMode restarts the timer with new interval
211
+ }
184
212
  this.checkConnection();
185
- }, this.heartbeatIntervalMs);
213
+ }, this.getEffectiveHeartbeatInterval());
186
214
  }
187
215
  /**
188
216
  * Stop heartbeat monitoring
@@ -193,6 +221,76 @@ class CDPClient {
193
221
  this.heartbeatTimer = null;
194
222
  }
195
223
  }
224
+ /**
225
+ * Set heartbeat mode. Restarts the heartbeat timer with the new interval.
226
+ */
227
+ setHeartbeatMode(mode) {
228
+ if (this.heartbeatMode === mode)
229
+ return;
230
+ const oldMode = this.heartbeatMode;
231
+ this.heartbeatMode = mode;
232
+ console.error(`[CDPClient] Heartbeat mode: ${oldMode} → ${mode} (interval: ${this.getEffectiveHeartbeatInterval()}ms)`);
233
+ // Restart heartbeat with new interval
234
+ if (this.heartbeatTimer) {
235
+ this.startHeartbeat();
236
+ }
237
+ // Auto-transition from recovery to active after 30s
238
+ if (this.heartbeatModeTimer) {
239
+ clearTimeout(this.heartbeatModeTimer);
240
+ this.heartbeatModeTimer = null;
241
+ }
242
+ if (mode === 'recovery') {
243
+ this.heartbeatModeTimer = setTimeout(() => {
244
+ this.heartbeatModeTimer = null;
245
+ if (this.heartbeatMode === 'recovery') {
246
+ this.setHeartbeatMode('active');
247
+ }
248
+ }, 30000);
249
+ this.heartbeatModeTimer.unref();
250
+ }
251
+ }
252
+ /**
253
+ * Get effective heartbeat interval based on current mode.
254
+ */
255
+ getEffectiveHeartbeatInterval() {
256
+ switch (this.heartbeatMode) {
257
+ case 'idle': return Math.max(this.heartbeatIntervalMs * 3, 15000); // 3x base or 15s min
258
+ case 'active': return this.heartbeatIntervalMs; // default (5s)
259
+ case 'heavy': return Math.max(Math.floor(this.heartbeatIntervalMs / 2), 2000); // half or 2s min
260
+ case 'recovery': return 1000; // 1s fixed during recovery
261
+ }
262
+ }
263
+ /**
264
+ * Record that a command was executed (for idle detection).
265
+ */
266
+ recordCommandActivity() {
267
+ this.lastCommandAt = Date.now();
268
+ if (this.heartbeatMode === 'idle') {
269
+ this.setHeartbeatMode('active');
270
+ }
271
+ }
272
+ /**
273
+ * Get current heartbeat mode.
274
+ */
275
+ getHeartbeatMode() {
276
+ return this.heartbeatMode;
277
+ }
278
+ /**
279
+ * Get connection health metrics.
280
+ */
281
+ getConnectionMetrics() {
282
+ const avgLatency = this.pingLatencies.length > 0
283
+ ? Math.round(this.pingLatencies.reduce((a, b) => a + b, 0) / this.pingLatencies.length)
284
+ : 0;
285
+ return {
286
+ msSinceLastVerified: this.lastVerifiedAt > 0 ? Date.now() - this.lastVerifiedAt : 0,
287
+ reconnectCount: this.reconnectCount,
288
+ avgPingLatencyMs: avgLatency,
289
+ heartbeatMode: this.heartbeatMode,
290
+ consecutiveSuccesses: this.consecutiveHeartbeatSuccesses,
291
+ lastVerifiedAt: this.lastVerifiedAt,
292
+ };
293
+ }
196
294
  /**
197
295
  * Check connection health.
198
296
  * Sends an active CDP probe (Browser.getVersion) to detect half-open WebSocket
@@ -215,6 +313,7 @@ class CDPClient {
215
313
  // Active probe: round-trip CDP command to detect dead WebSocket connections.
216
314
  // browser.isConnected() only checks a local flag — half-open TCP connections
217
315
  // (macOS sleep/wake, Chrome crash) pass the flag check but hang on real commands.
316
+ const pingStart = Date.now();
218
317
  let pingTid;
219
318
  await Promise.race([
220
319
  this.browser.version().finally(() => clearTimeout(pingTid)),
@@ -223,10 +322,17 @@ class CDPClient {
223
322
  }),
224
323
  ]);
225
324
  this.lastVerifiedAt = Date.now();
325
+ const pingLatency = Date.now() - pingStart;
326
+ this.pingLatencies.push(pingLatency);
327
+ if (this.pingLatencies.length > CDPClient.MAX_PING_SAMPLES) {
328
+ this.pingLatencies.shift();
329
+ }
330
+ this.consecutiveHeartbeatSuccesses++;
226
331
  this.consecutiveHeartbeatFailures = 0;
227
332
  return true;
228
333
  }
229
334
  catch (error) {
335
+ this.consecutiveHeartbeatSuccesses = 0;
230
336
  this.consecutiveHeartbeatFailures++;
231
337
  if (this.consecutiveHeartbeatFailures < 2) {
232
338
  // First failure: warn but don't disconnect. Chrome may be under heavy load.
@@ -256,6 +362,11 @@ class CDPClient {
256
362
  type: 'disconnected',
257
363
  timestamp: Date.now(),
258
364
  });
365
+ // Clear heartbeat mode timer to prevent 30s recovery timer from leaking
366
+ if (this.heartbeatModeTimer) {
367
+ clearTimeout(this.heartbeatModeTimer);
368
+ this.heartbeatModeTimer = null;
369
+ }
259
370
  // Clear existing sessions and stale state
260
371
  this.sessions.clear();
261
372
  this.targetIdIndex.clear();
@@ -275,6 +386,10 @@ class CDPClient {
275
386
  while (this.reconnectAttempts < this.maxReconnectAttempts) {
276
387
  this.reconnectAttempts++;
277
388
  console.error(`[CDPClient] Reconnect attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts}...`);
389
+ // Invalidate launcher cache at start of each attempt so ensureChrome()
390
+ // re-probes Chrome's HTTP endpoint to discover the new WebSocket UUID
391
+ // after a relaunch by the process watchdog
392
+ (0, launcher_1.getChromeLauncher)(this.port).invalidateInstance();
278
393
  this.emitConnectionEvent({
279
394
  type: 'reconnecting',
280
395
  timestamp: Date.now(),
@@ -284,6 +399,8 @@ class CDPClient {
284
399
  await this.connectInternal({ autoLaunch: false });
285
400
  console.error('[CDPClient] Reconnection successful');
286
401
  this.reconnectAttempts = 0;
402
+ this.reconnectCount++;
403
+ this.setHeartbeatMode('recovery');
287
404
  this.emitConnectionEvent({
288
405
  type: 'reconnected',
289
406
  timestamp: Date.now(),
@@ -293,7 +410,10 @@ class CDPClient {
293
410
  catch (error) {
294
411
  console.error(`[CDPClient] Reconnect attempt ${this.reconnectAttempts} failed:`, error);
295
412
  if (this.reconnectAttempts < this.maxReconnectAttempts) {
296
- await new Promise(resolve => setTimeout(resolve, this.reconnectDelayMs));
413
+ // Exponential backoff with jitter: baseDelay * 2^(attempt-1) + random(0..baseDelay/2)
414
+ const backoffDelay = Math.min(this.reconnectDelayMs * Math.pow(2, this.reconnectAttempts - 1) + Math.floor(Math.random() * this.reconnectDelayMs / 2), 30000);
415
+ console.error(`[CDPClient] Waiting ${backoffDelay}ms before next attempt (exponential backoff)...`);
416
+ await new Promise(resolve => setTimeout(resolve, backoffDelay));
297
417
  }
298
418
  }
299
419
  }
@@ -514,6 +634,10 @@ class CDPClient {
514
634
  // Invalidate any in-flight connect() — we're replacing the connection entirely
515
635
  this.pendingConnect = null;
516
636
  this.stopHeartbeat();
637
+ if (this.heartbeatModeTimer) {
638
+ clearTimeout(this.heartbeatModeTimer);
639
+ this.heartbeatModeTimer = null;
640
+ }
517
641
  if (this.browser) {
518
642
  try {
519
643
  this.browser.removeAllListeners('disconnected');
@@ -556,6 +680,10 @@ class CDPClient {
556
680
  */
557
681
  async disconnect() {
558
682
  this.stopHeartbeat();
683
+ if (this.heartbeatModeTimer) {
684
+ clearTimeout(this.heartbeatModeTimer);
685
+ this.heartbeatModeTimer = null;
686
+ }
559
687
  if (this.browser) {
560
688
  try {
561
689
  this.browser.removeAllListeners('disconnected');
@@ -980,7 +1108,7 @@ class CDPClient {
980
1108
  * @param settleMs Milliseconds to wait before attaching CDP (default 5000, range 1000-30000)
981
1109
  * @returns The Puppeteer Page and its targetId
982
1110
  */
983
- async createTargetStealth(url, settleMs = 5000) {
1111
+ async createTargetStealth(url, settleMs = 8000) {
984
1112
  const browser = this.getBrowser();
985
1113
  // Step 1: Create target via CDP Target.createTarget.
986
1114
  // Puppeteer's ChromeTargetManager uses Target.setAutoAttach with { exclude: true }
@@ -998,6 +1126,16 @@ class CDPClient {
998
1126
  throw new Error(`Stealth navigation: failed to create target: ${createErr instanceof Error ? createErr.message : String(createErr)}`);
999
1127
  }
1000
1128
  console.error(`[CDPClient] Stealth tab created: ${targetId}, settling for ${settleMs}ms`);
1129
+ // Warn if headless — Turnstile detection is nearly guaranteed in headless mode
1130
+ try {
1131
+ const version = await browser.version();
1132
+ if (version.toLowerCase().includes('headless')) {
1133
+ console.error('[CDPClient] WARNING: Stealth mode in headless Chrome is unlikely to bypass Turnstile. Use headed Chrome (--visible) for anti-bot pages.');
1134
+ }
1135
+ }
1136
+ catch {
1137
+ // Version check failed — continue
1138
+ }
1001
1139
  // Step 2: Wait for the page to load without CDP observation (Turnstile runs here)
1002
1140
  await new Promise(resolve => setTimeout(resolve, settleMs));
1003
1141
  // Step 3: Attach to the target via CDP to bring it into Puppeteer's attached set
@@ -1101,6 +1239,37 @@ class CDPClient {
1101
1239
  configurable: true,
1102
1240
  });
1103
1241
  }
1242
+ // 6. window dimensions — headless Chrome returns 0
1243
+ if (window.outerWidth === 0) {
1244
+ Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth, configurable: true });
1245
+ }
1246
+ if (window.outerHeight === 0) {
1247
+ Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight + 85, configurable: true });
1248
+ }
1249
+ // 7. navigator.mimeTypes — headless has 0 mimeTypes
1250
+ if (navigator.mimeTypes.length === 0) {
1251
+ Object.defineProperty(navigator, 'mimeTypes', {
1252
+ get: () => {
1253
+ const mt = typeof MimeTypeArray !== 'undefined' ? Object.create(MimeTypeArray.prototype) : [];
1254
+ mt[0] = { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' };
1255
+ Object.defineProperty(mt, 'length', { value: 1 });
1256
+ mt.item = (i) => mt[i] || null;
1257
+ mt.namedItem = (name) => name === 'application/pdf' ? mt[0] : null;
1258
+ return mt;
1259
+ },
1260
+ configurable: true,
1261
+ });
1262
+ }
1263
+ // 8. chrome.app and chrome.loadTimes stubs
1264
+ if (window.chrome) {
1265
+ const c = window.chrome;
1266
+ if (!c.app) {
1267
+ c.app = { isInstalled: false, getDetails: () => null, getIsInstalled: () => false, installState: () => 'disabled' };
1268
+ }
1269
+ if (!c.loadTimes) {
1270
+ c.loadTimes = () => ({});
1271
+ }
1272
+ }
1104
1273
  }).catch(() => { });
1105
1274
  console.error(`[CDPClient] Stealth tab ${targetId} attached after settle period`);
1106
1275
  return { page, targetId };
@@ -1216,6 +1385,40 @@ class CDPClient {
1216
1385
  });
1217
1386
  }
1218
1387
  }).catch(() => { });
1388
+ // Defense 4: window dimensions + chrome stubs (anti-headless, #361)
1389
+ page.evaluateOnNewDocument(() => {
1390
+ // outerWidth/outerHeight — headless Chrome returns 0
1391
+ if (window.outerWidth === 0) {
1392
+ Object.defineProperty(window, 'outerWidth', { get: () => window.innerWidth, configurable: true });
1393
+ }
1394
+ if (window.outerHeight === 0) {
1395
+ Object.defineProperty(window, 'outerHeight', { get: () => window.innerHeight + 85, configurable: true });
1396
+ }
1397
+ // navigator.mimeTypes — headless has 0 mimeTypes
1398
+ if (navigator.mimeTypes.length === 0) {
1399
+ Object.defineProperty(navigator, 'mimeTypes', {
1400
+ get: () => {
1401
+ const mt = typeof MimeTypeArray !== 'undefined' ? Object.create(MimeTypeArray.prototype) : [];
1402
+ mt[0] = { type: 'application/pdf', suffixes: 'pdf', description: 'Portable Document Format' };
1403
+ Object.defineProperty(mt, 'length', { value: 1 });
1404
+ mt.item = (i) => mt[i] || null;
1405
+ mt.namedItem = (name) => name === 'application/pdf' ? mt[0] : null;
1406
+ return mt;
1407
+ },
1408
+ configurable: true,
1409
+ });
1410
+ }
1411
+ // chrome.app and chrome.loadTimes stubs
1412
+ if (window.chrome) {
1413
+ const c = window.chrome;
1414
+ if (!c.app) {
1415
+ c.app = { isInstalled: false, getDetails: () => null, getIsInstalled: () => false, installState: () => 'disabled' };
1416
+ }
1417
+ if (!c.loadTimes) {
1418
+ c.loadTimes = () => ({});
1419
+ }
1420
+ }
1421
+ }).catch(() => { });
1219
1422
  // Deny file downloads by default — Content-Disposition: attachment
1220
1423
  // responses block the navigation promise indefinitely.
1221
1424
  this.send(page, 'Page.setDownloadBehavior', { behavior: 'deny' }).catch(() => { });
@@ -1310,11 +1513,12 @@ class CDPClient {
1310
1513
  return session;
1311
1514
  }
1312
1515
  /**
1313
- * Execute CDP command on a page
1516
+ * Execute CDP command on a page.
1517
+ * Wrapped with per-call timeout to prevent hung renderers from blocking indefinitely.
1314
1518
  */
1315
1519
  async send(page, method, params) {
1316
1520
  const session = await this.getCDPSession(page);
1317
- return session.send(method, params);
1521
+ return (0, with_timeout_1.withTimeout)(session.send(method, params), defaults_1.DEFAULT_CDP_SEND_TIMEOUT_MS, `CDP ${method}`);
1318
1522
  }
1319
1523
  /**
1320
1524
  * Get all targets