@olib-ai/owl-browser-sdk 1.0.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/README.md +582 -0
- package/dist/browser.d.ts +163 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +252 -0
- package/dist/browser.js.map +1 -0
- package/dist/connection.d.ts +80 -0
- package/dist/connection.d.ts.map +1 -0
- package/dist/connection.js +641 -0
- package/dist/connection.js.map +1 -0
- package/dist/context.d.ts +443 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +1017 -0
- package/dist/context.js.map +1 -0
- package/dist/core.d.ts +64 -0
- package/dist/core.d.ts.map +1 -0
- package/dist/core.js +131 -0
- package/dist/core.js.map +1 -0
- package/dist/errors.d.ts +83 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +150 -0
- package/dist/errors.js.map +1 -0
- package/dist/helpers.d.ts +11 -0
- package/dist/helpers.d.ts.map +1 -0
- package/dist/helpers.js +16 -0
- package/dist/helpers.js.map +1 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +68 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +514 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +46 -0
package/dist/context.js
ADDED
|
@@ -0,0 +1,1017 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.BrowserContext = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* Browser context (tab/window) for automation
|
|
6
|
+
*/
|
|
7
|
+
class BrowserContext {
|
|
8
|
+
constructor(contextId, core) {
|
|
9
|
+
this.contextId = contextId;
|
|
10
|
+
this.core = core;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Get the context ID
|
|
14
|
+
*/
|
|
15
|
+
getId() {
|
|
16
|
+
return this.contextId;
|
|
17
|
+
}
|
|
18
|
+
// ==================== NAVIGATION ====================
|
|
19
|
+
/**
|
|
20
|
+
* Navigate to a URL
|
|
21
|
+
* @param url - URL to navigate to
|
|
22
|
+
* @param options - Navigation options
|
|
23
|
+
*/
|
|
24
|
+
async goto(url, options) {
|
|
25
|
+
// Auto-add protocol if missing
|
|
26
|
+
if (!url.startsWith('http://') && !url.startsWith('https://')) {
|
|
27
|
+
url = 'https://' + url;
|
|
28
|
+
}
|
|
29
|
+
await this.core.sendCommand('navigate', {
|
|
30
|
+
context_id: this.contextId,
|
|
31
|
+
url,
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Reload the current page
|
|
36
|
+
* @param ignoreCache - Bypass cache (hard reload)
|
|
37
|
+
*/
|
|
38
|
+
async reload(ignoreCache = false) {
|
|
39
|
+
await this.core.sendCommand('reload', {
|
|
40
|
+
context_id: this.contextId,
|
|
41
|
+
ignore_cache: ignoreCache,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Navigate back in history
|
|
46
|
+
*/
|
|
47
|
+
async goBack() {
|
|
48
|
+
await this.core.sendCommand('goBack', {
|
|
49
|
+
context_id: this.contextId,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Navigate forward in history
|
|
54
|
+
*/
|
|
55
|
+
async goForward() {
|
|
56
|
+
await this.core.sendCommand('goForward', {
|
|
57
|
+
context_id: this.contextId,
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
// ==================== INTERACTIONS ====================
|
|
61
|
+
/**
|
|
62
|
+
* Click an element using CSS selector, position coordinates, or natural language description
|
|
63
|
+
* @param selector - CSS selector, position coordinates (e.g., "100x200"), or natural description (e.g., "search button", "login link")
|
|
64
|
+
*/
|
|
65
|
+
async click(selector) {
|
|
66
|
+
await this.core.sendCommand('click', {
|
|
67
|
+
context_id: this.contextId,
|
|
68
|
+
selector,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Type text into an input field
|
|
73
|
+
* @param selector - CSS selector, position coordinates (e.g., "100x200"), or natural description (e.g., "search box", "email input")
|
|
74
|
+
* @param text - Text to type
|
|
75
|
+
*/
|
|
76
|
+
async type(selector, text) {
|
|
77
|
+
await this.core.sendCommand('type', {
|
|
78
|
+
context_id: this.contextId,
|
|
79
|
+
selector,
|
|
80
|
+
text,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Select an option from a dropdown/select element
|
|
85
|
+
* @param selector - CSS selector or natural description (e.g., "country dropdown", "state list")
|
|
86
|
+
* @param value - Value or visible text of the option to select (e.g., "Morocco", "California")
|
|
87
|
+
*/
|
|
88
|
+
async pick(selector, value) {
|
|
89
|
+
await this.core.sendCommand('pick', {
|
|
90
|
+
context_id: this.contextId,
|
|
91
|
+
selector,
|
|
92
|
+
value,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Press a special key
|
|
97
|
+
* @param key - Key name (e.g., 'Enter', 'Tab', 'Escape')
|
|
98
|
+
*/
|
|
99
|
+
async pressKey(key) {
|
|
100
|
+
await this.core.sendCommand('pressKey', {
|
|
101
|
+
context_id: this.contextId,
|
|
102
|
+
key,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Submit the currently focused form by pressing Enter
|
|
107
|
+
* Useful for search boxes and forms that submit on Enter
|
|
108
|
+
*/
|
|
109
|
+
async submitForm() {
|
|
110
|
+
await this.core.sendCommand('submitForm', {
|
|
111
|
+
context_id: this.contextId,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Highlight an element for debugging
|
|
116
|
+
* @param selector - CSS selector or natural description
|
|
117
|
+
* @param borderColor - Border color (default: "#FF0000")
|
|
118
|
+
* @param backgroundColor - Background color (default: "rgba(255, 0, 0, 0.2)")
|
|
119
|
+
*/
|
|
120
|
+
async highlight(selector, borderColor = '#FF0000', backgroundColor = 'rgba(255, 0, 0, 0.2)') {
|
|
121
|
+
await this.core.sendCommand('highlight', {
|
|
122
|
+
context_id: this.contextId,
|
|
123
|
+
selector,
|
|
124
|
+
border_color: borderColor,
|
|
125
|
+
background_color: backgroundColor,
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
// ==================== CONTENT EXTRACTION ====================
|
|
129
|
+
/**
|
|
130
|
+
* Extract text content from the page
|
|
131
|
+
* @param selector - CSS selector or natural description (default: 'body')
|
|
132
|
+
*/
|
|
133
|
+
async extractText(selector = 'body') {
|
|
134
|
+
return await this.core.sendCommand('extractText', {
|
|
135
|
+
context_id: this.contextId,
|
|
136
|
+
selector,
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Get HTML content from the page
|
|
141
|
+
* @param cleanLevel - Cleaning level: 'minimal', 'basic', or 'aggressive'
|
|
142
|
+
*/
|
|
143
|
+
async getHTML(cleanLevel = 'basic') {
|
|
144
|
+
return await this.core.sendCommand('getHTML', {
|
|
145
|
+
context_id: this.contextId,
|
|
146
|
+
clean_level: cleanLevel,
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Get page content as Markdown
|
|
151
|
+
* @param options - Markdown extraction options
|
|
152
|
+
*/
|
|
153
|
+
async getMarkdown(options) {
|
|
154
|
+
return await this.core.sendCommand('getMarkdown', {
|
|
155
|
+
context_id: this.contextId,
|
|
156
|
+
include_links: options?.includeLinks ?? true,
|
|
157
|
+
include_images: options?.includeImages ?? true,
|
|
158
|
+
max_length: options?.maxLength ?? -1,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
/**
|
|
162
|
+
* Extract structured JSON data using templates
|
|
163
|
+
* @param template - Template name or empty for auto-detection
|
|
164
|
+
*/
|
|
165
|
+
async extractJSON(template = '') {
|
|
166
|
+
const result = await this.core.sendCommand('extractJSON', {
|
|
167
|
+
context_id: this.contextId,
|
|
168
|
+
template_name: template,
|
|
169
|
+
});
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Detect website type
|
|
174
|
+
*/
|
|
175
|
+
async detectWebsiteType() {
|
|
176
|
+
return await this.core.sendCommand('detectWebsiteType', {
|
|
177
|
+
context_id: this.contextId,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Get intelligent, structured summary of the current page using LLM
|
|
182
|
+
* The summary is cached per URL for fast repeat access
|
|
183
|
+
* @param forceRefresh - Force refresh the summary (ignore cache)
|
|
184
|
+
*/
|
|
185
|
+
async summarizePage(forceRefresh = false) {
|
|
186
|
+
return await this.core.sendCommand('summarizePage', {
|
|
187
|
+
context_id: this.contextId,
|
|
188
|
+
force_refresh: forceRefresh,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* List all available extraction templates
|
|
193
|
+
*/
|
|
194
|
+
async listTemplates() {
|
|
195
|
+
return await this.core.sendCommand('listTemplates', {});
|
|
196
|
+
}
|
|
197
|
+
// ==================== AI FEATURES ====================
|
|
198
|
+
/**
|
|
199
|
+
* Query the page using on-device LLM
|
|
200
|
+
* @param query - Natural language question about the page
|
|
201
|
+
*/
|
|
202
|
+
async queryPage(query) {
|
|
203
|
+
// Let the browser handle LLM readiness - it will wait internally
|
|
204
|
+
return await this.core.sendCommand('queryPage', {
|
|
205
|
+
context_id: this.contextId,
|
|
206
|
+
query,
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Check if the on-device LLM is ready to use
|
|
211
|
+
* @returns Status: "ready", "loading", or "unavailable"
|
|
212
|
+
*/
|
|
213
|
+
async llmStatus() {
|
|
214
|
+
return await this.core.sendCommand('llmStatus', {});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Execute natural language automation command
|
|
218
|
+
* @param command - Natural language command (e.g., "go to google.com and search for banana")
|
|
219
|
+
*/
|
|
220
|
+
async executeNLA(command) {
|
|
221
|
+
// Let the browser handle LLM readiness and execution - it will wait internally
|
|
222
|
+
return await this.core.sendCommand('executeNLA', {
|
|
223
|
+
context_id: this.contextId,
|
|
224
|
+
query: command,
|
|
225
|
+
});
|
|
226
|
+
}
|
|
227
|
+
// ==================== SCREENSHOT & VIDEO ====================
|
|
228
|
+
/**
|
|
229
|
+
* Take a screenshot
|
|
230
|
+
* @param options - Screenshot options
|
|
231
|
+
* @returns Buffer (or void if path is specified)
|
|
232
|
+
*/
|
|
233
|
+
async screenshot(options) {
|
|
234
|
+
const result = await this.core.sendCommand('screenshot', {
|
|
235
|
+
context_id: this.contextId,
|
|
236
|
+
});
|
|
237
|
+
const buffer = Buffer.from(result, 'base64');
|
|
238
|
+
// If path is specified, save to file
|
|
239
|
+
if (options?.path) {
|
|
240
|
+
const fs = require('fs');
|
|
241
|
+
fs.writeFileSync(options.path, buffer);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
return buffer;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Start video recording
|
|
248
|
+
* @param options - Video recording options
|
|
249
|
+
*/
|
|
250
|
+
async startVideoRecording(options) {
|
|
251
|
+
await this.core.sendCommand('startVideoRecording', {
|
|
252
|
+
context_id: this.contextId,
|
|
253
|
+
fps: options?.fps ?? 30,
|
|
254
|
+
codec: options?.codec ?? 'libx264',
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Pause video recording
|
|
259
|
+
*/
|
|
260
|
+
async pauseVideoRecording() {
|
|
261
|
+
await this.core.sendCommand('pauseVideoRecording', {
|
|
262
|
+
context_id: this.contextId,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Resume video recording
|
|
267
|
+
*/
|
|
268
|
+
async resumeVideoRecording() {
|
|
269
|
+
await this.core.sendCommand('resumeVideoRecording', {
|
|
270
|
+
context_id: this.contextId,
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Stop video recording and get video path
|
|
275
|
+
*/
|
|
276
|
+
async stopVideoRecording() {
|
|
277
|
+
return await this.core.sendCommand('stopVideoRecording', {
|
|
278
|
+
context_id: this.contextId,
|
|
279
|
+
});
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Get video recording statistics
|
|
283
|
+
*/
|
|
284
|
+
async getVideoStats() {
|
|
285
|
+
return await this.core.sendCommand('getVideoRecordingStats', {
|
|
286
|
+
context_id: this.contextId,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
// ==================== SCROLLING ====================
|
|
290
|
+
/**
|
|
291
|
+
* Scroll by specified pixels
|
|
292
|
+
* @param x - Horizontal scroll amount
|
|
293
|
+
* @param y - Vertical scroll amount
|
|
294
|
+
*/
|
|
295
|
+
async scrollBy(x, y) {
|
|
296
|
+
await this.core.sendCommand('scrollBy', {
|
|
297
|
+
context_id: this.contextId,
|
|
298
|
+
x,
|
|
299
|
+
y,
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* Scroll to absolute position
|
|
304
|
+
* @param x - Horizontal position
|
|
305
|
+
* @param y - Vertical position
|
|
306
|
+
*/
|
|
307
|
+
async scrollTo(x, y) {
|
|
308
|
+
await this.core.sendCommand('scrollTo', {
|
|
309
|
+
context_id: this.contextId,
|
|
310
|
+
x,
|
|
311
|
+
y,
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Scroll element into view
|
|
316
|
+
* @param selector - CSS selector or natural description
|
|
317
|
+
*/
|
|
318
|
+
async scrollToElement(selector) {
|
|
319
|
+
await this.core.sendCommand('scrollToElement', {
|
|
320
|
+
context_id: this.contextId,
|
|
321
|
+
selector,
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* Scroll to top of page
|
|
326
|
+
*/
|
|
327
|
+
async scrollToTop() {
|
|
328
|
+
await this.core.sendCommand('scrollToTop', {
|
|
329
|
+
context_id: this.contextId,
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Scroll to bottom of page
|
|
334
|
+
*/
|
|
335
|
+
async scrollToBottom() {
|
|
336
|
+
await this.core.sendCommand('scrollToBottom', {
|
|
337
|
+
context_id: this.contextId,
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
// ==================== WAITING ====================
|
|
341
|
+
/**
|
|
342
|
+
* Wait for element to appear
|
|
343
|
+
* @param selector - CSS selector or natural description
|
|
344
|
+
* @param options - Wait options
|
|
345
|
+
*/
|
|
346
|
+
async waitForSelector(selector, options) {
|
|
347
|
+
await this.core.sendCommand('waitForSelector', {
|
|
348
|
+
context_id: this.contextId,
|
|
349
|
+
selector,
|
|
350
|
+
timeout: options?.timeout ?? 5000,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
/**
|
|
354
|
+
* Wait for specified time (use sparingly - prefer waitForSelector)
|
|
355
|
+
* @param timeout - Time to wait in milliseconds
|
|
356
|
+
*/
|
|
357
|
+
async wait(timeout) {
|
|
358
|
+
// Only use when explicitly needed by user
|
|
359
|
+
await this.core.sendCommand('waitForTimeout', {
|
|
360
|
+
context_id: this.contextId,
|
|
361
|
+
timeout,
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
// ==================== PAGE STATE ====================
|
|
365
|
+
/**
|
|
366
|
+
* Get current URL
|
|
367
|
+
*/
|
|
368
|
+
async getCurrentURL() {
|
|
369
|
+
return await this.core.sendCommand('getCurrentURL', {
|
|
370
|
+
context_id: this.contextId,
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
/**
|
|
374
|
+
* Get page title
|
|
375
|
+
*/
|
|
376
|
+
async getTitle() {
|
|
377
|
+
return await this.core.sendCommand('getPageTitle', {
|
|
378
|
+
context_id: this.contextId,
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Get comprehensive page information
|
|
383
|
+
*/
|
|
384
|
+
async getPageInfo() {
|
|
385
|
+
return await this.core.sendCommand('getPageInfo', {
|
|
386
|
+
context_id: this.contextId,
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
// ==================== VIEWPORT ====================
|
|
390
|
+
/**
|
|
391
|
+
* Set viewport size
|
|
392
|
+
* @param viewport - Viewport dimensions
|
|
393
|
+
*/
|
|
394
|
+
async setViewport(viewport) {
|
|
395
|
+
await this.core.sendCommand('setViewport', {
|
|
396
|
+
context_id: this.contextId,
|
|
397
|
+
width: viewport.width,
|
|
398
|
+
height: viewport.height,
|
|
399
|
+
});
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Get current viewport size
|
|
403
|
+
*/
|
|
404
|
+
async getViewport() {
|
|
405
|
+
return await this.core.sendCommand('getViewport', {
|
|
406
|
+
context_id: this.contextId,
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
// ==================== DEMOGRAPHICS ====================
|
|
410
|
+
/**
|
|
411
|
+
* Get user demographics and context (location, time, weather)
|
|
412
|
+
* Useful for location-aware searches like "find me a restaurant" or "book a hotel"
|
|
413
|
+
*/
|
|
414
|
+
async getDemographics() {
|
|
415
|
+
return await this.core.sendCommand('getDemographics', {});
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Get user's current location based on IP address
|
|
419
|
+
*/
|
|
420
|
+
async getLocation() {
|
|
421
|
+
return await this.core.sendCommand('getLocation', {});
|
|
422
|
+
}
|
|
423
|
+
/**
|
|
424
|
+
* Get current date and time information
|
|
425
|
+
*/
|
|
426
|
+
async getDateTime() {
|
|
427
|
+
return await this.core.sendCommand('getDateTime', {});
|
|
428
|
+
}
|
|
429
|
+
/**
|
|
430
|
+
* Get current weather for the user's location
|
|
431
|
+
*/
|
|
432
|
+
async getWeather() {
|
|
433
|
+
return await this.core.sendCommand('getWeather', {});
|
|
434
|
+
}
|
|
435
|
+
// ==================== CAPTCHA SOLVING ====================
|
|
436
|
+
/**
|
|
437
|
+
* Detect if the current page has a CAPTCHA using heuristic analysis
|
|
438
|
+
* @returns Detection result with confidence score
|
|
439
|
+
*/
|
|
440
|
+
async detectCaptcha() {
|
|
441
|
+
return await this.core.sendCommand('detectCaptcha', {
|
|
442
|
+
context_id: this.contextId,
|
|
443
|
+
});
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Classify the type of CAPTCHA on the page
|
|
447
|
+
* @returns Classification result with CAPTCHA type and element selectors
|
|
448
|
+
*/
|
|
449
|
+
async classifyCaptcha() {
|
|
450
|
+
return await this.core.sendCommand('classifyCaptcha', {
|
|
451
|
+
context_id: this.contextId,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Solve a text-based CAPTCHA using vision model
|
|
456
|
+
* @param maxAttempts - Maximum number of attempts (default: 3)
|
|
457
|
+
*/
|
|
458
|
+
async solveTextCaptcha(maxAttempts = 3) {
|
|
459
|
+
return await this.core.sendCommand('solveTextCaptcha', {
|
|
460
|
+
context_id: this.contextId,
|
|
461
|
+
max_attempts: maxAttempts,
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Solve an image-selection CAPTCHA (e.g., "select all images with traffic lights")
|
|
466
|
+
* @param maxAttempts - Maximum number of attempts (default: 3)
|
|
467
|
+
*/
|
|
468
|
+
async solveImageCaptcha(maxAttempts = 3) {
|
|
469
|
+
return await this.core.sendCommand('solveImageCaptcha', {
|
|
470
|
+
context_id: this.contextId,
|
|
471
|
+
max_attempts: maxAttempts,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Auto-detect and solve any supported CAPTCHA type
|
|
476
|
+
* Automatically detects, classifies, and solves the CAPTCHA
|
|
477
|
+
* @param maxAttempts - Maximum number of attempts (default: 3)
|
|
478
|
+
*/
|
|
479
|
+
async solveCaptcha(maxAttempts = 3) {
|
|
480
|
+
return await this.core.sendCommand('solveCaptcha', {
|
|
481
|
+
context_id: this.contextId,
|
|
482
|
+
max_attempts: maxAttempts,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
// ==================== TEST EXECUTION ====================
|
|
486
|
+
/**
|
|
487
|
+
* Execute a test from Developer Playground JSON template
|
|
488
|
+
* @param test - Test template (JSON object or file path)
|
|
489
|
+
* @param options - Execution options
|
|
490
|
+
* @returns Test execution result
|
|
491
|
+
*
|
|
492
|
+
* @example
|
|
493
|
+
* ```typescript
|
|
494
|
+
* // Load test from JSON file
|
|
495
|
+
* const testJson = JSON.parse(fs.readFileSync('test.json', 'utf-8'));
|
|
496
|
+
* const result = await page.runTest(testJson);
|
|
497
|
+
* console.log(`Test: ${result.testName}`);
|
|
498
|
+
* console.log(`Success: ${result.successfulSteps}/${result.totalSteps}`);
|
|
499
|
+
* ```
|
|
500
|
+
*
|
|
501
|
+
* @example
|
|
502
|
+
* ```typescript
|
|
503
|
+
* // Define test inline
|
|
504
|
+
* const test = {
|
|
505
|
+
* name: "Login Test",
|
|
506
|
+
* description: "Test user login flow",
|
|
507
|
+
* steps: [
|
|
508
|
+
* { type: "navigate", url: "https://example.com/login" },
|
|
509
|
+
* { type: "type", selector: "#email", text: "user@example.com" },
|
|
510
|
+
* { type: "type", selector: "#password", text: "password123" },
|
|
511
|
+
* { type: "click", selector: "button[type='submit']" },
|
|
512
|
+
* { type: "wait", duration: 2000 },
|
|
513
|
+
* { type: "screenshot", filename: "after-login.png" }
|
|
514
|
+
* ]
|
|
515
|
+
* };
|
|
516
|
+
* const result = await page.runTest(test);
|
|
517
|
+
* ```
|
|
518
|
+
*/
|
|
519
|
+
async runTest(test, options) {
|
|
520
|
+
// Parse test if string (file path)
|
|
521
|
+
let testData;
|
|
522
|
+
if (typeof test === 'string') {
|
|
523
|
+
const fs = require('fs');
|
|
524
|
+
testData = JSON.parse(fs.readFileSync(test, 'utf-8'));
|
|
525
|
+
}
|
|
526
|
+
else {
|
|
527
|
+
testData = test;
|
|
528
|
+
}
|
|
529
|
+
const opts = {
|
|
530
|
+
continueOnError: options?.continueOnError ?? false,
|
|
531
|
+
screenshotOnError: options?.screenshotOnError ?? true,
|
|
532
|
+
verbose: options?.verbose ?? false,
|
|
533
|
+
};
|
|
534
|
+
const result = {
|
|
535
|
+
testName: testData.name,
|
|
536
|
+
totalSteps: testData.steps.filter(s => s.selected !== false).length,
|
|
537
|
+
executedSteps: 0,
|
|
538
|
+
successfulSteps: 0,
|
|
539
|
+
failedSteps: 0,
|
|
540
|
+
executionTime: 0,
|
|
541
|
+
success: true,
|
|
542
|
+
errors: [],
|
|
543
|
+
};
|
|
544
|
+
const startTime = Date.now();
|
|
545
|
+
if (opts.verbose) {
|
|
546
|
+
console.log(`[Test] Starting: ${testData.name}`);
|
|
547
|
+
if (testData.description) {
|
|
548
|
+
console.log(`[Test] Description: ${testData.description}`);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
for (let i = 0; i < testData.steps.length; i++) {
|
|
552
|
+
const step = testData.steps[i];
|
|
553
|
+
// Skip unselected steps
|
|
554
|
+
if (step.selected === false) {
|
|
555
|
+
if (opts.verbose) {
|
|
556
|
+
console.log(`[Test] Step ${i + 1}: Skipped (not selected)`);
|
|
557
|
+
}
|
|
558
|
+
continue;
|
|
559
|
+
}
|
|
560
|
+
result.executedSteps++;
|
|
561
|
+
if (opts.verbose) {
|
|
562
|
+
console.log(`[Test] Step ${i + 1}/${result.totalSteps}: ${step.type}`);
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
switch (step.type) {
|
|
566
|
+
case 'navigate':
|
|
567
|
+
if (!step.url)
|
|
568
|
+
throw new Error('Navigate step requires url');
|
|
569
|
+
await this.goto(step.url);
|
|
570
|
+
break;
|
|
571
|
+
case 'click':
|
|
572
|
+
if (!step.selector)
|
|
573
|
+
throw new Error('Click step requires selector');
|
|
574
|
+
await this.click(step.selector);
|
|
575
|
+
break;
|
|
576
|
+
case 'type':
|
|
577
|
+
if (!step.selector)
|
|
578
|
+
throw new Error('Type step requires selector');
|
|
579
|
+
if (!step.text)
|
|
580
|
+
throw new Error('Type step requires text');
|
|
581
|
+
await this.type(step.selector, step.text);
|
|
582
|
+
break;
|
|
583
|
+
case 'pick':
|
|
584
|
+
if (!step.selector)
|
|
585
|
+
throw new Error('Pick step requires selector');
|
|
586
|
+
if (!step.value)
|
|
587
|
+
throw new Error('Pick step requires value');
|
|
588
|
+
await this.pick(step.selector, step.value);
|
|
589
|
+
break;
|
|
590
|
+
case 'wait':
|
|
591
|
+
const duration = step.duration ?? 2000;
|
|
592
|
+
await this.wait(duration);
|
|
593
|
+
break;
|
|
594
|
+
case 'screenshot':
|
|
595
|
+
const filename = step.filename ?? 'screenshot.png';
|
|
596
|
+
await this.screenshot({ path: filename });
|
|
597
|
+
break;
|
|
598
|
+
case 'extract':
|
|
599
|
+
const selector = step.selector ?? 'body';
|
|
600
|
+
const text = await this.extractText(selector);
|
|
601
|
+
if (opts.verbose) {
|
|
602
|
+
console.log(`[Test] Extracted text: ${text.substring(0, 100)}...`);
|
|
603
|
+
}
|
|
604
|
+
break;
|
|
605
|
+
case 'submit_form':
|
|
606
|
+
await this.submitForm();
|
|
607
|
+
break;
|
|
608
|
+
case 'query':
|
|
609
|
+
if (!step.query)
|
|
610
|
+
throw new Error('Query step requires query');
|
|
611
|
+
const answer = await this.queryPage(step.query);
|
|
612
|
+
if (opts.verbose) {
|
|
613
|
+
console.log(`[Test] Query result: ${answer}`);
|
|
614
|
+
}
|
|
615
|
+
break;
|
|
616
|
+
case 'nla':
|
|
617
|
+
if (!step.command)
|
|
618
|
+
throw new Error('NLA step requires command');
|
|
619
|
+
const nlaResult = await this.executeNLA(step.command);
|
|
620
|
+
if (opts.verbose) {
|
|
621
|
+
console.log(`[Test] NLA result: ${nlaResult}`);
|
|
622
|
+
}
|
|
623
|
+
break;
|
|
624
|
+
case 'solve_captcha':
|
|
625
|
+
await this.solveCaptcha();
|
|
626
|
+
break;
|
|
627
|
+
case 'highlight':
|
|
628
|
+
if (!step.selector)
|
|
629
|
+
throw new Error('Highlight step requires selector');
|
|
630
|
+
await this.highlight(step.selector);
|
|
631
|
+
break;
|
|
632
|
+
case 'scroll_up':
|
|
633
|
+
await this.scrollBy(0, -500);
|
|
634
|
+
break;
|
|
635
|
+
case 'scroll_down':
|
|
636
|
+
await this.scrollBy(0, 500);
|
|
637
|
+
break;
|
|
638
|
+
case 'record_video':
|
|
639
|
+
const fps = step.fps ?? 30;
|
|
640
|
+
await this.startVideoRecording({ fps });
|
|
641
|
+
break;
|
|
642
|
+
case 'stop_video':
|
|
643
|
+
const videoPath = await this.stopVideoRecording();
|
|
644
|
+
if (opts.verbose) {
|
|
645
|
+
console.log(`[Test] Video saved: ${videoPath}`);
|
|
646
|
+
}
|
|
647
|
+
break;
|
|
648
|
+
default:
|
|
649
|
+
throw new Error(`Unknown step type: ${step.type}`);
|
|
650
|
+
}
|
|
651
|
+
result.successfulSteps++;
|
|
652
|
+
if (opts.verbose) {
|
|
653
|
+
console.log(`[Test] Step ${i + 1}: Success`);
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
catch (error) {
|
|
657
|
+
result.failedSteps++;
|
|
658
|
+
result.success = false;
|
|
659
|
+
const errorMessage = error.message || String(error);
|
|
660
|
+
result.errors.push({
|
|
661
|
+
step: i + 1,
|
|
662
|
+
type: step.type,
|
|
663
|
+
message: errorMessage,
|
|
664
|
+
});
|
|
665
|
+
console.error(`[Test] Step ${i + 1} failed: ${errorMessage}`);
|
|
666
|
+
// Take screenshot on error if enabled
|
|
667
|
+
if (opts.screenshotOnError) {
|
|
668
|
+
try {
|
|
669
|
+
const errorScreenshot = `error-step-${i + 1}.png`;
|
|
670
|
+
await this.screenshot({ path: errorScreenshot });
|
|
671
|
+
console.log(`[Test] Error screenshot saved: ${errorScreenshot}`);
|
|
672
|
+
}
|
|
673
|
+
catch (screenshotError) {
|
|
674
|
+
console.error(`[Test] Failed to take error screenshot:`, screenshotError);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
// Stop execution if continueOnError is false
|
|
678
|
+
if (!opts.continueOnError) {
|
|
679
|
+
console.error(`[Test] Stopping execution due to error`);
|
|
680
|
+
break;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
result.executionTime = Date.now() - startTime;
|
|
685
|
+
if (opts.verbose) {
|
|
686
|
+
console.log(`[Test] Completed: ${testData.name}`);
|
|
687
|
+
console.log(`[Test] Time: ${result.executionTime}ms`);
|
|
688
|
+
console.log(`[Test] Success: ${result.successfulSteps}/${result.totalSteps}`);
|
|
689
|
+
if (result.failedSteps > 0) {
|
|
690
|
+
console.log(`[Test] Failed: ${result.failedSteps}`);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
return result;
|
|
694
|
+
}
|
|
695
|
+
// ==================== COOKIE MANAGEMENT ====================
|
|
696
|
+
/**
|
|
697
|
+
* Get all cookies from the browser context
|
|
698
|
+
* @param url - Optional URL to filter cookies (returns all if not specified)
|
|
699
|
+
* @returns Array of cookies
|
|
700
|
+
*
|
|
701
|
+
* @example
|
|
702
|
+
* ```typescript
|
|
703
|
+
* // Get all cookies
|
|
704
|
+
* const allCookies = await page.getCookies();
|
|
705
|
+
*
|
|
706
|
+
* // Get cookies for a specific URL
|
|
707
|
+
* const siteCookies = await page.getCookies('https://example.com');
|
|
708
|
+
* ```
|
|
709
|
+
*/
|
|
710
|
+
async getCookies(url) {
|
|
711
|
+
return await this.core.sendCommand('getCookies', {
|
|
712
|
+
context_id: this.contextId,
|
|
713
|
+
url: url || '',
|
|
714
|
+
});
|
|
715
|
+
}
|
|
716
|
+
/**
|
|
717
|
+
* Set a cookie in the browser context
|
|
718
|
+
* @param url - URL to associate with the cookie (used for domain validation)
|
|
719
|
+
* @param name - Cookie name
|
|
720
|
+
* @param value - Cookie value
|
|
721
|
+
* @param options - Cookie options (domain, path, secure, httpOnly, sameSite, expires)
|
|
722
|
+
* @returns true if cookie was set successfully
|
|
723
|
+
*
|
|
724
|
+
* @example
|
|
725
|
+
* ```typescript
|
|
726
|
+
* // Set a simple cookie
|
|
727
|
+
* await page.setCookie('https://example.com', 'sessionId', 'abc123');
|
|
728
|
+
*
|
|
729
|
+
* // Set a cookie with options
|
|
730
|
+
* await page.setCookie('https://example.com', 'prefs', 'dark-mode', {
|
|
731
|
+
* path: '/',
|
|
732
|
+
* secure: true,
|
|
733
|
+
* httpOnly: true,
|
|
734
|
+
* sameSite: 'strict',
|
|
735
|
+
* expires: Date.now() / 1000 + 86400 // 1 day from now
|
|
736
|
+
* });
|
|
737
|
+
* ```
|
|
738
|
+
*/
|
|
739
|
+
async setCookie(url, name, value, options) {
|
|
740
|
+
return await this.core.sendCommand('setCookie', {
|
|
741
|
+
context_id: this.contextId,
|
|
742
|
+
url,
|
|
743
|
+
name,
|
|
744
|
+
value,
|
|
745
|
+
domain: options?.domain || '',
|
|
746
|
+
path: options?.path || '/',
|
|
747
|
+
secure: options?.secure || false,
|
|
748
|
+
httpOnly: options?.httpOnly || false,
|
|
749
|
+
sameSite: options?.sameSite || 'lax',
|
|
750
|
+
expires: options?.expires || -1,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Delete cookies from the browser context
|
|
755
|
+
* @param url - Optional URL to filter which cookies to delete
|
|
756
|
+
* @param name - Optional specific cookie name to delete
|
|
757
|
+
* @returns true if cookies were deleted successfully
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```typescript
|
|
761
|
+
* // Delete all cookies
|
|
762
|
+
* await page.deleteCookies();
|
|
763
|
+
*
|
|
764
|
+
* // Delete cookies for a specific URL
|
|
765
|
+
* await page.deleteCookies('https://example.com');
|
|
766
|
+
*
|
|
767
|
+
* // Delete a specific cookie
|
|
768
|
+
* await page.deleteCookies('https://example.com', 'sessionId');
|
|
769
|
+
* ```
|
|
770
|
+
*/
|
|
771
|
+
async deleteCookies(url, name) {
|
|
772
|
+
return await this.core.sendCommand('deleteCookies', {
|
|
773
|
+
context_id: this.contextId,
|
|
774
|
+
url: url || '',
|
|
775
|
+
cookie_name: name || '',
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
// ==================== PROXY MANAGEMENT ====================
|
|
779
|
+
/**
|
|
780
|
+
* Configure proxy settings for this browser context
|
|
781
|
+
* Includes stealth features to prevent proxy/VPN detection
|
|
782
|
+
* @param config - Proxy configuration
|
|
783
|
+
* @returns true if proxy was configured successfully
|
|
784
|
+
*
|
|
785
|
+
* @example
|
|
786
|
+
* ```typescript
|
|
787
|
+
* // Configure SOCKS5 proxy with stealth
|
|
788
|
+
* await page.setProxy({
|
|
789
|
+
* type: 'socks5h',
|
|
790
|
+
* host: 'proxy.example.com',
|
|
791
|
+
* port: 1080,
|
|
792
|
+
* username: 'user',
|
|
793
|
+
* password: 'pass',
|
|
794
|
+
* stealth: true,
|
|
795
|
+
* timezoneOverride: 'America/New_York'
|
|
796
|
+
* });
|
|
797
|
+
* ```
|
|
798
|
+
*/
|
|
799
|
+
async setProxy(config) {
|
|
800
|
+
return await this.core.sendCommand('setProxy', {
|
|
801
|
+
context_id: this.contextId,
|
|
802
|
+
type: config.type,
|
|
803
|
+
host: config.host,
|
|
804
|
+
port: config.port,
|
|
805
|
+
username: config.username || '',
|
|
806
|
+
password: config.password || '',
|
|
807
|
+
stealth: config.stealth !== false,
|
|
808
|
+
block_webrtc: config.blockWebrtc !== false,
|
|
809
|
+
spoof_timezone: config.spoofTimezone || false,
|
|
810
|
+
spoof_language: config.spoofLanguage || false,
|
|
811
|
+
timezone_override: config.timezoneOverride || '',
|
|
812
|
+
language_override: config.languageOverride || '',
|
|
813
|
+
});
|
|
814
|
+
}
|
|
815
|
+
/**
|
|
816
|
+
* Get current proxy configuration and connection status
|
|
817
|
+
* @returns Proxy status object
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* ```typescript
|
|
821
|
+
* const status = await page.getProxyStatus();
|
|
822
|
+
* if (status.enabled && status.connected) {
|
|
823
|
+
* console.log(`Connected to ${status.type}://${status.host}:${status.port}`);
|
|
824
|
+
* }
|
|
825
|
+
* ```
|
|
826
|
+
*/
|
|
827
|
+
async getProxyStatus() {
|
|
828
|
+
return await this.core.sendCommand('getProxyStatus', {
|
|
829
|
+
context_id: this.contextId,
|
|
830
|
+
});
|
|
831
|
+
}
|
|
832
|
+
/**
|
|
833
|
+
* Enable/connect the configured proxy
|
|
834
|
+
* @returns true if proxy was connected successfully
|
|
835
|
+
*/
|
|
836
|
+
async connectProxy() {
|
|
837
|
+
return await this.core.sendCommand('connectProxy', {
|
|
838
|
+
context_id: this.contextId,
|
|
839
|
+
});
|
|
840
|
+
}
|
|
841
|
+
/**
|
|
842
|
+
* Disable/disconnect the proxy, reverting to direct connection
|
|
843
|
+
* @returns true if proxy was disconnected successfully
|
|
844
|
+
*/
|
|
845
|
+
async disconnectProxy() {
|
|
846
|
+
return await this.core.sendCommand('disconnectProxy', {
|
|
847
|
+
context_id: this.contextId,
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
// ==================== BROWSER PROFILE MANAGEMENT ====================
|
|
851
|
+
/**
|
|
852
|
+
* Save the current context state to a browser profile
|
|
853
|
+
*
|
|
854
|
+
* The profile includes fingerprints, cookies, and configuration settings.
|
|
855
|
+
* This enables persistent browser identities across sessions.
|
|
856
|
+
*
|
|
857
|
+
* @param profilePath - Path to save the profile JSON file. If not provided,
|
|
858
|
+
* uses the profile path set during context creation.
|
|
859
|
+
* @returns BrowserProfile object with the saved state
|
|
860
|
+
*
|
|
861
|
+
* @example
|
|
862
|
+
* ```typescript
|
|
863
|
+
* // Save profile to file
|
|
864
|
+
* const profile = await page.saveProfile('/path/to/profile.json');
|
|
865
|
+
* console.log(`Saved profile: ${profile.profileId}`);
|
|
866
|
+
* console.log(`Cookies: ${profile.cookies.length}`);
|
|
867
|
+
* ```
|
|
868
|
+
*/
|
|
869
|
+
async saveProfile(profilePath) {
|
|
870
|
+
const result = await this.core.sendCommand('saveProfile', {
|
|
871
|
+
context_id: this.contextId,
|
|
872
|
+
profile_path: profilePath || '',
|
|
873
|
+
});
|
|
874
|
+
return this.parseProfile(result);
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Get the current profile state for this context
|
|
878
|
+
*
|
|
879
|
+
* Returns the in-memory profile state without saving to disk.
|
|
880
|
+
*
|
|
881
|
+
* @returns BrowserProfile object with current state
|
|
882
|
+
*
|
|
883
|
+
* @example
|
|
884
|
+
* ```typescript
|
|
885
|
+
* const profile = await page.getProfile();
|
|
886
|
+
* console.log(`User Agent: ${profile.fingerprint?.userAgent}`);
|
|
887
|
+
* console.log(`Cookies: ${profile.cookies.length}`);
|
|
888
|
+
* ```
|
|
889
|
+
*/
|
|
890
|
+
async getProfile() {
|
|
891
|
+
const result = await this.core.sendCommand('getProfile', {
|
|
892
|
+
context_id: this.contextId,
|
|
893
|
+
});
|
|
894
|
+
return this.parseProfile(result);
|
|
895
|
+
}
|
|
896
|
+
/**
|
|
897
|
+
* Update the profile file with current cookies from the browser
|
|
898
|
+
*
|
|
899
|
+
* This is useful when you want to persist cookie changes without
|
|
900
|
+
* saving the entire profile state.
|
|
901
|
+
*
|
|
902
|
+
* @returns true if cookies were updated successfully
|
|
903
|
+
*
|
|
904
|
+
* @example
|
|
905
|
+
* ```typescript
|
|
906
|
+
* // After logging in
|
|
907
|
+
* await page.goto('https://example.com/login');
|
|
908
|
+
* await page.type('email input', 'user@example.com');
|
|
909
|
+
* await page.type('password input', 'password');
|
|
910
|
+
* await page.click('login button');
|
|
911
|
+
*
|
|
912
|
+
* // Save the session cookies
|
|
913
|
+
* await page.updateProfileCookies();
|
|
914
|
+
* ```
|
|
915
|
+
*/
|
|
916
|
+
async updateProfileCookies() {
|
|
917
|
+
return await this.core.sendCommand('updateProfileCookies', {
|
|
918
|
+
context_id: this.contextId,
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
/**
|
|
922
|
+
* Parse profile data from command result
|
|
923
|
+
*/
|
|
924
|
+
parseProfile(data) {
|
|
925
|
+
if (typeof data === 'string') {
|
|
926
|
+
data = JSON.parse(data);
|
|
927
|
+
}
|
|
928
|
+
if (!data || typeof data !== 'object') {
|
|
929
|
+
return {
|
|
930
|
+
profileId: '',
|
|
931
|
+
profileName: '',
|
|
932
|
+
createdAt: '',
|
|
933
|
+
modifiedAt: '',
|
|
934
|
+
version: 1,
|
|
935
|
+
cookies: [],
|
|
936
|
+
hasLlmConfig: false,
|
|
937
|
+
hasProxyConfig: false,
|
|
938
|
+
autoSaveCookies: true,
|
|
939
|
+
persistLocalStorage: true,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
// Parse fingerprint
|
|
943
|
+
let fingerprint;
|
|
944
|
+
const fp = data.fingerprint;
|
|
945
|
+
if (fp) {
|
|
946
|
+
fingerprint = {
|
|
947
|
+
userAgent: fp.user_agent || '',
|
|
948
|
+
platform: fp.platform || 'Win32',
|
|
949
|
+
vendor: fp.vendor || 'Google Inc.',
|
|
950
|
+
languages: fp.languages || ['en-US', 'en'],
|
|
951
|
+
hardwareConcurrency: fp.hardware_concurrency || 8,
|
|
952
|
+
deviceMemory: fp.device_memory || 8,
|
|
953
|
+
maxTouchPoints: fp.max_touch_points || 0,
|
|
954
|
+
canvasNoiseSeed: fp.canvas_noise_seed || 0,
|
|
955
|
+
gpuProfileIndex: fp.gpu_profile_index || 0,
|
|
956
|
+
webglVendor: fp.webgl_vendor || '',
|
|
957
|
+
webglRenderer: fp.webgl_renderer || '',
|
|
958
|
+
screenWidth: fp.screen_width || 1920,
|
|
959
|
+
screenHeight: fp.screen_height || 1080,
|
|
960
|
+
colorDepth: fp.color_depth || 24,
|
|
961
|
+
pixelRatio: fp.pixel_ratio || 1,
|
|
962
|
+
timezone: fp.timezone || '',
|
|
963
|
+
locale: fp.locale || 'en-US',
|
|
964
|
+
audioNoiseSeed: fp.audio_noise_seed || 0,
|
|
965
|
+
installedFonts: fp.installed_fonts || [],
|
|
966
|
+
hasPdfPlugin: fp.has_pdf_plugin !== false,
|
|
967
|
+
hasChromePdf: fp.has_chrome_pdf !== false,
|
|
968
|
+
};
|
|
969
|
+
}
|
|
970
|
+
// Parse cookies
|
|
971
|
+
const cookies = (data.cookies || []).map((c) => ({
|
|
972
|
+
name: c.name || '',
|
|
973
|
+
value: c.value || '',
|
|
974
|
+
domain: c.domain || '',
|
|
975
|
+
path: c.path || '/',
|
|
976
|
+
secure: c.secure || false,
|
|
977
|
+
httpOnly: c.httpOnly || false,
|
|
978
|
+
sameSite: c.sameSite || 'lax',
|
|
979
|
+
expires: c.expires || -1,
|
|
980
|
+
}));
|
|
981
|
+
return {
|
|
982
|
+
profileId: data.profile_id || '',
|
|
983
|
+
profileName: data.profile_name || '',
|
|
984
|
+
createdAt: data.created_at || '',
|
|
985
|
+
modifiedAt: data.modified_at || '',
|
|
986
|
+
version: data.version || 1,
|
|
987
|
+
fingerprint,
|
|
988
|
+
cookies,
|
|
989
|
+
hasLlmConfig: data.has_llm_config || false,
|
|
990
|
+
llmConfig: data.llm_config ? {
|
|
991
|
+
enabled: data.llm_config.enabled ?? true,
|
|
992
|
+
useBuiltin: data.llm_config.use_builtin ?? true,
|
|
993
|
+
externalEndpoint: data.llm_config.external_endpoint,
|
|
994
|
+
externalModel: data.llm_config.external_model,
|
|
995
|
+
} : undefined,
|
|
996
|
+
hasProxyConfig: data.has_proxy_config || false,
|
|
997
|
+
proxyConfig: data.proxy_config ? {
|
|
998
|
+
type: data.proxy_config.type || 'http',
|
|
999
|
+
host: data.proxy_config.host || '',
|
|
1000
|
+
port: data.proxy_config.port || 0,
|
|
1001
|
+
username: data.proxy_config.username,
|
|
1002
|
+
password: data.proxy_config.password,
|
|
1003
|
+
} : undefined,
|
|
1004
|
+
autoSaveCookies: data.auto_save_cookies !== false,
|
|
1005
|
+
persistLocalStorage: data.persist_local_storage !== false,
|
|
1006
|
+
};
|
|
1007
|
+
}
|
|
1008
|
+
// ==================== CLEANUP ====================
|
|
1009
|
+
/**
|
|
1010
|
+
* Close this context and release resources
|
|
1011
|
+
*/
|
|
1012
|
+
async close() {
|
|
1013
|
+
await this.core.releaseContext(this.contextId);
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
exports.BrowserContext = BrowserContext;
|
|
1017
|
+
//# sourceMappingURL=context.js.map
|