erosolar-cli 1.5.14 → 1.6.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/dist/browser/BrowserSessionManager.d.ts +307 -0
- package/dist/browser/BrowserSessionManager.d.ts.map +1 -0
- package/dist/browser/BrowserSessionManager.js +713 -0
- package/dist/browser/BrowserSessionManager.js.map +1 -0
- package/dist/capabilities/browserAutomationCapability.d.ts +37 -0
- package/dist/capabilities/browserAutomationCapability.d.ts.map +1 -0
- package/dist/capabilities/browserAutomationCapability.js +49 -0
- package/dist/capabilities/browserAutomationCapability.js.map +1 -0
- package/dist/capabilities/frontendTestingCapability.d.ts +13 -0
- package/dist/capabilities/frontendTestingCapability.d.ts.map +1 -0
- package/dist/capabilities/frontendTestingCapability.js +28 -0
- package/dist/capabilities/frontendTestingCapability.js.map +1 -0
- package/dist/capabilities/index.d.ts +2 -0
- package/dist/capabilities/index.d.ts.map +1 -1
- package/dist/capabilities/index.js +2 -0
- package/dist/capabilities/index.js.map +1 -1
- package/dist/plugins/tools/browser/browserAutomationPlugin.d.ts +14 -0
- package/dist/plugins/tools/browser/browserAutomationPlugin.d.ts.map +1 -0
- package/dist/plugins/tools/browser/browserAutomationPlugin.js +26 -0
- package/dist/plugins/tools/browser/browserAutomationPlugin.js.map +1 -0
- package/dist/plugins/tools/frontendTesting/frontendTestingPlugin.d.ts +3 -0
- package/dist/plugins/tools/frontendTesting/frontendTestingPlugin.d.ts.map +1 -0
- package/dist/plugins/tools/frontendTesting/frontendTestingPlugin.js +14 -0
- package/dist/plugins/tools/frontendTesting/frontendTestingPlugin.js.map +1 -0
- package/dist/plugins/tools/nodeDefaults.d.ts.map +1 -1
- package/dist/plugins/tools/nodeDefaults.js +4 -0
- package/dist/plugins/tools/nodeDefaults.js.map +1 -1
- package/dist/tools/browserAutomationTools.d.ts +23 -0
- package/dist/tools/browserAutomationTools.d.ts.map +1 -0
- package/dist/tools/browserAutomationTools.js +908 -0
- package/dist/tools/browserAutomationTools.js.map +1 -0
- package/dist/tools/frontendTestingTools.d.ts +35 -0
- package/dist/tools/frontendTestingTools.d.ts.map +1 -0
- package/dist/tools/frontendTestingTools.js +1254 -0
- package/dist/tools/frontendTestingTools.js.map +1 -0
- package/package.json +4 -1
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Browser Session Manager - Manages browser instances, sessions, cookies, and authentication
|
|
3
|
+
*
|
|
4
|
+
* Provides browser-use-like functionality for AI agents:
|
|
5
|
+
* - Persistent browser sessions with cookie management
|
|
6
|
+
* - Authentication state persistence
|
|
7
|
+
* - Multi-profile support
|
|
8
|
+
* - Session recovery and cleanup
|
|
9
|
+
*
|
|
10
|
+
* @license MIT
|
|
11
|
+
* @author Bo Shang
|
|
12
|
+
*/
|
|
13
|
+
import { chromium, firefox, webkit } from 'playwright';
|
|
14
|
+
import * as fs from 'fs';
|
|
15
|
+
import * as path from 'path';
|
|
16
|
+
/**
|
|
17
|
+
* Browser Session Manager
|
|
18
|
+
* Manages browser instances, sessions, and authentication state
|
|
19
|
+
*/
|
|
20
|
+
export class BrowserSessionManager {
|
|
21
|
+
sessions = new Map();
|
|
22
|
+
storageDir;
|
|
23
|
+
constructor(storageDir) {
|
|
24
|
+
this.storageDir = storageDir || path.join(process.env['HOME'] || '~', '.erosolar', 'browser-sessions');
|
|
25
|
+
this.ensureStorageDir();
|
|
26
|
+
}
|
|
27
|
+
ensureStorageDir() {
|
|
28
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
29
|
+
fs.mkdirSync(this.storageDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a new browser session
|
|
34
|
+
*/
|
|
35
|
+
async createSession(config) {
|
|
36
|
+
const sessionId = config.sessionId || `session-${Date.now()}`;
|
|
37
|
+
// Check if session already exists
|
|
38
|
+
if (this.sessions.has(sessionId)) {
|
|
39
|
+
return sessionId;
|
|
40
|
+
}
|
|
41
|
+
// Select browser type
|
|
42
|
+
const browserType = config.browserType || 'chromium';
|
|
43
|
+
const browserLauncher = browserType === 'firefox' ? firefox : browserType === 'webkit' ? webkit : chromium;
|
|
44
|
+
// Build launch options
|
|
45
|
+
const launchOptions = {
|
|
46
|
+
headless: config.headless !== false,
|
|
47
|
+
};
|
|
48
|
+
if (config.proxy) {
|
|
49
|
+
launchOptions.proxy = config.proxy;
|
|
50
|
+
}
|
|
51
|
+
// Launch browser
|
|
52
|
+
const browser = await browserLauncher.launch(launchOptions);
|
|
53
|
+
// Build context options
|
|
54
|
+
const contextOptions = {
|
|
55
|
+
viewport: config.viewport || { width: 1920, height: 1080 },
|
|
56
|
+
};
|
|
57
|
+
if (config.userAgent) {
|
|
58
|
+
contextOptions.userAgent = config.userAgent;
|
|
59
|
+
}
|
|
60
|
+
// Apply stealth mode settings
|
|
61
|
+
if (config.stealthMode) {
|
|
62
|
+
contextOptions.userAgent = contextOptions.userAgent ||
|
|
63
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36';
|
|
64
|
+
contextOptions.locale = 'en-US';
|
|
65
|
+
contextOptions.timezoneId = 'America/New_York';
|
|
66
|
+
contextOptions.permissions = ['geolocation'];
|
|
67
|
+
contextOptions.extraHTTPHeaders = {
|
|
68
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
// Load existing session state if available
|
|
72
|
+
const savedState = await this.loadSessionState(sessionId);
|
|
73
|
+
if (savedState && savedState.cookies.length > 0) {
|
|
74
|
+
contextOptions.storageState = {
|
|
75
|
+
cookies: savedState.cookies.map(c => ({
|
|
76
|
+
name: c.name,
|
|
77
|
+
value: c.value,
|
|
78
|
+
domain: c.domain,
|
|
79
|
+
path: c.path,
|
|
80
|
+
expires: c.expires || -1,
|
|
81
|
+
httpOnly: c.httpOnly || false,
|
|
82
|
+
secure: c.secure || false,
|
|
83
|
+
sameSite: c.sameSite || 'Lax',
|
|
84
|
+
})),
|
|
85
|
+
origins: [],
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Create browser context
|
|
89
|
+
const context = await browser.newContext(contextOptions);
|
|
90
|
+
// Apply stealth scripts
|
|
91
|
+
if (config.stealthMode) {
|
|
92
|
+
await this.applyStealthScripts(context);
|
|
93
|
+
}
|
|
94
|
+
// Create initial page
|
|
95
|
+
const page = await context.newPage();
|
|
96
|
+
// Set default timeout
|
|
97
|
+
page.setDefaultTimeout(config.timeout || 30000);
|
|
98
|
+
// Create session state
|
|
99
|
+
const state = {
|
|
100
|
+
sessionId,
|
|
101
|
+
browserType,
|
|
102
|
+
isActive: true,
|
|
103
|
+
createdAt: Date.now(),
|
|
104
|
+
lastAccessedAt: Date.now(),
|
|
105
|
+
isAuthenticated: savedState?.isAuthenticated || false,
|
|
106
|
+
authDomain: savedState?.authDomain,
|
|
107
|
+
cookies: savedState?.cookies || [],
|
|
108
|
+
};
|
|
109
|
+
// Store active session
|
|
110
|
+
this.sessions.set(sessionId, {
|
|
111
|
+
config,
|
|
112
|
+
browser,
|
|
113
|
+
context,
|
|
114
|
+
page,
|
|
115
|
+
state,
|
|
116
|
+
});
|
|
117
|
+
return sessionId;
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Apply stealth scripts to avoid bot detection
|
|
121
|
+
*/
|
|
122
|
+
async applyStealthScripts(context) {
|
|
123
|
+
await context.addInitScript(() => {
|
|
124
|
+
// Override webdriver detection
|
|
125
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
126
|
+
get: () => undefined,
|
|
127
|
+
});
|
|
128
|
+
// Override plugins
|
|
129
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
130
|
+
get: () => [1, 2, 3, 4, 5],
|
|
131
|
+
});
|
|
132
|
+
// Override languages
|
|
133
|
+
Object.defineProperty(navigator, 'languages', {
|
|
134
|
+
get: () => ['en-US', 'en'],
|
|
135
|
+
});
|
|
136
|
+
// Override permissions
|
|
137
|
+
const originalQuery = window.navigator.permissions.query;
|
|
138
|
+
window.navigator.permissions.query = (parameters) => parameters.name === 'notifications'
|
|
139
|
+
? Promise.resolve({ state: Notification.permission })
|
|
140
|
+
: originalQuery(parameters);
|
|
141
|
+
// Override chrome runtime
|
|
142
|
+
window.chrome = {
|
|
143
|
+
runtime: {},
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Get an active session
|
|
149
|
+
*/
|
|
150
|
+
getSession(sessionId) {
|
|
151
|
+
const session = this.sessions.get(sessionId);
|
|
152
|
+
if (session) {
|
|
153
|
+
session.state.lastAccessedAt = Date.now();
|
|
154
|
+
}
|
|
155
|
+
return session;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get the page for a session
|
|
159
|
+
*/
|
|
160
|
+
getPage(sessionId) {
|
|
161
|
+
return this.getSession(sessionId)?.page;
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Navigate to a URL
|
|
165
|
+
*/
|
|
166
|
+
async navigateTo(sessionId, url, options) {
|
|
167
|
+
const session = this.getSession(sessionId);
|
|
168
|
+
if (!session) {
|
|
169
|
+
return { success: false, url: '', title: '', error: `Session ${sessionId} not found` };
|
|
170
|
+
}
|
|
171
|
+
try {
|
|
172
|
+
const response = await session.page.goto(url, {
|
|
173
|
+
waitUntil: options?.waitUntil || 'domcontentloaded',
|
|
174
|
+
});
|
|
175
|
+
session.state.currentUrl = session.page.url();
|
|
176
|
+
return {
|
|
177
|
+
success: response?.ok() ?? true,
|
|
178
|
+
url: session.page.url(),
|
|
179
|
+
title: await session.page.title(),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
catch (error) {
|
|
183
|
+
return {
|
|
184
|
+
success: false,
|
|
185
|
+
url: url,
|
|
186
|
+
title: '',
|
|
187
|
+
error: error instanceof Error ? error.message : String(error),
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Perform authentication
|
|
193
|
+
*/
|
|
194
|
+
async authenticate(sessionId, authConfig) {
|
|
195
|
+
const session = this.getSession(sessionId);
|
|
196
|
+
if (!session) {
|
|
197
|
+
return { success: false, message: `Session ${sessionId} not found` };
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
switch (authConfig.type) {
|
|
201
|
+
case 'form':
|
|
202
|
+
return await this.performFormAuth(session, authConfig);
|
|
203
|
+
case 'basic':
|
|
204
|
+
return await this.performBasicAuth(session, authConfig);
|
|
205
|
+
case 'cookie':
|
|
206
|
+
return await this.performCookieAuth(session, authConfig);
|
|
207
|
+
case 'token':
|
|
208
|
+
return await this.performTokenAuth(session, authConfig);
|
|
209
|
+
case 'oauth':
|
|
210
|
+
return { success: false, message: 'OAuth authentication requires manual interaction' };
|
|
211
|
+
default:
|
|
212
|
+
return { success: false, message: `Unknown auth type: ${authConfig.type}` };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
catch (error) {
|
|
216
|
+
return {
|
|
217
|
+
success: false,
|
|
218
|
+
message: error instanceof Error ? error.message : String(error),
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Perform form-based authentication
|
|
224
|
+
*/
|
|
225
|
+
async performFormAuth(session, authConfig) {
|
|
226
|
+
const { page } = session;
|
|
227
|
+
const { credentials, formSelectors, loginUrl, successUrl, successSelector } = authConfig;
|
|
228
|
+
if (!credentials?.username || !credentials?.password) {
|
|
229
|
+
return { success: false, message: 'Username and password required for form auth' };
|
|
230
|
+
}
|
|
231
|
+
if (!formSelectors) {
|
|
232
|
+
return { success: false, message: 'Form selectors required for form auth' };
|
|
233
|
+
}
|
|
234
|
+
// Navigate to login page if specified
|
|
235
|
+
if (loginUrl) {
|
|
236
|
+
await page.goto(loginUrl, { waitUntil: 'domcontentloaded' });
|
|
237
|
+
}
|
|
238
|
+
// Fill in credentials
|
|
239
|
+
await page.fill(formSelectors.usernameField, credentials.username);
|
|
240
|
+
await page.fill(formSelectors.passwordField, credentials.password);
|
|
241
|
+
// Submit form
|
|
242
|
+
await page.click(formSelectors.submitButton);
|
|
243
|
+
// Wait for navigation or success indicator
|
|
244
|
+
if (successUrl) {
|
|
245
|
+
await page.waitForURL(successUrl, { timeout: 10000 }).catch(() => { });
|
|
246
|
+
}
|
|
247
|
+
else if (successSelector) {
|
|
248
|
+
await page.waitForSelector(successSelector, { timeout: 10000 }).catch(() => { });
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
await page.waitForLoadState('networkidle');
|
|
252
|
+
}
|
|
253
|
+
// Check if authentication succeeded
|
|
254
|
+
const currentUrl = page.url();
|
|
255
|
+
const isSuccess = successUrl ? currentUrl.includes(successUrl) :
|
|
256
|
+
successSelector ? await page.$(successSelector) !== null : true;
|
|
257
|
+
if (isSuccess) {
|
|
258
|
+
session.state.isAuthenticated = true;
|
|
259
|
+
session.state.authDomain = new URL(currentUrl).hostname;
|
|
260
|
+
await this.saveCookies(session);
|
|
261
|
+
return { success: true, message: 'Authentication successful' };
|
|
262
|
+
}
|
|
263
|
+
return { success: false, message: 'Authentication failed - could not verify success' };
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Perform basic HTTP authentication
|
|
267
|
+
*/
|
|
268
|
+
async performBasicAuth(session, authConfig) {
|
|
269
|
+
const { context } = session;
|
|
270
|
+
const { credentials } = authConfig;
|
|
271
|
+
if (!credentials?.username || !credentials?.password) {
|
|
272
|
+
return { success: false, message: 'Username and password required for basic auth' };
|
|
273
|
+
}
|
|
274
|
+
await context.setHTTPCredentials({
|
|
275
|
+
username: credentials.username,
|
|
276
|
+
password: credentials.password,
|
|
277
|
+
});
|
|
278
|
+
session.state.isAuthenticated = true;
|
|
279
|
+
return { success: true, message: 'Basic auth credentials set' };
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Perform cookie-based authentication
|
|
283
|
+
*/
|
|
284
|
+
async performCookieAuth(session, _authConfig) {
|
|
285
|
+
const { context } = session;
|
|
286
|
+
// Load cookies from storage
|
|
287
|
+
const savedState = await this.loadSessionState(session.state.sessionId);
|
|
288
|
+
if (savedState && savedState.cookies.length > 0) {
|
|
289
|
+
await context.addCookies(savedState.cookies.map(c => ({
|
|
290
|
+
name: c.name,
|
|
291
|
+
value: c.value,
|
|
292
|
+
domain: c.domain,
|
|
293
|
+
path: c.path,
|
|
294
|
+
expires: c.expires || -1,
|
|
295
|
+
httpOnly: c.httpOnly || false,
|
|
296
|
+
secure: c.secure || false,
|
|
297
|
+
sameSite: c.sameSite || 'Lax',
|
|
298
|
+
})));
|
|
299
|
+
session.state.isAuthenticated = true;
|
|
300
|
+
return { success: true, message: 'Cookies loaded from storage' };
|
|
301
|
+
}
|
|
302
|
+
return { success: false, message: 'No stored cookies found' };
|
|
303
|
+
}
|
|
304
|
+
/**
|
|
305
|
+
* Perform token-based authentication
|
|
306
|
+
*/
|
|
307
|
+
async performTokenAuth(session, authConfig) {
|
|
308
|
+
const { context } = session;
|
|
309
|
+
const { credentials } = authConfig;
|
|
310
|
+
if (!credentials?.token) {
|
|
311
|
+
return { success: false, message: 'Token required for token auth' };
|
|
312
|
+
}
|
|
313
|
+
// Set authorization header for all requests
|
|
314
|
+
await context.setExtraHTTPHeaders({
|
|
315
|
+
'Authorization': `Bearer ${credentials.token}`,
|
|
316
|
+
});
|
|
317
|
+
session.state.isAuthenticated = true;
|
|
318
|
+
return { success: true, message: 'Token auth header set' };
|
|
319
|
+
}
|
|
320
|
+
/**
|
|
321
|
+
* Save cookies for a session
|
|
322
|
+
*/
|
|
323
|
+
async saveCookies(session) {
|
|
324
|
+
const cookies = await session.context.cookies();
|
|
325
|
+
session.state.cookies = cookies.map(c => ({
|
|
326
|
+
name: c.name,
|
|
327
|
+
value: c.value,
|
|
328
|
+
domain: c.domain,
|
|
329
|
+
path: c.path,
|
|
330
|
+
expires: c.expires,
|
|
331
|
+
httpOnly: c.httpOnly,
|
|
332
|
+
secure: c.secure,
|
|
333
|
+
sameSite: c.sameSite,
|
|
334
|
+
}));
|
|
335
|
+
await this.saveSessionState(session.state);
|
|
336
|
+
}
|
|
337
|
+
/**
|
|
338
|
+
* Save session state to disk
|
|
339
|
+
*/
|
|
340
|
+
async saveSessionState(state) {
|
|
341
|
+
const filePath = path.join(this.storageDir, `${state.sessionId}.json`);
|
|
342
|
+
fs.writeFileSync(filePath, JSON.stringify(state, null, 2), 'utf-8');
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* Load session state from disk
|
|
346
|
+
*/
|
|
347
|
+
async loadSessionState(sessionId) {
|
|
348
|
+
const filePath = path.join(this.storageDir, `${sessionId}.json`);
|
|
349
|
+
if (fs.existsSync(filePath)) {
|
|
350
|
+
try {
|
|
351
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
352
|
+
return JSON.parse(content);
|
|
353
|
+
}
|
|
354
|
+
catch {
|
|
355
|
+
return null;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
/**
|
|
361
|
+
* Take a screenshot
|
|
362
|
+
*/
|
|
363
|
+
async takeScreenshot(sessionId, options) {
|
|
364
|
+
const session = this.getSession(sessionId);
|
|
365
|
+
if (!session) {
|
|
366
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
367
|
+
}
|
|
368
|
+
try {
|
|
369
|
+
const screenshotOptions = {
|
|
370
|
+
fullPage: options?.fullPage || false,
|
|
371
|
+
type: 'png',
|
|
372
|
+
};
|
|
373
|
+
if (options?.path) {
|
|
374
|
+
screenshotOptions.path = options.path;
|
|
375
|
+
await session.page.screenshot(screenshotOptions);
|
|
376
|
+
return { success: true, path: options.path };
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
const buffer = await session.page.screenshot(screenshotOptions);
|
|
380
|
+
return { success: true, data: buffer.toString('base64') };
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
catch (error) {
|
|
384
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* Click on an element
|
|
389
|
+
*/
|
|
390
|
+
async click(sessionId, selector, options) {
|
|
391
|
+
const session = this.getSession(sessionId);
|
|
392
|
+
if (!session) {
|
|
393
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
394
|
+
}
|
|
395
|
+
try {
|
|
396
|
+
await session.page.click(selector, {
|
|
397
|
+
timeout: options?.timeout || 10000,
|
|
398
|
+
force: options?.force || false,
|
|
399
|
+
});
|
|
400
|
+
return { success: true };
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Type text into an element
|
|
408
|
+
*/
|
|
409
|
+
async type(sessionId, selector, text, options) {
|
|
410
|
+
const session = this.getSession(sessionId);
|
|
411
|
+
if (!session) {
|
|
412
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
413
|
+
}
|
|
414
|
+
try {
|
|
415
|
+
if (options?.clear) {
|
|
416
|
+
await session.page.fill(selector, '');
|
|
417
|
+
}
|
|
418
|
+
await session.page.type(selector, text, { delay: options?.delay || 50 });
|
|
419
|
+
return { success: true };
|
|
420
|
+
}
|
|
421
|
+
catch (error) {
|
|
422
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Fill an input field (faster than type)
|
|
427
|
+
*/
|
|
428
|
+
async fill(sessionId, selector, value) {
|
|
429
|
+
const session = this.getSession(sessionId);
|
|
430
|
+
if (!session) {
|
|
431
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
432
|
+
}
|
|
433
|
+
try {
|
|
434
|
+
await session.page.fill(selector, value);
|
|
435
|
+
return { success: true };
|
|
436
|
+
}
|
|
437
|
+
catch (error) {
|
|
438
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
/**
|
|
442
|
+
* Get text content of an element
|
|
443
|
+
*/
|
|
444
|
+
async getText(sessionId, selector) {
|
|
445
|
+
const session = this.getSession(sessionId);
|
|
446
|
+
if (!session) {
|
|
447
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
448
|
+
}
|
|
449
|
+
try {
|
|
450
|
+
const text = await session.page.textContent(selector);
|
|
451
|
+
return { success: true, text: text || '' };
|
|
452
|
+
}
|
|
453
|
+
catch (error) {
|
|
454
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Get page HTML content
|
|
459
|
+
*/
|
|
460
|
+
async getPageContent(sessionId) {
|
|
461
|
+
const session = this.getSession(sessionId);
|
|
462
|
+
if (!session) {
|
|
463
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
464
|
+
}
|
|
465
|
+
try {
|
|
466
|
+
const html = await session.page.content();
|
|
467
|
+
return { success: true, html };
|
|
468
|
+
}
|
|
469
|
+
catch (error) {
|
|
470
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Wait for a selector to appear
|
|
475
|
+
*/
|
|
476
|
+
async waitForSelector(sessionId, selector, options) {
|
|
477
|
+
const session = this.getSession(sessionId);
|
|
478
|
+
if (!session) {
|
|
479
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
480
|
+
}
|
|
481
|
+
try {
|
|
482
|
+
await session.page.waitForSelector(selector, {
|
|
483
|
+
timeout: options?.timeout || 30000,
|
|
484
|
+
state: options?.state || 'visible',
|
|
485
|
+
});
|
|
486
|
+
return { success: true };
|
|
487
|
+
}
|
|
488
|
+
catch (error) {
|
|
489
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Execute JavaScript in the page context
|
|
494
|
+
*/
|
|
495
|
+
async executeScript(sessionId, script, args) {
|
|
496
|
+
const session = this.getSession(sessionId);
|
|
497
|
+
if (!session) {
|
|
498
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
499
|
+
}
|
|
500
|
+
try {
|
|
501
|
+
const result = await session.page.evaluate(script, args);
|
|
502
|
+
return { success: true, result: result };
|
|
503
|
+
}
|
|
504
|
+
catch (error) {
|
|
505
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
/**
|
|
509
|
+
* Select an option from a dropdown
|
|
510
|
+
*/
|
|
511
|
+
async selectOption(sessionId, selector, value) {
|
|
512
|
+
const session = this.getSession(sessionId);
|
|
513
|
+
if (!session) {
|
|
514
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
515
|
+
}
|
|
516
|
+
try {
|
|
517
|
+
await session.page.selectOption(selector, value);
|
|
518
|
+
return { success: true };
|
|
519
|
+
}
|
|
520
|
+
catch (error) {
|
|
521
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
/**
|
|
525
|
+
* Check/uncheck a checkbox
|
|
526
|
+
*/
|
|
527
|
+
async setChecked(sessionId, selector, checked) {
|
|
528
|
+
const session = this.getSession(sessionId);
|
|
529
|
+
if (!session) {
|
|
530
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
531
|
+
}
|
|
532
|
+
try {
|
|
533
|
+
await session.page.setChecked(selector, checked);
|
|
534
|
+
return { success: true };
|
|
535
|
+
}
|
|
536
|
+
catch (error) {
|
|
537
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
/**
|
|
541
|
+
* Upload a file
|
|
542
|
+
*/
|
|
543
|
+
async uploadFile(sessionId, selector, filePath) {
|
|
544
|
+
const session = this.getSession(sessionId);
|
|
545
|
+
if (!session) {
|
|
546
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
547
|
+
}
|
|
548
|
+
try {
|
|
549
|
+
await session.page.setInputFiles(selector, filePath);
|
|
550
|
+
return { success: true };
|
|
551
|
+
}
|
|
552
|
+
catch (error) {
|
|
553
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Get all cookies for the session
|
|
558
|
+
*/
|
|
559
|
+
async getCookies(sessionId, urls) {
|
|
560
|
+
const session = this.getSession(sessionId);
|
|
561
|
+
if (!session) {
|
|
562
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
563
|
+
}
|
|
564
|
+
try {
|
|
565
|
+
const cookies = await session.context.cookies(urls);
|
|
566
|
+
return {
|
|
567
|
+
success: true,
|
|
568
|
+
cookies: cookies.map(c => ({
|
|
569
|
+
name: c.name,
|
|
570
|
+
value: c.value,
|
|
571
|
+
domain: c.domain,
|
|
572
|
+
path: c.path,
|
|
573
|
+
expires: c.expires,
|
|
574
|
+
httpOnly: c.httpOnly,
|
|
575
|
+
secure: c.secure,
|
|
576
|
+
sameSite: c.sameSite,
|
|
577
|
+
})),
|
|
578
|
+
};
|
|
579
|
+
}
|
|
580
|
+
catch (error) {
|
|
581
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Add cookies to the session
|
|
586
|
+
*/
|
|
587
|
+
async addCookies(sessionId, cookies) {
|
|
588
|
+
const session = this.getSession(sessionId);
|
|
589
|
+
if (!session) {
|
|
590
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
591
|
+
}
|
|
592
|
+
try {
|
|
593
|
+
await session.context.addCookies(cookies.map(c => ({
|
|
594
|
+
name: c.name,
|
|
595
|
+
value: c.value,
|
|
596
|
+
domain: c.domain,
|
|
597
|
+
path: c.path,
|
|
598
|
+
expires: c.expires || -1,
|
|
599
|
+
httpOnly: c.httpOnly || false,
|
|
600
|
+
secure: c.secure || false,
|
|
601
|
+
sameSite: c.sameSite || 'Lax',
|
|
602
|
+
})));
|
|
603
|
+
return { success: true };
|
|
604
|
+
}
|
|
605
|
+
catch (error) {
|
|
606
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
/**
|
|
610
|
+
* Clear cookies for the session
|
|
611
|
+
*/
|
|
612
|
+
async clearCookies(sessionId) {
|
|
613
|
+
const session = this.getSession(sessionId);
|
|
614
|
+
if (!session) {
|
|
615
|
+
return { success: false, error: `Session ${sessionId} not found` };
|
|
616
|
+
}
|
|
617
|
+
try {
|
|
618
|
+
await session.context.clearCookies();
|
|
619
|
+
session.state.cookies = [];
|
|
620
|
+
session.state.isAuthenticated = false;
|
|
621
|
+
return { success: true };
|
|
622
|
+
}
|
|
623
|
+
catch (error) {
|
|
624
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Close a session
|
|
629
|
+
*/
|
|
630
|
+
async closeSession(sessionId, saveCookies = true) {
|
|
631
|
+
const session = this.sessions.get(sessionId);
|
|
632
|
+
if (session) {
|
|
633
|
+
if (saveCookies) {
|
|
634
|
+
await this.saveCookies(session);
|
|
635
|
+
}
|
|
636
|
+
session.state.isActive = false;
|
|
637
|
+
await session.page.close().catch(() => { });
|
|
638
|
+
await session.context.close().catch(() => { });
|
|
639
|
+
await session.browser.close().catch(() => { });
|
|
640
|
+
this.sessions.delete(sessionId);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
/**
|
|
644
|
+
* Close all sessions
|
|
645
|
+
*/
|
|
646
|
+
async closeAllSessions() {
|
|
647
|
+
for (const sessionId of this.sessions.keys()) {
|
|
648
|
+
await this.closeSession(sessionId);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* List all stored sessions
|
|
653
|
+
*/
|
|
654
|
+
listStoredSessions() {
|
|
655
|
+
if (!fs.existsSync(this.storageDir)) {
|
|
656
|
+
return [];
|
|
657
|
+
}
|
|
658
|
+
return fs.readdirSync(this.storageDir)
|
|
659
|
+
.filter(f => f.endsWith('.json'))
|
|
660
|
+
.map(f => f.replace('.json', ''));
|
|
661
|
+
}
|
|
662
|
+
/**
|
|
663
|
+
* Delete stored session data
|
|
664
|
+
*/
|
|
665
|
+
deleteStoredSession(sessionId) {
|
|
666
|
+
const filePath = path.join(this.storageDir, `${sessionId}.json`);
|
|
667
|
+
if (fs.existsSync(filePath)) {
|
|
668
|
+
fs.unlinkSync(filePath);
|
|
669
|
+
return true;
|
|
670
|
+
}
|
|
671
|
+
return false;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Get session info
|
|
675
|
+
*/
|
|
676
|
+
getSessionInfo(sessionId) {
|
|
677
|
+
const session = this.sessions.get(sessionId);
|
|
678
|
+
if (session) {
|
|
679
|
+
return { ...session.state };
|
|
680
|
+
}
|
|
681
|
+
// For stored sessions, we need to load synchronously
|
|
682
|
+
const filePath = path.join(this.storageDir, `${sessionId}.json`);
|
|
683
|
+
if (fs.existsSync(filePath)) {
|
|
684
|
+
try {
|
|
685
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
686
|
+
return JSON.parse(content);
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
return null;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Check if session is authenticated
|
|
696
|
+
*/
|
|
697
|
+
isAuthenticated(sessionId) {
|
|
698
|
+
const session = this.sessions.get(sessionId);
|
|
699
|
+
return session?.state.isAuthenticated || false;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
// Export singleton instance
|
|
703
|
+
let defaultManager = null;
|
|
704
|
+
export function getDefaultBrowserSessionManager() {
|
|
705
|
+
if (!defaultManager) {
|
|
706
|
+
defaultManager = new BrowserSessionManager();
|
|
707
|
+
}
|
|
708
|
+
return defaultManager;
|
|
709
|
+
}
|
|
710
|
+
export function createBrowserSessionManager(storageDir) {
|
|
711
|
+
return new BrowserSessionManager(storageDir);
|
|
712
|
+
}
|
|
713
|
+
//# sourceMappingURL=BrowserSessionManager.js.map
|