browser-use 0.2.0 → 0.3.0

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 (259) hide show
  1. package/README.md +295 -686
  2. package/dist/actor/element.d.ts +19 -0
  3. package/dist/actor/element.js +46 -0
  4. package/dist/actor/index.d.ts +4 -0
  5. package/dist/actor/index.js +4 -0
  6. package/dist/actor/mouse.d.ts +19 -0
  7. package/dist/actor/mouse.js +39 -0
  8. package/dist/actor/page.d.ts +29 -0
  9. package/dist/actor/page.js +88 -0
  10. package/dist/actor/utils.d.ts +4 -0
  11. package/dist/actor/utils.js +35 -0
  12. package/dist/agent/cloud-events.d.ts +18 -0
  13. package/dist/agent/cloud-events.js +65 -2
  14. package/dist/agent/gif.d.ts +1 -0
  15. package/dist/agent/gif.js +24 -2
  16. package/dist/agent/judge.d.ts +17 -0
  17. package/dist/agent/judge.js +197 -0
  18. package/dist/agent/message-manager/service.d.ts +12 -4
  19. package/dist/agent/message-manager/service.js +205 -39
  20. package/dist/agent/message-manager/utils.js +0 -1
  21. package/dist/agent/message-manager/views.d.ts +4 -0
  22. package/dist/agent/message-manager/views.js +11 -7
  23. package/dist/agent/prompts.d.ts +24 -3
  24. package/dist/agent/prompts.js +274 -59
  25. package/dist/agent/service.d.ts +99 -41
  26. package/dist/agent/service.js +2266 -472
  27. package/dist/agent/variable-detector.d.ts +12 -0
  28. package/dist/agent/variable-detector.js +211 -0
  29. package/dist/agent/views.d.ts +237 -18
  30. package/dist/agent/views.js +446 -33
  31. package/dist/browser/cloud/cloud.d.ts +20 -0
  32. package/dist/browser/cloud/cloud.js +129 -0
  33. package/dist/browser/cloud/index.d.ts +2 -0
  34. package/dist/browser/cloud/index.js +2 -0
  35. package/dist/browser/cloud/views.d.ts +41 -0
  36. package/dist/browser/cloud/views.js +35 -0
  37. package/dist/browser/events.d.ts +345 -0
  38. package/dist/browser/events.js +566 -0
  39. package/dist/browser/extensions.js +17 -17
  40. package/dist/browser/index.d.ts +4 -0
  41. package/dist/browser/index.js +4 -0
  42. package/dist/browser/profile.d.ts +8 -2
  43. package/dist/browser/profile.js +79 -12
  44. package/dist/browser/session-manager.d.ts +85 -0
  45. package/dist/browser/session-manager.js +208 -0
  46. package/dist/browser/session.d.ts +100 -8
  47. package/dist/browser/session.js +1097 -58
  48. package/dist/browser/types.d.ts +0 -2
  49. package/dist/browser/views.d.ts +39 -0
  50. package/dist/browser/views.js +32 -0
  51. package/dist/browser/watchdogs/aboutblank-watchdog.d.ts +12 -0
  52. package/dist/browser/watchdogs/aboutblank-watchdog.js +131 -0
  53. package/dist/browser/watchdogs/base.d.ts +21 -0
  54. package/dist/browser/watchdogs/base.js +81 -0
  55. package/dist/browser/watchdogs/cdp-session-watchdog.d.ts +14 -0
  56. package/dist/browser/watchdogs/cdp-session-watchdog.js +177 -0
  57. package/dist/browser/watchdogs/crash-watchdog.d.ts +38 -0
  58. package/dist/browser/watchdogs/crash-watchdog.js +296 -0
  59. package/dist/browser/watchdogs/default-action-watchdog.d.ts +49 -0
  60. package/dist/browser/watchdogs/default-action-watchdog.js +212 -0
  61. package/dist/browser/watchdogs/dom-watchdog.d.ts +8 -0
  62. package/dist/browser/watchdogs/dom-watchdog.js +31 -0
  63. package/dist/browser/watchdogs/downloads-watchdog.d.ts +77 -0
  64. package/dist/browser/watchdogs/downloads-watchdog.js +409 -0
  65. package/dist/browser/watchdogs/har-recording-watchdog.d.ts +19 -0
  66. package/dist/browser/watchdogs/har-recording-watchdog.js +317 -0
  67. package/dist/browser/watchdogs/index.d.ts +15 -0
  68. package/dist/browser/watchdogs/index.js +15 -0
  69. package/dist/browser/watchdogs/local-browser-watchdog.d.ts +10 -0
  70. package/dist/browser/watchdogs/local-browser-watchdog.js +32 -0
  71. package/dist/browser/watchdogs/permissions-watchdog.d.ts +8 -0
  72. package/dist/browser/watchdogs/permissions-watchdog.js +73 -0
  73. package/dist/browser/watchdogs/popups-watchdog.d.ts +13 -0
  74. package/dist/browser/watchdogs/popups-watchdog.js +77 -0
  75. package/dist/browser/watchdogs/recording-watchdog.d.ts +27 -0
  76. package/dist/browser/watchdogs/recording-watchdog.js +249 -0
  77. package/dist/browser/watchdogs/screenshot-watchdog.d.ts +6 -0
  78. package/dist/browser/watchdogs/screenshot-watchdog.js +13 -0
  79. package/dist/browser/watchdogs/security-watchdog.d.ts +10 -0
  80. package/dist/browser/watchdogs/security-watchdog.js +84 -0
  81. package/dist/browser/watchdogs/storage-state-watchdog.d.ts +24 -0
  82. package/dist/browser/watchdogs/storage-state-watchdog.js +288 -0
  83. package/dist/cli.d.ts +7 -2
  84. package/dist/cli.js +182 -25
  85. package/dist/code-use/formatting.d.ts +3 -0
  86. package/dist/code-use/formatting.js +18 -0
  87. package/dist/code-use/index.d.ts +6 -0
  88. package/dist/code-use/index.js +6 -0
  89. package/dist/code-use/namespace.d.ts +5 -0
  90. package/dist/code-use/namespace.js +81 -0
  91. package/dist/code-use/notebook-export.d.ts +3 -0
  92. package/dist/code-use/notebook-export.js +56 -0
  93. package/dist/code-use/service.d.ts +24 -0
  94. package/dist/code-use/service.js +104 -0
  95. package/dist/code-use/utils.d.ts +4 -0
  96. package/dist/code-use/utils.js +98 -0
  97. package/dist/code-use/views.d.ts +108 -0
  98. package/dist/code-use/views.js +165 -0
  99. package/dist/config.d.ts +13 -0
  100. package/dist/config.js +69 -3
  101. package/dist/controller/registry/service.d.ts +10 -1
  102. package/dist/controller/registry/service.js +266 -10
  103. package/dist/controller/registry/views.d.ts +4 -1
  104. package/dist/controller/registry/views.js +25 -2
  105. package/dist/controller/service.d.ts +10 -1
  106. package/dist/controller/service.js +1807 -268
  107. package/dist/controller/views.d.ts +78 -155
  108. package/dist/controller/views.js +61 -12
  109. package/dist/dom/history-tree-processor/service.d.ts +5 -0
  110. package/dist/dom/history-tree-processor/service.js +169 -14
  111. package/dist/dom/history-tree-processor/view.d.ts +7 -1
  112. package/dist/dom/history-tree-processor/view.js +10 -1
  113. package/dist/dom/markdown-extractor.d.ts +37 -0
  114. package/dist/dom/markdown-extractor.js +345 -0
  115. package/dist/dom/service.d.ts +3 -1
  116. package/dist/dom/service.js +76 -0
  117. package/dist/dom/views.d.ts +1 -0
  118. package/dist/dom/views.js +45 -0
  119. package/dist/event-bus.d.ts +107 -7
  120. package/dist/event-bus.js +313 -10
  121. package/dist/exceptions.d.ts +0 -3
  122. package/dist/exceptions.js +0 -7
  123. package/dist/filesystem/file-system.d.ts +18 -0
  124. package/dist/filesystem/file-system.js +503 -42
  125. package/dist/index.d.ts +7 -0
  126. package/dist/index.js +6 -0
  127. package/dist/integrations/gmail/actions.d.ts +3 -3
  128. package/dist/integrations/gmail/actions.js +4 -4
  129. package/dist/llm/anthropic/chat.d.ts +18 -1
  130. package/dist/llm/anthropic/chat.js +123 -55
  131. package/dist/llm/anthropic/serializer.d.ts +2 -0
  132. package/dist/llm/anthropic/serializer.js +81 -9
  133. package/dist/llm/aws/chat-anthropic.d.ts +17 -0
  134. package/dist/llm/aws/chat-anthropic.js +126 -26
  135. package/dist/llm/aws/chat-bedrock.d.ts +28 -1
  136. package/dist/llm/aws/chat-bedrock.js +161 -34
  137. package/dist/llm/aws/serializer.d.ts +13 -1
  138. package/dist/llm/aws/serializer.js +56 -17
  139. package/dist/llm/azure/chat.d.ts +53 -2
  140. package/dist/llm/azure/chat.js +366 -54
  141. package/dist/llm/base.d.ts +2 -0
  142. package/dist/llm/browser-use/chat.d.ts +40 -0
  143. package/dist/llm/browser-use/chat.js +305 -0
  144. package/dist/llm/browser-use/index.d.ts +1 -0
  145. package/dist/llm/browser-use/index.js +1 -0
  146. package/dist/llm/cerebras/chat.d.ts +39 -0
  147. package/dist/llm/cerebras/chat.js +178 -0
  148. package/dist/llm/cerebras/index.d.ts +2 -0
  149. package/dist/llm/cerebras/index.js +2 -0
  150. package/dist/llm/cerebras/serializer.d.ts +7 -0
  151. package/dist/llm/cerebras/serializer.js +82 -0
  152. package/dist/llm/deepseek/chat.d.ts +19 -2
  153. package/dist/llm/deepseek/chat.js +138 -25
  154. package/dist/llm/google/chat.d.ts +46 -2
  155. package/dist/llm/google/chat.js +267 -64
  156. package/dist/llm/google/serializer.d.ts +9 -1
  157. package/dist/llm/google/serializer.js +141 -34
  158. package/dist/llm/groq/chat.d.ts +21 -2
  159. package/dist/llm/groq/chat.js +125 -26
  160. package/dist/llm/groq/parser.js +3 -1
  161. package/dist/llm/mistral/chat.d.ts +43 -0
  162. package/dist/llm/mistral/chat.js +154 -0
  163. package/dist/llm/mistral/index.d.ts +2 -0
  164. package/dist/llm/mistral/index.js +2 -0
  165. package/dist/llm/mistral/schema.d.ts +8 -0
  166. package/dist/llm/mistral/schema.js +27 -0
  167. package/dist/llm/models.d.ts +2 -0
  168. package/dist/llm/models.js +317 -0
  169. package/dist/llm/ollama/chat.d.ts +13 -1
  170. package/dist/llm/ollama/chat.js +110 -19
  171. package/dist/llm/ollama/serializer.d.ts +1 -0
  172. package/dist/llm/ollama/serializer.js +34 -12
  173. package/dist/llm/openai/chat.d.ts +16 -0
  174. package/dist/llm/openai/chat.js +94 -44
  175. package/dist/llm/openai/like.d.ts +5 -3
  176. package/dist/llm/openai/like.js +7 -3
  177. package/dist/llm/openai/responses-serializer.d.ts +18 -0
  178. package/dist/llm/openai/responses-serializer.js +72 -0
  179. package/dist/llm/openrouter/chat.d.ts +28 -2
  180. package/dist/llm/openrouter/chat.js +115 -29
  181. package/dist/llm/schema.d.ts +11 -1
  182. package/dist/llm/schema.js +81 -1
  183. package/dist/llm/vercel/chat.d.ts +50 -0
  184. package/dist/llm/vercel/chat.js +276 -0
  185. package/dist/llm/vercel/index.d.ts +1 -0
  186. package/dist/llm/vercel/index.js +1 -0
  187. package/dist/llm/vercel/serializer.d.ts +5 -0
  188. package/dist/llm/vercel/serializer.js +7 -0
  189. package/dist/llm/views.d.ts +2 -1
  190. package/dist/llm/views.js +3 -1
  191. package/dist/logging-config.d.ts +2 -0
  192. package/dist/logging-config.js +82 -29
  193. package/dist/mcp/client.d.ts +10 -5
  194. package/dist/mcp/client.js +14 -9
  195. package/dist/mcp/controller.d.ts +42 -3
  196. package/dist/mcp/controller.js +56 -31
  197. package/dist/mcp/server.d.ts +14 -0
  198. package/dist/mcp/server.js +255 -52
  199. package/dist/observability.js +10 -4
  200. package/dist/sandbox/index.d.ts +2 -0
  201. package/dist/sandbox/index.js +2 -0
  202. package/dist/sandbox/sandbox.d.ts +19 -0
  203. package/dist/sandbox/sandbox.js +140 -0
  204. package/dist/sandbox/views.d.ts +67 -0
  205. package/dist/sandbox/views.js +121 -0
  206. package/dist/skill-cli/index.d.ts +3 -0
  207. package/dist/skill-cli/index.js +3 -0
  208. package/dist/skill-cli/protocol.d.ts +30 -0
  209. package/dist/skill-cli/protocol.js +48 -0
  210. package/dist/skill-cli/server.d.ts +11 -0
  211. package/dist/skill-cli/server.js +85 -0
  212. package/dist/skill-cli/sessions.d.ts +24 -0
  213. package/dist/skill-cli/sessions.js +47 -0
  214. package/dist/skills/index.d.ts +3 -0
  215. package/dist/skills/index.js +3 -0
  216. package/dist/skills/service.d.ts +27 -0
  217. package/dist/skills/service.js +266 -0
  218. package/dist/skills/utils.d.ts +6 -0
  219. package/dist/skills/utils.js +53 -0
  220. package/dist/skills/views.d.ts +40 -0
  221. package/dist/skills/views.js +10 -0
  222. package/dist/sync/auth.js +8 -3
  223. package/dist/sync/service.d.ts +6 -6
  224. package/dist/sync/service.js +54 -89
  225. package/dist/telemetry/views.d.ts +20 -6
  226. package/dist/telemetry/views.js +23 -5
  227. package/dist/tokens/custom-pricing.d.ts +2 -0
  228. package/dist/tokens/custom-pricing.js +22 -0
  229. package/dist/tokens/index.d.ts +2 -0
  230. package/dist/tokens/index.js +2 -0
  231. package/dist/tokens/mappings.d.ts +1 -0
  232. package/dist/tokens/mappings.js +3 -0
  233. package/dist/tokens/service.js +27 -8
  234. package/dist/tools/extraction/index.d.ts +2 -0
  235. package/dist/tools/extraction/index.js +2 -0
  236. package/dist/tools/extraction/schema-utils.d.ts +6 -0
  237. package/dist/tools/extraction/schema-utils.js +237 -0
  238. package/dist/tools/extraction/views.d.ts +7 -0
  239. package/dist/tools/index.d.ts +5 -0
  240. package/dist/tools/index.js +5 -0
  241. package/dist/tools/registry/index.d.ts +2 -0
  242. package/dist/tools/registry/index.js +2 -0
  243. package/dist/tools/registry/service.d.ts +1 -0
  244. package/dist/tools/registry/service.js +1 -0
  245. package/dist/tools/registry/views.d.ts +1 -0
  246. package/dist/tools/registry/views.js +1 -0
  247. package/dist/tools/service.d.ts +2 -0
  248. package/dist/tools/service.js +1 -0
  249. package/dist/tools/utils.d.ts +2 -0
  250. package/dist/tools/utils.js +57 -0
  251. package/dist/tools/views.d.ts +1 -0
  252. package/dist/tools/views.js +1 -0
  253. package/dist/utils.d.ts +10 -1
  254. package/dist/utils.js +70 -3
  255. package/package.json +87 -26
  256. package/dist/dom/playground/process-dom.js +0 -5
  257. package/dist/dom/playground/test-accessibility.d.ts +0 -44
  258. package/dist/dom/playground/test-accessibility.js +0 -111
  259. /package/dist/{dom/playground/process-dom.d.ts → tools/extraction/views.js} +0 -0
