browser-use 0.0.1 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +761 -0
- package/dist/agent/cloud-events.d.ts +264 -0
- package/dist/agent/cloud-events.js +318 -0
- package/dist/agent/gif.d.ts +15 -0
- package/dist/agent/gif.js +215 -0
- package/dist/agent/index.d.ts +8 -0
- package/dist/agent/index.js +8 -0
- package/dist/agent/message-manager/service.d.ts +30 -0
- package/dist/agent/message-manager/service.js +208 -0
- package/dist/agent/message-manager/utils.d.ts +2 -0
- package/dist/agent/message-manager/utils.js +41 -0
- package/dist/agent/message-manager/views.d.ts +26 -0
- package/dist/agent/message-manager/views.js +73 -0
- package/dist/agent/prompts.d.ts +52 -0
- package/dist/agent/prompts.js +259 -0
- package/dist/agent/service.d.ts +290 -0
- package/dist/agent/service.js +2200 -0
- package/dist/agent/views.d.ts +741 -0
- package/dist/agent/views.js +537 -0
- package/dist/browser/browser.d.ts +7 -0
- package/dist/browser/browser.js +5 -0
- package/dist/browser/context.d.ts +8 -0
- package/dist/browser/context.js +4 -0
- package/dist/browser/dvd-screensaver.d.ts +101 -0
- package/dist/browser/dvd-screensaver.js +270 -0
- package/dist/browser/extensions.d.ts +63 -0
- package/dist/browser/extensions.js +359 -0
- package/dist/browser/index.d.ts +10 -0
- package/dist/browser/index.js +9 -0
- package/dist/browser/playwright-manager.d.ts +47 -0
- package/dist/browser/playwright-manager.js +146 -0
- package/dist/browser/profile.d.ts +196 -0
- package/dist/browser/profile.js +815 -0
- package/dist/browser/session.d.ts +505 -0
- package/dist/browser/session.js +3409 -0
- package/dist/browser/types.d.ts +1184 -0
- package/dist/browser/types.js +1 -0
- package/dist/browser/utils.d.ts +1 -0
- package/dist/browser/utils.js +19 -0
- package/dist/browser/views.d.ts +78 -0
- package/dist/browser/views.js +72 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +44 -0
- package/dist/config.d.ts +108 -0
- package/dist/config.js +430 -0
- package/dist/controller/index.d.ts +3 -0
- package/dist/controller/index.js +3 -0
- package/dist/controller/registry/index.d.ts +2 -0
- package/dist/controller/registry/index.js +2 -0
- package/dist/controller/registry/service.d.ts +45 -0
- package/dist/controller/registry/service.js +184 -0
- package/dist/controller/registry/views.d.ts +55 -0
- package/dist/controller/registry/views.js +174 -0
- package/dist/controller/service.d.ts +49 -0
- package/dist/controller/service.js +1176 -0
- package/dist/controller/views.d.ts +241 -0
- package/dist/controller/views.js +88 -0
- package/dist/dom/clickable-element-processor/service.d.ts +11 -0
- package/dist/dom/clickable-element-processor/service.js +60 -0
- package/dist/dom/dom_tree/index.js +1400 -0
- package/dist/dom/history-tree-processor/service.d.ts +14 -0
- package/dist/dom/history-tree-processor/service.js +75 -0
- package/dist/dom/history-tree-processor/view.d.ts +54 -0
- package/dist/dom/history-tree-processor/view.js +56 -0
- package/dist/dom/playground/extraction.d.ts +19 -0
- package/dist/dom/playground/extraction.js +187 -0
- package/dist/dom/playground/process-dom.d.ts +1 -0
- package/dist/dom/playground/process-dom.js +5 -0
- package/dist/dom/playground/test-accessibility.d.ts +44 -0
- package/dist/dom/playground/test-accessibility.js +111 -0
- package/dist/dom/service.d.ts +19 -0
- package/dist/dom/service.js +227 -0
- package/dist/dom/utils.d.ts +1 -0
- package/dist/dom/utils.js +6 -0
- package/dist/dom/views.d.ts +61 -0
- package/dist/dom/views.js +247 -0
- package/dist/event-bus.d.ts +11 -0
- package/dist/event-bus.js +19 -0
- package/dist/exceptions.d.ts +10 -0
- package/dist/exceptions.js +22 -0
- package/dist/filesystem/file-system.d.ts +68 -0
- package/dist/filesystem/file-system.js +412 -0
- package/dist/filesystem/index.d.ts +1 -0
- package/dist/filesystem/index.js +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +33 -0
- package/dist/integrations/gmail/actions.d.ts +12 -0
- package/dist/integrations/gmail/actions.js +113 -0
- package/dist/integrations/gmail/index.d.ts +2 -0
- package/dist/integrations/gmail/index.js +2 -0
- package/dist/integrations/gmail/service.d.ts +61 -0
- package/dist/integrations/gmail/service.js +260 -0
- package/dist/llm/anthropic/chat.d.ts +28 -0
- package/dist/llm/anthropic/chat.js +126 -0
- package/dist/llm/anthropic/index.d.ts +2 -0
- package/dist/llm/anthropic/index.js +2 -0
- package/dist/llm/anthropic/serializer.d.ts +68 -0
- package/dist/llm/anthropic/serializer.js +285 -0
- package/dist/llm/aws/chat-anthropic.d.ts +61 -0
- package/dist/llm/aws/chat-anthropic.js +176 -0
- package/dist/llm/aws/chat-bedrock.d.ts +15 -0
- package/dist/llm/aws/chat-bedrock.js +80 -0
- package/dist/llm/aws/index.d.ts +3 -0
- package/dist/llm/aws/index.js +3 -0
- package/dist/llm/aws/serializer.d.ts +5 -0
- package/dist/llm/aws/serializer.js +68 -0
- package/dist/llm/azure/chat.d.ts +15 -0
- package/dist/llm/azure/chat.js +83 -0
- package/dist/llm/azure/index.d.ts +1 -0
- package/dist/llm/azure/index.js +1 -0
- package/dist/llm/base.d.ts +16 -0
- package/dist/llm/base.js +1 -0
- package/dist/llm/deepseek/chat.d.ts +15 -0
- package/dist/llm/deepseek/chat.js +51 -0
- package/dist/llm/deepseek/index.d.ts +2 -0
- package/dist/llm/deepseek/index.js +2 -0
- package/dist/llm/deepseek/serializer.d.ts +6 -0
- package/dist/llm/deepseek/serializer.js +57 -0
- package/dist/llm/exceptions.d.ts +10 -0
- package/dist/llm/exceptions.js +18 -0
- package/dist/llm/google/chat.d.ts +20 -0
- package/dist/llm/google/chat.js +144 -0
- package/dist/llm/google/index.d.ts +2 -0
- package/dist/llm/google/index.js +2 -0
- package/dist/llm/google/serializer.d.ts +6 -0
- package/dist/llm/google/serializer.js +64 -0
- package/dist/llm/groq/chat.d.ts +15 -0
- package/dist/llm/groq/chat.js +52 -0
- package/dist/llm/groq/index.d.ts +3 -0
- package/dist/llm/groq/index.js +3 -0
- package/dist/llm/groq/parser.d.ts +32 -0
- package/dist/llm/groq/parser.js +189 -0
- package/dist/llm/groq/serializer.d.ts +6 -0
- package/dist/llm/groq/serializer.js +56 -0
- package/dist/llm/messages.d.ts +77 -0
- package/dist/llm/messages.js +157 -0
- package/dist/llm/ollama/chat.d.ts +15 -0
- package/dist/llm/ollama/chat.js +77 -0
- package/dist/llm/ollama/index.d.ts +2 -0
- package/dist/llm/ollama/index.js +2 -0
- package/dist/llm/ollama/serializer.d.ts +6 -0
- package/dist/llm/ollama/serializer.js +53 -0
- package/dist/llm/openai/chat.d.ts +38 -0
- package/dist/llm/openai/chat.js +174 -0
- package/dist/llm/openai/index.d.ts +3 -0
- package/dist/llm/openai/index.js +3 -0
- package/dist/llm/openai/like.d.ts +17 -0
- package/dist/llm/openai/like.js +19 -0
- package/dist/llm/openai/serializer.d.ts +6 -0
- package/dist/llm/openai/serializer.js +57 -0
- package/dist/llm/openrouter/chat.d.ts +15 -0
- package/dist/llm/openrouter/chat.js +74 -0
- package/dist/llm/openrouter/index.d.ts +2 -0
- package/dist/llm/openrouter/index.js +2 -0
- package/dist/llm/openrouter/serializer.d.ts +3 -0
- package/dist/llm/openrouter/serializer.js +3 -0
- package/dist/llm/schema.d.ts +6 -0
- package/dist/llm/schema.js +77 -0
- package/dist/llm/views.d.ts +15 -0
- package/dist/llm/views.js +12 -0
- package/dist/logging-config.d.ts +25 -0
- package/dist/logging-config.js +89 -0
- package/dist/mcp/client.d.ts +142 -0
- package/dist/mcp/client.js +638 -0
- package/dist/mcp/controller.d.ts +6 -0
- package/dist/mcp/controller.js +38 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/server.d.ts +134 -0
- package/dist/mcp/server.js +759 -0
- package/dist/observability-decorators.d.ts +158 -0
- package/dist/observability-decorators.js +286 -0
- package/dist/observability.d.ts +23 -0
- package/dist/observability.js +58 -0
- package/dist/screenshots/index.d.ts +1 -0
- package/dist/screenshots/index.js +1 -0
- package/dist/screenshots/service.d.ts +6 -0
- package/dist/screenshots/service.js +28 -0
- package/dist/sync/auth.d.ts +27 -0
- package/dist/sync/auth.js +205 -0
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.js +2 -0
- package/dist/sync/service.d.ts +21 -0
- package/dist/sync/service.js +146 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.js +2 -0
- package/dist/telemetry/service.d.ts +12 -0
- package/dist/telemetry/service.js +85 -0
- package/dist/telemetry/views.d.ts +112 -0
- package/dist/telemetry/views.js +112 -0
- package/dist/tokens/index.d.ts +2 -0
- package/dist/tokens/index.js +2 -0
- package/dist/tokens/service.d.ts +35 -0
- package/dist/tokens/service.js +423 -0
- package/dist/tokens/views.d.ts +58 -0
- package/dist/tokens/views.js +1 -0
- package/dist/utils.d.ts +128 -0
- package/dist/utils.js +529 -0
- package/package.json +94 -5
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DVD Screensaver Loading Animation
|
|
3
|
+
*
|
|
4
|
+
* Displays a fun DVD logo bouncing animation while waiting for browser operations.
|
|
5
|
+
* Inspired by the classic DVD screensaver.
|
|
6
|
+
*/
|
|
7
|
+
import { createLogger } from '../logging-config.js';
|
|
8
|
+
const logger = createLogger('browser_use.dvd_screensaver');
|
|
9
|
+
/**
|
|
10
|
+
* DVD Screensaver Animation Controller
|
|
11
|
+
*/
|
|
12
|
+
export class DVDScreensaver {
|
|
13
|
+
isRunning = false;
|
|
14
|
+
intervalId = null;
|
|
15
|
+
width = 80;
|
|
16
|
+
height = 20;
|
|
17
|
+
x = 0;
|
|
18
|
+
y = 0;
|
|
19
|
+
dx = 1;
|
|
20
|
+
dy = 1;
|
|
21
|
+
logoWidth = 10;
|
|
22
|
+
logoHeight = 3;
|
|
23
|
+
colors = [
|
|
24
|
+
'\x1b[31m',
|
|
25
|
+
'\x1b[32m',
|
|
26
|
+
'\x1b[33m',
|
|
27
|
+
'\x1b[34m',
|
|
28
|
+
'\x1b[35m',
|
|
29
|
+
'\x1b[36m',
|
|
30
|
+
];
|
|
31
|
+
currentColorIndex = 0;
|
|
32
|
+
cornerHits = 0;
|
|
33
|
+
frameCount = 0;
|
|
34
|
+
message;
|
|
35
|
+
constructor(message = 'Loading...') {
|
|
36
|
+
this.message = message;
|
|
37
|
+
this.x = Math.floor(Math.random() * (this.width - this.logoWidth));
|
|
38
|
+
this.y = Math.floor(Math.random() * (this.height - this.logoHeight));
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Start the animation
|
|
42
|
+
*/
|
|
43
|
+
start(fps = 10) {
|
|
44
|
+
if (this.isRunning) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
this.isRunning = true;
|
|
48
|
+
// Hide cursor
|
|
49
|
+
process.stderr.write('\x1b[?25l');
|
|
50
|
+
// Clear screen
|
|
51
|
+
this.clear();
|
|
52
|
+
this.intervalId = setInterval(() => {
|
|
53
|
+
this.update();
|
|
54
|
+
this.render();
|
|
55
|
+
}, 1000 / fps);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Stop the animation
|
|
59
|
+
*/
|
|
60
|
+
stop() {
|
|
61
|
+
if (!this.isRunning) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
this.isRunning = false;
|
|
65
|
+
if (this.intervalId) {
|
|
66
|
+
clearInterval(this.intervalId);
|
|
67
|
+
this.intervalId = null;
|
|
68
|
+
}
|
|
69
|
+
// Clear screen one last time
|
|
70
|
+
this.clear();
|
|
71
|
+
// Show cursor
|
|
72
|
+
process.stderr.write('\x1b[?25h');
|
|
73
|
+
// Log corner hits if any
|
|
74
|
+
if (this.cornerHits > 0) {
|
|
75
|
+
logger.debug(`DVD logo hit corner ${this.cornerHits} time(s)! 🎯`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Update logo position
|
|
80
|
+
*/
|
|
81
|
+
update() {
|
|
82
|
+
this.frameCount++;
|
|
83
|
+
// Update position
|
|
84
|
+
this.x += this.dx;
|
|
85
|
+
this.y += this.dy;
|
|
86
|
+
let hitCorner = false;
|
|
87
|
+
// Check horizontal bounds
|
|
88
|
+
if (this.x <= 0 || this.x >= this.width - this.logoWidth) {
|
|
89
|
+
this.dx = -this.dx;
|
|
90
|
+
this.x = Math.max(0, Math.min(this.x, this.width - this.logoWidth));
|
|
91
|
+
this.changeColor();
|
|
92
|
+
// Check if hit corner
|
|
93
|
+
if (this.y <= 0 || this.y >= this.height - this.logoHeight) {
|
|
94
|
+
hitCorner = true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
// Check vertical bounds
|
|
98
|
+
if (this.y <= 0 || this.y >= this.height - this.logoHeight) {
|
|
99
|
+
this.dy = -this.dy;
|
|
100
|
+
this.y = Math.max(0, Math.min(this.y, this.height - this.logoHeight));
|
|
101
|
+
this.changeColor();
|
|
102
|
+
// Check if hit corner
|
|
103
|
+
if (this.x <= 0 || this.x >= this.width - this.logoWidth) {
|
|
104
|
+
hitCorner = true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (hitCorner) {
|
|
108
|
+
this.cornerHits++;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Change logo color
|
|
113
|
+
*/
|
|
114
|
+
changeColor() {
|
|
115
|
+
this.currentColorIndex = (this.currentColorIndex + 1) % this.colors.length;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Render the current frame
|
|
119
|
+
*/
|
|
120
|
+
render() {
|
|
121
|
+
// Move cursor to top-left
|
|
122
|
+
process.stderr.write('\x1b[H');
|
|
123
|
+
const color = this.colors[this.currentColorIndex];
|
|
124
|
+
const reset = '\x1b[0m';
|
|
125
|
+
// Build frame
|
|
126
|
+
const frame = [];
|
|
127
|
+
// Draw border and content
|
|
128
|
+
for (let row = 0; row < this.height; row++) {
|
|
129
|
+
let line = '';
|
|
130
|
+
for (let col = 0; col < this.width; col++) {
|
|
131
|
+
// Check if this position is part of the logo
|
|
132
|
+
if (row >= this.y &&
|
|
133
|
+
row < this.y + this.logoHeight &&
|
|
134
|
+
col >= this.x &&
|
|
135
|
+
col < this.x + this.logoWidth) {
|
|
136
|
+
// Draw logo
|
|
137
|
+
const logoRow = row - this.y;
|
|
138
|
+
const logoCol = col - this.x;
|
|
139
|
+
line += color + this.getLogoChar(logoRow, logoCol) + reset;
|
|
140
|
+
}
|
|
141
|
+
else if (row === 0 ||
|
|
142
|
+
row === this.height - 1 ||
|
|
143
|
+
col === 0 ||
|
|
144
|
+
col === this.width - 1) {
|
|
145
|
+
// Draw border
|
|
146
|
+
line += '·';
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
line += ' ';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
frame.push(line);
|
|
153
|
+
}
|
|
154
|
+
// Add status message
|
|
155
|
+
const statusLine = `\n${this.message} (Frame: ${this.frameCount}, Corner hits: ${this.cornerHits})`;
|
|
156
|
+
// Write frame to stderr
|
|
157
|
+
process.stderr.write(frame.join('\n') + statusLine);
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Get character for logo at specific position
|
|
161
|
+
*/
|
|
162
|
+
getLogoChar(row, col) {
|
|
163
|
+
// Simple "DVD" text logo
|
|
164
|
+
if (row === 1) {
|
|
165
|
+
const text = ' DVD ';
|
|
166
|
+
return col < text.length ? text[col] : ' ';
|
|
167
|
+
}
|
|
168
|
+
return '▓';
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Clear screen
|
|
172
|
+
*/
|
|
173
|
+
clear() {
|
|
174
|
+
process.stderr.write('\x1b[2J\x1b[H');
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Show DVD screensaver loading animation
|
|
179
|
+
* Returns a function to stop the animation
|
|
180
|
+
*
|
|
181
|
+
* @param message - Message to display
|
|
182
|
+
* @param fps - Frames per second (default: 10)
|
|
183
|
+
* @returns Function to stop the animation
|
|
184
|
+
*
|
|
185
|
+
* @example
|
|
186
|
+
* const stopAnimation = showDVDScreensaver('Loading browser...');
|
|
187
|
+
* await someAsyncOperation();
|
|
188
|
+
* stopAnimation();
|
|
189
|
+
*/
|
|
190
|
+
export function showDVDScreensaver(message = 'Loading...', fps = 10) {
|
|
191
|
+
const screensaver = new DVDScreensaver(message);
|
|
192
|
+
screensaver.start(fps);
|
|
193
|
+
return () => {
|
|
194
|
+
screensaver.stop();
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Run an async operation with DVD screensaver animation
|
|
199
|
+
*
|
|
200
|
+
* @param operation - Async operation to run
|
|
201
|
+
* @param message - Message to display
|
|
202
|
+
* @returns Result of the operation
|
|
203
|
+
*
|
|
204
|
+
* @example
|
|
205
|
+
* const result = await withDVDScreensaver(
|
|
206
|
+
* async () => await longRunningOperation(),
|
|
207
|
+
* 'Processing...'
|
|
208
|
+
* );
|
|
209
|
+
*/
|
|
210
|
+
export async function withDVDScreensaver(operation, message = 'Loading...') {
|
|
211
|
+
const stopAnimation = showDVDScreensaver(message);
|
|
212
|
+
try {
|
|
213
|
+
const result = await operation();
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
finally {
|
|
217
|
+
stopAnimation();
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Simple spinner animation (alternative to DVD screensaver)
|
|
222
|
+
*/
|
|
223
|
+
export class SpinnerAnimation {
|
|
224
|
+
isRunning = false;
|
|
225
|
+
intervalId = null;
|
|
226
|
+
frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
227
|
+
currentFrame = 0;
|
|
228
|
+
message;
|
|
229
|
+
constructor(message = 'Loading...') {
|
|
230
|
+
this.message = message;
|
|
231
|
+
}
|
|
232
|
+
start(fps = 10) {
|
|
233
|
+
if (this.isRunning) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
this.isRunning = true;
|
|
237
|
+
process.stderr.write('\x1b[?25l'); // Hide cursor
|
|
238
|
+
this.intervalId = setInterval(() => {
|
|
239
|
+
this.render();
|
|
240
|
+
this.currentFrame = (this.currentFrame + 1) % this.frames.length;
|
|
241
|
+
}, 1000 / fps);
|
|
242
|
+
}
|
|
243
|
+
stop() {
|
|
244
|
+
if (!this.isRunning) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
this.isRunning = false;
|
|
248
|
+
if (this.intervalId) {
|
|
249
|
+
clearInterval(this.intervalId);
|
|
250
|
+
this.intervalId = null;
|
|
251
|
+
}
|
|
252
|
+
// Clear line and show cursor
|
|
253
|
+
process.stderr.write('\r\x1b[K');
|
|
254
|
+
process.stderr.write('\x1b[?25h');
|
|
255
|
+
}
|
|
256
|
+
render() {
|
|
257
|
+
const frame = this.frames[this.currentFrame];
|
|
258
|
+
process.stderr.write(`\r${frame} ${this.message}`);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Show simple spinner animation
|
|
263
|
+
*/
|
|
264
|
+
export function showSpinner(message = 'Loading...', fps = 10) {
|
|
265
|
+
const spinner = new SpinnerAnimation(message);
|
|
266
|
+
spinner.start(fps);
|
|
267
|
+
return () => {
|
|
268
|
+
spinner.stop();
|
|
269
|
+
};
|
|
270
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Extension Management
|
|
3
|
+
* Handles Chrome extension download, installation, and runtime integration.
|
|
4
|
+
* Supports both Manifest V2 and V3 extensions.
|
|
5
|
+
*/
|
|
6
|
+
export interface BrowserExtensionDescriptor {
|
|
7
|
+
name: string;
|
|
8
|
+
webstore_id?: string;
|
|
9
|
+
id?: string;
|
|
10
|
+
webstore_url?: string;
|
|
11
|
+
crx_url?: string;
|
|
12
|
+
crx_path?: string;
|
|
13
|
+
unpacked_path?: string;
|
|
14
|
+
version?: string;
|
|
15
|
+
manifest?: any;
|
|
16
|
+
manifest_version?: string;
|
|
17
|
+
homepage_url?: string;
|
|
18
|
+
options_url?: string;
|
|
19
|
+
target?: any;
|
|
20
|
+
target_ctx?: any;
|
|
21
|
+
target_type?: string;
|
|
22
|
+
target_url?: string;
|
|
23
|
+
read_manifest?: () => any;
|
|
24
|
+
read_version?: () => string | null;
|
|
25
|
+
dispatch_eval?: (...args: any[]) => Promise<any>;
|
|
26
|
+
dispatch_popup?: () => Promise<any>;
|
|
27
|
+
dispatch_action?: (tab?: any) => Promise<any>;
|
|
28
|
+
dispatch_message?: (message: any, options?: any) => Promise<any>;
|
|
29
|
+
dispatch_command?: (command: string, tab?: any) => Promise<any>;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Generate extension ID from unpacked path
|
|
33
|
+
* Chrome uses SHA256 hash of the directory path
|
|
34
|
+
*/
|
|
35
|
+
export declare function getExtensionId(unpackedPath: string): string | null;
|
|
36
|
+
/**
|
|
37
|
+
* Load or install extension
|
|
38
|
+
*/
|
|
39
|
+
export declare function loadOrInstallExtension(ext: BrowserExtensionDescriptor, extensionsDir?: string): Promise<BrowserExtensionDescriptor>;
|
|
40
|
+
/**
|
|
41
|
+
* Check if a browser target is an extension
|
|
42
|
+
*/
|
|
43
|
+
export declare function isTargetExtension(target: any): Promise<{
|
|
44
|
+
target_type: string | null;
|
|
45
|
+
target_ctx: any;
|
|
46
|
+
target_url: string | null;
|
|
47
|
+
target_is_bg: boolean;
|
|
48
|
+
target_is_extension: boolean;
|
|
49
|
+
extension_id: string | null;
|
|
50
|
+
manifest_version: string;
|
|
51
|
+
}>;
|
|
52
|
+
/**
|
|
53
|
+
* Load extension from browser target (runtime connection)
|
|
54
|
+
*/
|
|
55
|
+
export declare function loadExtensionFromTarget(extensions: BrowserExtensionDescriptor[], target: any): Promise<BrowserExtensionDescriptor | null>;
|
|
56
|
+
/**
|
|
57
|
+
* Load all Chrome extensions from browser targets
|
|
58
|
+
*/
|
|
59
|
+
export declare function getChromeExtensionsFromBrowser(browser: any, extensions: BrowserExtensionDescriptor[]): Promise<BrowserExtensionDescriptor[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Install extensions from persona configuration
|
|
62
|
+
*/
|
|
63
|
+
export declare function installExtensionsFromConfig(extensionConfigs: BrowserExtensionDescriptor[], extensionsDir: string): Promise<BrowserExtensionDescriptor[]>;
|
|
@@ -0,0 +1,359 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Extension Management
|
|
3
|
+
* Handles Chrome extension download, installation, and runtime integration.
|
|
4
|
+
* Supports both Manifest V2 and V3 extensions.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { pipeline } from 'node:stream/promises';
|
|
10
|
+
import { createWriteStream } from 'node:fs';
|
|
11
|
+
import { Readable } from 'node:stream';
|
|
12
|
+
// @ts-ignore - extract-zip types may not be available
|
|
13
|
+
import extract from 'extract-zip';
|
|
14
|
+
import { createLogger } from '../logging-config.js';
|
|
15
|
+
const logger = createLogger('browser_use.extensions');
|
|
16
|
+
/**
|
|
17
|
+
* Generate extension ID from unpacked path
|
|
18
|
+
* Chrome uses SHA256 hash of the directory path
|
|
19
|
+
*/
|
|
20
|
+
export function getExtensionId(unpackedPath) {
|
|
21
|
+
const manifestPath = path.join(unpackedPath, 'manifest.json');
|
|
22
|
+
if (!fs.existsSync(manifestPath)) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
// Chrome uses SHA256 hash and converts to letter format
|
|
26
|
+
const hash = createHash('sha256').update(unpackedPath).digest('hex');
|
|
27
|
+
const extensionId = hash
|
|
28
|
+
.slice(0, 32)
|
|
29
|
+
.split('')
|
|
30
|
+
.map((char) => String.fromCharCode(parseInt(char, 16) + 'a'.charCodeAt(0)))
|
|
31
|
+
.join('');
|
|
32
|
+
return extensionId;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Download CRX file from Chrome Web Store
|
|
36
|
+
*/
|
|
37
|
+
async function downloadCrx(crxUrl, crxPath) {
|
|
38
|
+
try {
|
|
39
|
+
logger.info(`[🛠️] Downloading extension from ${crxUrl}...`);
|
|
40
|
+
const response = await fetch(crxUrl);
|
|
41
|
+
if (!response.ok || !response.body) {
|
|
42
|
+
logger.warning(`[⚠️] Failed to download extension: ${response.statusText}`);
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
// Ensure directory exists
|
|
46
|
+
const dir = path.dirname(crxPath);
|
|
47
|
+
if (!fs.existsSync(dir)) {
|
|
48
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
49
|
+
}
|
|
50
|
+
// Download file
|
|
51
|
+
const fileStream = createWriteStream(crxPath);
|
|
52
|
+
await pipeline(Readable.fromWeb(response.body), fileStream);
|
|
53
|
+
logger.info(`[✅] Downloaded to ${crxPath}`);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
logger.error(`[❌] Download failed: ${error.message}`);
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Extract CRX file to unpacked directory
|
|
63
|
+
*/
|
|
64
|
+
async function unpackCrx(crxPath, unpackedPath) {
|
|
65
|
+
try {
|
|
66
|
+
// Ensure unpacked directory exists
|
|
67
|
+
if (!fs.existsSync(unpackedPath)) {
|
|
68
|
+
fs.mkdirSync(unpackedPath, { recursive: true });
|
|
69
|
+
}
|
|
70
|
+
// Extract zip file (CRX is essentially a ZIP with extra header)
|
|
71
|
+
await extract(crxPath, { dir: path.resolve(unpackedPath) });
|
|
72
|
+
// Verify manifest exists
|
|
73
|
+
const manifestPath = path.join(unpackedPath, 'manifest.json');
|
|
74
|
+
if (!fs.existsSync(manifestPath)) {
|
|
75
|
+
logger.error(`[❌] No manifest.json found in ${unpackedPath}`);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
logger.info(`[✅] Extracted to ${unpackedPath}`);
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
catch (error) {
|
|
82
|
+
logger.error(`[❌] Extraction failed: ${error.message}`);
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Install extension (download and unpack if needed)
|
|
88
|
+
*/
|
|
89
|
+
async function installExtension(extension) {
|
|
90
|
+
const manifestPath = path.join(extension.unpacked_path, 'manifest.json');
|
|
91
|
+
const crxPath = extension.crx_path;
|
|
92
|
+
// Download CRX if neither manifest nor CRX exists
|
|
93
|
+
if (!fs.existsSync(manifestPath) && !fs.existsSync(crxPath)) {
|
|
94
|
+
logger.info(`[🛠️] Downloading missing extension ${extension.name} ${extension.webstore_id} -> ${crxPath}`);
|
|
95
|
+
const downloaded = await downloadCrx(extension.crx_url, crxPath);
|
|
96
|
+
if (!downloaded) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Unpack CRX if manifest doesn't exist
|
|
101
|
+
if (!fs.existsSync(manifestPath)) {
|
|
102
|
+
const unpacked = await unpackCrx(crxPath, extension.unpacked_path);
|
|
103
|
+
if (!unpacked) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Load or install extension
|
|
111
|
+
*/
|
|
112
|
+
export async function loadOrInstallExtension(ext, extensionsDir = path.join(process.cwd(), '.browser-use', 'extensions')) {
|
|
113
|
+
if (!ext.webstore_id && !ext.unpacked_path) {
|
|
114
|
+
throw new Error('Extension must have either webstore_id or unpacked_path');
|
|
115
|
+
}
|
|
116
|
+
// Set statically computable extension metadata
|
|
117
|
+
ext.webstore_id = ext.webstore_id || ext.id;
|
|
118
|
+
ext.name = ext.name || ext.webstore_id;
|
|
119
|
+
ext.webstore_url =
|
|
120
|
+
ext.webstore_url ||
|
|
121
|
+
`https://chromewebstore.google.com/detail/${ext.webstore_id}`;
|
|
122
|
+
ext.crx_url =
|
|
123
|
+
ext.crx_url ||
|
|
124
|
+
`https://clients2.google.com/service/update2/crx?response=redirect&prodversion=130.0&acceptformat=crx3&x=id%3D${ext.webstore_id}%26uc`;
|
|
125
|
+
ext.crx_path =
|
|
126
|
+
ext.crx_path ||
|
|
127
|
+
path.join(extensionsDir, `${ext.webstore_id}__${ext.name}.crx`);
|
|
128
|
+
ext.unpacked_path =
|
|
129
|
+
ext.unpacked_path ||
|
|
130
|
+
path.join(extensionsDir, `${ext.webstore_id}__${ext.name}`);
|
|
131
|
+
const manifestPath = path.join(ext.unpacked_path, 'manifest.json');
|
|
132
|
+
// Helper functions
|
|
133
|
+
const readManifest = () => {
|
|
134
|
+
if (fs.existsSync(manifestPath)) {
|
|
135
|
+
return JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));
|
|
136
|
+
}
|
|
137
|
+
return null;
|
|
138
|
+
};
|
|
139
|
+
const readVersion = () => {
|
|
140
|
+
const manifest = readManifest();
|
|
141
|
+
return manifest?.version || null;
|
|
142
|
+
};
|
|
143
|
+
ext.read_manifest = readManifest;
|
|
144
|
+
ext.read_version = readVersion;
|
|
145
|
+
// Install extension if not already installed
|
|
146
|
+
if (!readVersion()) {
|
|
147
|
+
await installExtension(ext);
|
|
148
|
+
}
|
|
149
|
+
// Auto-detect ID and version
|
|
150
|
+
ext.id = getExtensionId(ext.unpacked_path) ?? undefined;
|
|
151
|
+
ext.version = readVersion() ?? undefined;
|
|
152
|
+
if (!ext.version) {
|
|
153
|
+
logger.warning(`[❌] Unable to detect ID and version of installed extension ${ext.unpacked_path}`);
|
|
154
|
+
}
|
|
155
|
+
else {
|
|
156
|
+
logger.info(`[➕] Installed extension ${ext.name} (${ext.version})... ${ext.unpacked_path}`);
|
|
157
|
+
}
|
|
158
|
+
return ext;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Check if a browser target is an extension
|
|
162
|
+
*/
|
|
163
|
+
export async function isTargetExtension(target) {
|
|
164
|
+
let target_type = null;
|
|
165
|
+
let target_ctx = null;
|
|
166
|
+
let target_url = null;
|
|
167
|
+
try {
|
|
168
|
+
target_type = await target.type();
|
|
169
|
+
target_ctx = (await target.worker?.()) || (await target.page?.()) || null;
|
|
170
|
+
target_url =
|
|
171
|
+
(await target.url?.()) || (target_ctx ? await target_ctx.url() : null);
|
|
172
|
+
}
|
|
173
|
+
catch (error) {
|
|
174
|
+
if (error.message?.includes('No target with given id found')) {
|
|
175
|
+
// Target already closed
|
|
176
|
+
target_type = 'closed';
|
|
177
|
+
target_ctx = null;
|
|
178
|
+
target_url = 'about:closed';
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
const target_is_bg = ['service_worker', 'background_page'].includes(target_type || '');
|
|
185
|
+
const target_is_extension = target_url?.startsWith('chrome-extension://') || false;
|
|
186
|
+
const extension_id = target_is_extension
|
|
187
|
+
? target_url.split('://')[1].split('/')[0]
|
|
188
|
+
: null;
|
|
189
|
+
const manifest_version = target_type === 'service_worker' ? '3' : '2';
|
|
190
|
+
return {
|
|
191
|
+
target_type,
|
|
192
|
+
target_ctx,
|
|
193
|
+
target_url,
|
|
194
|
+
target_is_bg,
|
|
195
|
+
target_is_extension,
|
|
196
|
+
extension_id,
|
|
197
|
+
manifest_version,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Load extension from browser target (runtime connection)
|
|
202
|
+
*/
|
|
203
|
+
export async function loadExtensionFromTarget(extensions, target) {
|
|
204
|
+
const extensionInfo = await isTargetExtension(target);
|
|
205
|
+
const { target_is_bg, target_is_extension, target_type, target_ctx, target_url, extension_id, manifest_version, } = extensionInfo;
|
|
206
|
+
if (!target_is_bg || !extension_id || !target_ctx) {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
// Get manifest from extension
|
|
210
|
+
const manifest = await target_ctx.evaluate('() => chrome.runtime.getManifest()');
|
|
211
|
+
const name = manifest.name;
|
|
212
|
+
const version = manifest.version;
|
|
213
|
+
const homepage_url = manifest.homepage_url;
|
|
214
|
+
const options_page = manifest.options_page;
|
|
215
|
+
const options_ui = manifest.options_ui || {};
|
|
216
|
+
if (!version || !extension_id) {
|
|
217
|
+
return null;
|
|
218
|
+
}
|
|
219
|
+
// Get options URL
|
|
220
|
+
const options_url = await target_ctx.evaluate('(options_page) => chrome.runtime.getURL(options_page)', options_page || options_ui.page || 'options.html');
|
|
221
|
+
// Get keyboard commands
|
|
222
|
+
const commands = await target_ctx.evaluate(`
|
|
223
|
+
async () => {
|
|
224
|
+
return await new Promise((resolve) => {
|
|
225
|
+
if (chrome.commands) {
|
|
226
|
+
chrome.commands.getAll(resolve);
|
|
227
|
+
} else {
|
|
228
|
+
resolve({});
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
`);
|
|
233
|
+
// Dispatch helpers
|
|
234
|
+
const dispatch_eval = async (...args) => {
|
|
235
|
+
return await target_ctx.evaluate(...args);
|
|
236
|
+
};
|
|
237
|
+
const dispatch_popup = async () => {
|
|
238
|
+
return await target_ctx.evaluate("() => chrome.action?.openPopup() || chrome.tabs.create({url: chrome.runtime.getURL('popup.html')})");
|
|
239
|
+
};
|
|
240
|
+
let dispatch_action;
|
|
241
|
+
let dispatch_message;
|
|
242
|
+
let dispatch_command;
|
|
243
|
+
if (manifest_version === '3') {
|
|
244
|
+
// Manifest V3 APIs
|
|
245
|
+
dispatch_action = async (tab) => {
|
|
246
|
+
return await target_ctx.evaluate(`
|
|
247
|
+
async (tab) => {
|
|
248
|
+
tab = tab || (await new Promise((resolve) =>
|
|
249
|
+
chrome.tabs.query({currentWindow: true, active: true}, ([tab]) => resolve(tab))
|
|
250
|
+
));
|
|
251
|
+
return await chrome.action.onClicked.dispatch(tab);
|
|
252
|
+
}
|
|
253
|
+
`, tab);
|
|
254
|
+
};
|
|
255
|
+
dispatch_message = async (message, options) => {
|
|
256
|
+
return await target_ctx.evaluate(`
|
|
257
|
+
async (extension_id, message, options) => {
|
|
258
|
+
return await chrome.runtime.sendMessage(extension_id, message, options);
|
|
259
|
+
}
|
|
260
|
+
`, extension_id, message, options);
|
|
261
|
+
};
|
|
262
|
+
dispatch_command = async (command, tab) => {
|
|
263
|
+
return await target_ctx.evaluate(`
|
|
264
|
+
async (command, tab) => {
|
|
265
|
+
return await chrome.commands.onCommand.dispatch(command, tab);
|
|
266
|
+
}
|
|
267
|
+
`, command, tab);
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
// Manifest V2 APIs
|
|
272
|
+
dispatch_action = async (tab) => {
|
|
273
|
+
return await target_ctx.evaluate(`
|
|
274
|
+
async (tab) => {
|
|
275
|
+
tab = tab || (await new Promise((resolve) =>
|
|
276
|
+
chrome.tabs.query({currentWindow: true, active: true}, ([tab]) => resolve(tab))
|
|
277
|
+
));
|
|
278
|
+
return await chrome.browserAction.onClicked.dispatch(tab);
|
|
279
|
+
}
|
|
280
|
+
`, tab);
|
|
281
|
+
};
|
|
282
|
+
dispatch_message = async (message, options) => {
|
|
283
|
+
return await target_ctx.evaluate(`
|
|
284
|
+
async (extension_id, message, options) => {
|
|
285
|
+
return await new Promise((resolve) =>
|
|
286
|
+
chrome.runtime.sendMessage(extension_id, message, options, resolve)
|
|
287
|
+
);
|
|
288
|
+
}
|
|
289
|
+
`, extension_id, message, options);
|
|
290
|
+
};
|
|
291
|
+
dispatch_command = async (command, tab) => {
|
|
292
|
+
return await target_ctx.evaluate(`
|
|
293
|
+
async (command, tab) => {
|
|
294
|
+
return await new Promise((resolve) =>
|
|
295
|
+
chrome.commands.onCommand.dispatch(command, tab, resolve)
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
`, command, tab);
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// Find existing extension or create new one
|
|
302
|
+
const existing_extension = extensions.find((ext) => ext.id === extension_id) ||
|
|
303
|
+
{};
|
|
304
|
+
const new_extension = {
|
|
305
|
+
...existing_extension,
|
|
306
|
+
id: extension_id,
|
|
307
|
+
name,
|
|
308
|
+
target,
|
|
309
|
+
target_ctx,
|
|
310
|
+
target_type: target_type,
|
|
311
|
+
target_url: target_url,
|
|
312
|
+
manifest_version,
|
|
313
|
+
manifest,
|
|
314
|
+
version,
|
|
315
|
+
homepage_url,
|
|
316
|
+
options_url,
|
|
317
|
+
dispatch_eval,
|
|
318
|
+
dispatch_popup,
|
|
319
|
+
dispatch_action,
|
|
320
|
+
dispatch_message,
|
|
321
|
+
dispatch_command,
|
|
322
|
+
};
|
|
323
|
+
logger.info(`[➕] Loaded extension ${name.slice(0, 32)} (${version}) ${target_type}... ${target_url}`);
|
|
324
|
+
// Update existing extension in-place
|
|
325
|
+
Object.assign(existing_extension, new_extension);
|
|
326
|
+
return new_extension;
|
|
327
|
+
}
|
|
328
|
+
/**
|
|
329
|
+
* Load all Chrome extensions from browser targets
|
|
330
|
+
*/
|
|
331
|
+
export async function getChromeExtensionsFromBrowser(browser, extensions) {
|
|
332
|
+
logger.info(`[⚙️] Loading ${extensions.length} chrome extensions from browser...`);
|
|
333
|
+
// Find loaded extensions at runtime by checking all browser targets
|
|
334
|
+
const targets = await browser.targets();
|
|
335
|
+
for (const target of targets) {
|
|
336
|
+
await loadExtensionFromTarget(extensions, target);
|
|
337
|
+
}
|
|
338
|
+
return extensions;
|
|
339
|
+
}
|
|
340
|
+
/**
|
|
341
|
+
* Install extensions from persona configuration
|
|
342
|
+
*/
|
|
343
|
+
export async function installExtensionsFromConfig(extensionConfigs, extensionsDir) {
|
|
344
|
+
logger.info('*************************************************************************');
|
|
345
|
+
logger.info(`[⚙️] Installing ${extensionConfigs.length} chrome extensions...`);
|
|
346
|
+
const extensions = [];
|
|
347
|
+
try {
|
|
348
|
+
// Install each extension
|
|
349
|
+
for (const config of extensionConfigs) {
|
|
350
|
+
const extension = await loadOrInstallExtension(config, extensionsDir);
|
|
351
|
+
extensions.push(extension);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
catch (error) {
|
|
355
|
+
logger.error(`[❌] Extension installation failed: ${error.message}`);
|
|
356
|
+
}
|
|
357
|
+
logger.info('*************************************************************************');
|
|
358
|
+
return extensions;
|
|
359
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export * from './profile.js';
|
|
2
|
+
export * from './views.js';
|
|
3
|
+
export * from './utils.js';
|
|
4
|
+
export * from './session.js';
|
|
5
|
+
export * from './extensions.js';
|
|
6
|
+
export * from './dvd-screensaver.js';
|
|
7
|
+
export * from './playwright-manager.js';
|
|
8
|
+
export type { Browser, BrowserConfig, BrowserContext, BrowserContextConfig, } from './context.js';
|
|
9
|
+
export type { Browser as PlaywrightBrowser, BrowserContext as PlaywrightBrowserContext, Page, Locator, FrameLocator, ElementHandle, Playwright, PlaywrightOrPatchright, ClientCertificate, Geolocation, HttpCredentials, ProxySettings, StorageState, ViewportSize, } from './types.js';
|
|
10
|
+
export { async_playwright } from './types.js';
|