@@ -0,0 +1,409 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { BrowserConnectedEvent, BrowserLaunchEvent, BrowserStateRequestEvent, BrowserStoppedEvent, DownloadProgressEvent, DownloadStartedEvent, FileDownloadedEvent, NavigationCompleteEvent, TabClosedEvent, TabCreatedEvent, } from '../events.js';
4
+ import { BaseWatchdog } from './base.js';
5
+ export class DownloadsWatchdog extends BaseWatchdog {
6
+ static LISTENS_TO = [
7
+ BrowserConnectedEvent,
8
+ BrowserLaunchEvent,
9
+ BrowserStateRequestEvent,
10
+ BrowserStoppedEvent,
11
+ TabCreatedEvent,
12
+ TabClosedEvent,
13
+ NavigationCompleteEvent,
14
+ DownloadStartedEvent,
15
+ DownloadProgressEvent,
16
+ FileDownloadedEvent,
17
+ ];
18
+ static EMITS = [
19
+ DownloadStartedEvent,
20
+ DownloadProgressEvent,
21
+ FileDownloadedEvent,
22
+ ];
23
+ _activeDownloads = new Map();
24
+ _downloadStartCallbacks = [];
25
+ _downloadProgressCallbacks = [];
26
+ _downloadCompleteCallbacks = [];
27
+ _cdpSession = null;
28
+ _cdpListeners = [];
29
+ _networkDownloads = new Map();
30
+ _detectedDownloadUrls = new Set();
31
+ async on_BrowserConnectedEvent() {
32
+ await this._startCdpDownloadMonitoring();
33
+ }
34
+ on_BrowserLaunchEvent() {
35
+ const downloadsPath = this.browser_session.browser_profile.downloads_path;
36
+ if (!downloadsPath) {
37
+ return;
38
+ }
39
+ fs.mkdirSync(downloadsPath, { recursive: true });
40
+ }
41
+ async on_BrowserStateRequestEvent(event) {
42
+ const activeTab = this.browser_session.active_tab;
43
+ if (!activeTab?.target_id || !activeTab.url) {
44
+ return;
45
+ }
46
+ await this.event_bus.dispatch(new NavigationCompleteEvent({
47
+ target_id: activeTab.target_id,
48
+ url: activeTab.url,
49
+ status: null,
50
+ error_message: null,
51
+ loading_status: null,
52
+ event_parent_id: event.event_id,
53
+ }));
54
+ }
55
+ on_BrowserStoppedEvent() {
56
+ this._activeDownloads.clear();
57
+ this._downloadStartCallbacks = [];
58
+ this._downloadProgressCallbacks = [];
59
+ this._downloadCompleteCallbacks = [];
60
+ this._networkDownloads.clear();
61
+ this._detectedDownloadUrls.clear();
62
+ void this._stopCdpDownloadMonitoring();
63
+ }
64
+ on_TabCreatedEvent() {
65
+ return null;
66
+ }
67
+ on_TabClosedEvent() {
68
+ return null;
69
+ }
70
+ on_NavigationCompleteEvent() {
71
+ return null;
72
+ }
73
+ on_DownloadStartedEvent(event) {
74
+ const startInfo = {
75
+ guid: event.guid,
76
+ url: event.url,
77
+ suggested_filename: event.suggested_filename,
78
+ auto_download: event.auto_download,
79
+ };
80
+ this._activeDownloads.set(event.guid, {
81
+ url: event.url,
82
+ suggested_filename: event.suggested_filename,
83
+ started_at: new Date().toISOString(),
84
+ received_bytes: 0,
85
+ total_bytes: 0,
86
+ state: 'inProgress',
87
+ });
88
+ for (const callback of this._downloadStartCallbacks) {
89
+ try {
90
+ callback(startInfo);
91
+ }
92
+ catch (error) {
93
+ this.browser_session.logger.debug(`[DownloadsWatchdog] Error in download start callback: ${error.message}`);
94
+ }
95
+ }
96
+ }
97
+ on_DownloadProgressEvent(event) {
98
+ const existing = this._activeDownloads.get(event.guid);
99
+ if (existing) {
100
+ existing.received_bytes = event.received_bytes;
101
+ existing.total_bytes = event.total_bytes;
102
+ existing.state = event.state;
103
+ if (event.state === 'completed' || event.state === 'canceled') {
104
+ this._activeDownloads.delete(event.guid);
105
+ }
106
+ }
107
+ const progressInfo = {
108
+ guid: event.guid,
109
+ received_bytes: event.received_bytes,
110
+ total_bytes: event.total_bytes,
111
+ state: event.state,
112
+ };
113
+ for (const callback of this._downloadProgressCallbacks) {
114
+ try {
115
+ callback(progressInfo);
116
+ }
117
+ catch (error) {
118
+ this.browser_session.logger.debug(`[DownloadsWatchdog] Error in download progress callback: ${error.message}`);
119
+ }
120
+ }
121
+ }
122
+ on_FileDownloadedEvent(event) {
123
+ if (event.guid) {
124
+ this._activeDownloads.delete(event.guid);
125
+ }
126
+ this.browser_session.add_downloaded_file(event.path);
127
+ const completeInfo = {
128
+ guid: event.guid,
129
+ url: event.url,
130
+ path: event.path,
131
+ file_name: event.file_name,
132
+ file_size: event.file_size,
133
+ file_type: event.file_type,
134
+ mime_type: event.mime_type,
135
+ auto_download: event.auto_download,
136
+ };
137
+ for (const callback of this._downloadCompleteCallbacks) {
138
+ try {
139
+ callback(completeInfo);
140
+ }
141
+ catch (error) {
142
+ this.browser_session.logger.debug(`[DownloadsWatchdog] Error in download complete callback: ${error.message}`);
143
+ }
144
+ }
145
+ return event.path;
146
+ }
147
+ get_active_downloads() {
148
+ return [...this._activeDownloads.entries()].map(([guid, metadata]) => ({
149
+ guid,
150
+ ...metadata,
151
+ }));
152
+ }
153
+ register_download_callbacks(on_start_or_options = null, on_progress = null, on_complete = null) {
154
+ const { on_start, on_progress: resolvedProgress, on_complete: resolvedEnd, } = this._normalizeCallbackRegistration(on_start_or_options, on_progress, on_complete);
155
+ if (on_start) {
156
+ this._downloadStartCallbacks.push(on_start);
157
+ }
158
+ if (resolvedProgress) {
159
+ this._downloadProgressCallbacks.push(resolvedProgress);
160
+ }
161
+ if (resolvedEnd) {
162
+ this._downloadCompleteCallbacks.push(resolvedEnd);
163
+ }
164
+ }
165
+ unregister_download_callbacks(on_start_or_options = null, on_progress = null, on_complete = null) {
166
+ const { on_start, on_progress: resolvedProgress, on_complete: resolvedEnd, } = this._normalizeCallbackRegistration(on_start_or_options, on_progress, on_complete);
167
+ if (on_start) {
168
+ this._downloadStartCallbacks = this._downloadStartCallbacks.filter((callback) => callback !== on_start);
169
+ }
170
+ if (resolvedProgress) {
171
+ this._downloadProgressCallbacks = this._downloadProgressCallbacks.filter((callback) => callback !== resolvedProgress);
172
+ }
173
+ if (resolvedEnd) {
174
+ this._downloadCompleteCallbacks = this._downloadCompleteCallbacks.filter((callback) => callback !== resolvedEnd);
175
+ }
176
+ }
177
+ onDetached() {
178
+ this._activeDownloads.clear();
179
+ this._downloadStartCallbacks = [];
180
+ this._downloadProgressCallbacks = [];
181
+ this._downloadCompleteCallbacks = [];
182
+ this._networkDownloads.clear();
183
+ this._detectedDownloadUrls.clear();
184
+ void this._stopCdpDownloadMonitoring();
185
+ }
186
+ _normalizeCallbackRegistration(on_start_or_options, on_progress, on_complete) {
187
+ if (on_start_or_options &&
188
+ typeof on_start_or_options === 'object' &&
189
+ !Array.isArray(on_start_or_options)) {
190
+ return {
191
+ on_start: on_start_or_options.on_start ?? null,
192
+ on_progress: on_start_or_options.on_progress ?? null,
193
+ on_complete: on_start_or_options.on_complete ?? null,
194
+ };
195
+ }
196
+ return {
197
+ on_start: typeof on_start_or_options === 'function' ? on_start_or_options : null,
198
+ on_progress,
199
+ on_complete,
200
+ };
201
+ }
202
+ async _startCdpDownloadMonitoring() {
203
+ if (this._cdpSession) {
204
+ return;
205
+ }
206
+ if (!this.browser_session.browser_context?.newCDPSession) {
207
+ return;
208
+ }
209
+ try {
210
+ const session = (await this.browser_session.get_or_create_cdp_session(null));
211
+ await session.send?.('Network.enable');
212
+ this._cdpSession = session;
213
+ const onResponseReceived = (payload) => {
214
+ void this._handleNetworkResponse(payload);
215
+ };
216
+ const onLoadingFinished = (payload) => {
217
+ void this._handleNetworkLoadingFinished(payload);
218
+ };
219
+ session.on?.('Network.responseReceived', onResponseReceived);
220
+ session.on?.('Network.loadingFinished', onLoadingFinished);
221
+ this._cdpListeners = [
222
+ {
223
+ event: 'Network.responseReceived',
224
+ handler: onResponseReceived,
225
+ },
226
+ {
227
+ event: 'Network.loadingFinished',
228
+ handler: onLoadingFinished,
229
+ },
230
+ ];
231
+ }
232
+ catch (error) {
233
+ this.browser_session.logger.debug(`[DownloadsWatchdog] CDP download monitoring unavailable: ${error.message}`);
234
+ await this._stopCdpDownloadMonitoring();
235
+ }
236
+ }
237
+ async _stopCdpDownloadMonitoring() {
238
+ if (!this._cdpSession) {
239
+ return;
240
+ }
241
+ for (const listener of this._cdpListeners) {
242
+ this._cdpSession.off?.(listener.event, listener.handler);
243
+ }
244
+ this._cdpListeners = [];
245
+ try {
246
+ await this._cdpSession.detach?.();
247
+ }
248
+ catch {
249
+ // Ignore detach errors during shutdown.
250
+ }
251
+ finally {
252
+ this._cdpSession = null;
253
+ }
254
+ }
255
+ async _handleNetworkResponse(payload) {
256
+ const requestId = String(payload?.requestId ?? '');
257
+ if (!requestId) {
258
+ return;
259
+ }
260
+ const response = payload?.response ?? {};
261
+ const url = String(response?.url ?? '').trim();
262
+ if (!url || this._detectedDownloadUrls.has(url)) {
263
+ return;
264
+ }
265
+ const headers = this._normalizeHeaders(response?.headers);
266
+ const mimeType = typeof response?.mimeType === 'string'
267
+ ? response.mimeType.toLowerCase()
268
+ : '';
269
+ const contentDisposition = headers['content-disposition'] ?? '';
270
+ const isPdf = mimeType.includes('application/pdf') || /\.pdf(?:$|\?)/i.test(url);
271
+ const isAttachment = /attachment/i.test(contentDisposition);
272
+ const isBinary = mimeType.includes('application/octet-stream');
273
+ if (!isPdf && !isAttachment && !isBinary) {
274
+ return;
275
+ }
276
+ this._detectedDownloadUrls.add(url);
277
+ const suggestedFilename = this._resolveSuggestedFilename(contentDisposition, url);
278
+ const guid = `cdp-${requestId}`;
279
+ const autoDownload = isPdf && this.browser_session.auto_download_pdfs();
280
+ const fileType = this._inferFileType(suggestedFilename, mimeType);
281
+ this._networkDownloads.set(requestId, {
282
+ guid,
283
+ url,
284
+ suggested_filename: suggestedFilename,
285
+ mime_type: mimeType || null,
286
+ file_type: fileType,
287
+ auto_download: autoDownload,
288
+ });
289
+ await this.event_bus.dispatch(new DownloadStartedEvent({
290
+ guid,
291
+ url,
292
+ suggested_filename: suggestedFilename,
293
+ auto_download: autoDownload,
294
+ }));
295
+ }
296
+ async _handleNetworkLoadingFinished(payload) {
297
+ const requestId = String(payload?.requestId ?? '');
298
+ if (!requestId) {
299
+ return;
300
+ }
301
+ const metadata = this._networkDownloads.get(requestId);
302
+ if (!metadata) {
303
+ return;
304
+ }
305
+ this._networkDownloads.delete(requestId);
306
+ const encodedDataLength = typeof payload?.encodedDataLength === 'number'
307
+ ? Math.max(0, Math.floor(payload.encodedDataLength))
308
+ : 0;
309
+ await this.event_bus.dispatch(new DownloadProgressEvent({
310
+ guid: metadata.guid,
311
+ received_bytes: encodedDataLength,
312
+ total_bytes: encodedDataLength,
313
+ state: 'completed',
314
+ }));
315
+ if (!metadata.auto_download ||
316
+ !metadata.mime_type?.includes('application/pdf')) {
317
+ return;
318
+ }
319
+ const downloadsPath = this.browser_session.browser_profile.downloads_path;
320
+ if (!downloadsPath || !this._cdpSession?.send) {
321
+ return;
322
+ }
323
+ try {
324
+ const responseBody = await this._cdpSession.send('Network.getResponseBody', {
325
+ requestId,
326
+ });
327
+ const body = typeof responseBody?.body === 'string' ? responseBody.body : '';
328
+ if (!body) {
329
+ return;
330
+ }
331
+ fs.mkdirSync(downloadsPath, { recursive: true });
332
+ const uniqueFilename = await this._getUniqueFilename(downloadsPath, metadata.suggested_filename);
333
+ const filePath = path.join(downloadsPath, uniqueFilename);
334
+ const content = responseBody?.base64Encoded
335
+ ? Buffer.from(body, 'base64')
336
+ : Buffer.from(body, 'utf8');
337
+ fs.writeFileSync(filePath, content);
338
+ await this.event_bus.dispatch(new FileDownloadedEvent({
339
+ guid: metadata.guid,
340
+ url: metadata.url,
341
+ path: filePath,
342
+ file_name: uniqueFilename,
343
+ file_size: content.length,
344
+ file_type: metadata.file_type,
345
+ mime_type: metadata.mime_type,
346
+ auto_download: true,
347
+ }));
348
+ }
349
+ catch (error) {
350
+ this.browser_session.logger.debug(`[DownloadsWatchdog] Failed to materialize CDP download body: ${error.message}`);
351
+ }
352
+ }
353
+ _normalizeHeaders(input) {
354
+ if (!input || typeof input !== 'object') {
355
+ return {};
356
+ }
357
+ return Object.fromEntries(Object.entries(input).map(([k, v]) => [
358
+ k.toLowerCase(),
359
+ String(v),
360
+ ]));
361
+ }
362
+ _resolveSuggestedFilename(contentDisposition, url) {
363
+ const filenameMatch = /filename\*=UTF-8''([^;]+)|filename="?([^";]+)"?/i.exec(contentDisposition);
364
+ const fromHeader = filenameMatch?.[1] || filenameMatch?.[2] || '';
365
+ const candidate = decodeURIComponent(fromHeader || '').trim();
366
+ if (candidate) {
367
+ return this._sanitizeFilename(candidate);
368
+ }
369
+ try {
370
+ const parsed = new URL(url);
371
+ const basename = path.basename(parsed.pathname);
372
+ if (basename) {
373
+ return this._sanitizeFilename(basename);
374
+ }
375
+ }
376
+ catch {
377
+ // Ignore URL parsing errors and fallback below.
378
+ }
379
+ return 'download';
380
+ }
381
+ _sanitizeFilename(filename) {
382
+ const sanitized = filename
383
+ .replace(/[\\/:*?"<>|]+/g, '_')
384
+ .replace(/\s+/g, ' ')
385
+ .trim();
386
+ return sanitized || 'download';
387
+ }
388
+ _inferFileType(filename, mimeType) {
389
+ const ext = path.extname(filename).replace('.', '').toLowerCase();
390
+ if (ext) {
391
+ return ext;
392
+ }
393
+ if (mimeType.includes('pdf')) {
394
+ return 'pdf';
395
+ }
396
+ return null;
397
+ }
398
+ async _getUniqueFilename(directory, filename) {
399
+ const ext = path.extname(filename);
400
+ const basename = ext ? filename.slice(0, -ext.length) : filename;
401
+ let candidate = filename || 'download';
402
+ let counter = 1;
403
+ while (fs.existsSync(path.join(directory, candidate))) {
404
+ candidate = `${basename || 'download'}_${counter}${ext}`;
405
+ counter += 1;
406
+ }
407
+ return candidate;
408
+ }
409
+ }
@@ -0,0 +1,19 @@
1
+ import { BrowserConnectedEvent, BrowserStopEvent, BrowserStartEvent, BrowserStoppedEvent } from '../events.js';
2
+ import { BaseWatchdog } from './base.js';
3
+ export declare class HarRecordingWatchdog extends BaseWatchdog {
4
+ static LISTENS_TO: (typeof BrowserStartEvent | typeof BrowserStopEvent | typeof BrowserConnectedEvent | typeof BrowserStoppedEvent)[];
5
+ private _harPath;
6
+ private _cdpSession;
7
+ private _listeners;
8
+ private _entries;
9
+ on_BrowserStartEvent(): Promise<void>;
10
+ on_BrowserConnectedEvent(): Promise<void>;
11
+ on_BrowserStopEvent(): Promise<void>;
12
+ on_BrowserStoppedEvent(): Promise<void>;
13
+ protected onDetached(): void;
14
+ private _resolveConfiguredHarPath;
15
+ private _resolveAndPrepareHarPath;
16
+ private _startCdpCaptureIfNeeded;
17
+ private _writeHarFallbackIfNeeded;
18
+ private _teardownCapture;
19
+ }