@monostate/browsernative-client 2.0.0 → 2.2.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 +44 -0
- package/index.d.ts +113 -0
- package/index.js +168 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,50 @@ const client = new BrowserNativeClient(apiKey, {
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
### Browser sessions
|
|
116
|
+
|
|
117
|
+
Persistent browser sessions with real-time control over WebSocket:
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
const session = await client.createSession({ mode: 'auto' });
|
|
121
|
+
|
|
122
|
+
await session.goto('https://example.com');
|
|
123
|
+
await session.click('#login');
|
|
124
|
+
await session.type('#email', 'user@example.com');
|
|
125
|
+
const state = await session.getPageState({ includeScreenshot: true });
|
|
126
|
+
await session.screenshot();
|
|
127
|
+
|
|
128
|
+
// Listen for backend fallback events
|
|
129
|
+
session.on('fallback', (e) => console.log(`${e.from} -> ${e.to}: ${e.reason}`));
|
|
130
|
+
|
|
131
|
+
session.close();
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
Modes: `auto` (LightPanda + Chrome fallback), `headless`, `visual`, `computer-use`.
|
|
135
|
+
|
|
136
|
+
#### Computer use mode
|
|
137
|
+
|
|
138
|
+
For AI agents that control the browser by pixel coordinates:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const session = await client.createSession({ mode: 'computer-use', screenWidth: 1280, screenHeight: 800 });
|
|
142
|
+
|
|
143
|
+
await session.goto('https://example.com');
|
|
144
|
+
await session.clickAt(640, 400);
|
|
145
|
+
await session.typeText('hello world');
|
|
146
|
+
await session.mouseMove(100, 200);
|
|
147
|
+
await session.drag(10, 20, 300, 400);
|
|
148
|
+
await session.scrollAt(640, 400, 'down', 5);
|
|
149
|
+
const pos = await session.getCursorPosition();
|
|
150
|
+
const size = await session.getScreenSize();
|
|
151
|
+
const screenshot = await session.screenshot();
|
|
152
|
+
|
|
153
|
+
// VNC streaming URL
|
|
154
|
+
console.log(session.vncUrl);
|
|
155
|
+
|
|
156
|
+
session.close();
|
|
157
|
+
```
|
|
158
|
+
|
|
115
159
|
## TypeScript
|
|
116
160
|
|
|
117
161
|
Full type definitions included.
|
package/index.d.ts
CHANGED
|
@@ -168,6 +168,114 @@ export interface HealthResult {
|
|
|
168
168
|
responseTime: number;
|
|
169
169
|
}
|
|
170
170
|
|
|
171
|
+
export interface SessionOptions {
|
|
172
|
+
/** Browser mode (default: 'auto') */
|
|
173
|
+
mode?: 'auto' | 'headless' | 'visual' | 'computer-use';
|
|
174
|
+
/** Screen width for computer-use mode */
|
|
175
|
+
screenWidth?: number;
|
|
176
|
+
/** Screen height for computer-use mode */
|
|
177
|
+
screenHeight?: number;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export interface PageState {
|
|
181
|
+
url: string;
|
|
182
|
+
title: string;
|
|
183
|
+
content?: string;
|
|
184
|
+
screenshot?: string;
|
|
185
|
+
interactiveElements?: Array<{
|
|
186
|
+
tag: string;
|
|
187
|
+
text?: string;
|
|
188
|
+
selector: string;
|
|
189
|
+
type?: string;
|
|
190
|
+
href?: string;
|
|
191
|
+
}>;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
export interface FallbackEvent {
|
|
195
|
+
type: 'fallback';
|
|
196
|
+
from: string;
|
|
197
|
+
to: string;
|
|
198
|
+
reason: string;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export interface ClosedEvent {
|
|
202
|
+
type: 'closed';
|
|
203
|
+
reason: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export interface Cookie {
|
|
207
|
+
name: string;
|
|
208
|
+
value: string;
|
|
209
|
+
domain?: string;
|
|
210
|
+
path?: string;
|
|
211
|
+
expires?: number;
|
|
212
|
+
httpOnly?: boolean;
|
|
213
|
+
secure?: boolean;
|
|
214
|
+
sameSite?: 'Strict' | 'Lax' | 'None';
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Persistent browser session over WebSocket
|
|
219
|
+
*/
|
|
220
|
+
export declare class BrowserSession {
|
|
221
|
+
/** Session ID assigned by the server */
|
|
222
|
+
sessionId: string | null;
|
|
223
|
+
/** Current browser backend ('lightpanda', 'chrome', or 'computer-use') */
|
|
224
|
+
backend: string | null;
|
|
225
|
+
/** VNC URL for computer-use mode (null otherwise) */
|
|
226
|
+
vncUrl: string | null;
|
|
227
|
+
|
|
228
|
+
/** Navigate to a URL */
|
|
229
|
+
goto(url: string): Promise<any>;
|
|
230
|
+
/** Click an element */
|
|
231
|
+
click(selector: string, options?: { timeout?: number }): Promise<any>;
|
|
232
|
+
/** Type text into an element */
|
|
233
|
+
type(selector: string, text: string, options?: { clear?: boolean; delay?: number }): Promise<any>;
|
|
234
|
+
/** Scroll the page */
|
|
235
|
+
scroll(direction?: 'up' | 'down', amount?: number): Promise<any>;
|
|
236
|
+
/** Hover over an element */
|
|
237
|
+
hover(selector: string): Promise<any>;
|
|
238
|
+
/** Select option(s) in a dropdown */
|
|
239
|
+
select(selector: string, ...values: string[]): Promise<any>;
|
|
240
|
+
/** Press a keyboard key */
|
|
241
|
+
pressKey(key: string): Promise<any>;
|
|
242
|
+
/** Navigate back */
|
|
243
|
+
goBack(): Promise<any>;
|
|
244
|
+
/** Navigate forward */
|
|
245
|
+
goForward(): Promise<any>;
|
|
246
|
+
/** Take a screenshot */
|
|
247
|
+
screenshot(options?: { fullPage?: boolean; type?: 'png' | 'jpeg' | 'webp'; quality?: number }): Promise<any>;
|
|
248
|
+
/** Get current page state with interactive elements */
|
|
249
|
+
getPageState(options?: { includeScreenshot?: boolean }): Promise<PageState>;
|
|
250
|
+
/** Extract page content as structured data */
|
|
251
|
+
extractContent(): Promise<any>;
|
|
252
|
+
/** Wait for a selector to appear */
|
|
253
|
+
waitFor(selector: string, timeout?: number): Promise<any>;
|
|
254
|
+
/** Evaluate JavaScript in the page context */
|
|
255
|
+
evaluate(fn: string): Promise<any>;
|
|
256
|
+
/** Get all cookies */
|
|
257
|
+
getCookies(): Promise<Cookie[]>;
|
|
258
|
+
/** Set cookies */
|
|
259
|
+
setCookies(cookies: Cookie[]): Promise<any>;
|
|
260
|
+
|
|
261
|
+
/** Coordinate-based actions (computer-use mode) */
|
|
262
|
+
mouseMove(x: number, y: number): Promise<any>;
|
|
263
|
+
clickAt(x: number, y: number, button?: 'left' | 'right' | 'middle'): Promise<any>;
|
|
264
|
+
doubleClickAt(x: number, y: number, button?: 'left' | 'right' | 'middle'): Promise<any>;
|
|
265
|
+
drag(startX: number, startY: number, endX: number, endY: number): Promise<any>;
|
|
266
|
+
scrollAt(x: number, y: number, direction: 'up' | 'down', amount?: number): Promise<any>;
|
|
267
|
+
typeText(text: string): Promise<any>;
|
|
268
|
+
getCursorPosition(): Promise<{ x: number; y: number }>;
|
|
269
|
+
getScreenSize(): Promise<{ width: number; height: number }>;
|
|
270
|
+
|
|
271
|
+
/** Listen for session events */
|
|
272
|
+
on(event: 'fallback', handler: (event: FallbackEvent) => void): this;
|
|
273
|
+
on(event: 'closed', handler: (event: ClosedEvent) => void): this;
|
|
274
|
+
|
|
275
|
+
/** Close the session */
|
|
276
|
+
close(): void;
|
|
277
|
+
}
|
|
278
|
+
|
|
171
279
|
/**
|
|
172
280
|
* Browser Native API Client
|
|
173
281
|
*/
|
|
@@ -204,6 +312,11 @@ export declare class BrowserNativeClient {
|
|
|
204
312
|
*/
|
|
205
313
|
getUsage(days?: number): Promise<UsageResult>;
|
|
206
314
|
|
|
315
|
+
/**
|
|
316
|
+
* Create a persistent browser session via WebSocket
|
|
317
|
+
*/
|
|
318
|
+
createSession(options?: SessionOptions): Promise<BrowserSession>;
|
|
319
|
+
|
|
207
320
|
/**
|
|
208
321
|
* Check API health and your account status
|
|
209
322
|
*/
|
package/index.js
CHANGED
|
@@ -143,6 +143,30 @@ export class BrowserNativeClient {
|
|
|
143
143
|
return this._makeRequest('/stats', {}, 'GET');
|
|
144
144
|
}
|
|
145
145
|
|
|
146
|
+
/**
|
|
147
|
+
* Create a persistent browser session via WebSocket
|
|
148
|
+
* @param {object} options - Session options
|
|
149
|
+
* @param {'auto'|'headless'|'visual'|'computer-use'} options.mode - Browser mode (default: 'auto')
|
|
150
|
+
* @param {number} [options.screenWidth] - Screen width for computer-use mode
|
|
151
|
+
* @param {number} [options.screenHeight] - Screen height for computer-use mode
|
|
152
|
+
* @returns {Promise<BrowserSession>} Connected browser session
|
|
153
|
+
*/
|
|
154
|
+
async createSession(options = {}) {
|
|
155
|
+
const wsUrl = this.baseUrl.replace(/^http/, 'ws');
|
|
156
|
+
const mode = options.mode || 'auto';
|
|
157
|
+
const params = new URLSearchParams({
|
|
158
|
+
apiKey: this.apiKey,
|
|
159
|
+
mode,
|
|
160
|
+
});
|
|
161
|
+
if (options.screenWidth) params.set('screenWidth', String(options.screenWidth));
|
|
162
|
+
if (options.screenHeight) params.set('screenHeight', String(options.screenHeight));
|
|
163
|
+
const url = `${wsUrl}/session?${params}`;
|
|
164
|
+
|
|
165
|
+
const session = new BrowserSession(url, { verbose: this.verbose });
|
|
166
|
+
await session.connect();
|
|
167
|
+
return session;
|
|
168
|
+
}
|
|
169
|
+
|
|
146
170
|
/**
|
|
147
171
|
* Check API health and your account status
|
|
148
172
|
* @returns {Promise<object>} Health check result
|
|
@@ -294,5 +318,149 @@ export async function bulkScrape(urls, apiKey, options = {}) {
|
|
|
294
318
|
return client.bulkScrape(urls, options);
|
|
295
319
|
}
|
|
296
320
|
|
|
321
|
+
// ── Browser Session (WebSocket) ──────────────────────────────────────────────
|
|
322
|
+
|
|
323
|
+
class BrowserSession {
|
|
324
|
+
constructor(url, options = {}) {
|
|
325
|
+
this._url = url;
|
|
326
|
+
this._ws = null;
|
|
327
|
+
this._nextId = 1;
|
|
328
|
+
this._pending = new Map(); // id → { resolve, reject }
|
|
329
|
+
this._verbose = options.verbose || false;
|
|
330
|
+
this._eventHandlers = {};
|
|
331
|
+
this.sessionId = null;
|
|
332
|
+
this.backend = null;
|
|
333
|
+
this.vncUrl = null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
async connect() {
|
|
337
|
+
return new Promise((resolve, reject) => {
|
|
338
|
+
const WebSocketImpl = typeof WebSocket !== 'undefined'
|
|
339
|
+
? WebSocket
|
|
340
|
+
: null;
|
|
341
|
+
|
|
342
|
+
if (!WebSocketImpl) {
|
|
343
|
+
// Node.js — try dynamic import
|
|
344
|
+
import('ws').then(ws => {
|
|
345
|
+
this._ws = new ws.default(this._url);
|
|
346
|
+
this._wireEvents(resolve, reject);
|
|
347
|
+
}).catch(() => {
|
|
348
|
+
reject(new Error('WebSocket not available. Install "ws" package for Node.js: npm install ws'));
|
|
349
|
+
});
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
this._ws = new WebSocketImpl(this._url);
|
|
354
|
+
this._wireEvents(resolve, reject);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
_wireEvents(onConnected, onError) {
|
|
359
|
+
this._ws.onmessage = (event) => {
|
|
360
|
+
const data = typeof event.data === 'string' ? event.data : event.data.toString();
|
|
361
|
+
let msg;
|
|
362
|
+
try { msg = JSON.parse(data); } catch { return; }
|
|
363
|
+
|
|
364
|
+
if (msg.type === 'connected') {
|
|
365
|
+
this.sessionId = msg.sessionId;
|
|
366
|
+
this.backend = msg.backend;
|
|
367
|
+
this.vncUrl = msg.vncUrl || null;
|
|
368
|
+
if (this._verbose) console.log(`Browser Native: Session ${msg.sessionId} connected (${msg.backend})`);
|
|
369
|
+
onConnected(this);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
if (msg.type === 'fallback') {
|
|
374
|
+
this.backend = msg.to;
|
|
375
|
+
if (this._verbose) console.log(`Browser Native: Fallback ${msg.from} → ${msg.to} (${msg.reason})`);
|
|
376
|
+
if (this._eventHandlers.fallback) this._eventHandlers.fallback(msg);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
if (msg.type === 'closed') {
|
|
381
|
+
if (this._verbose) console.log(`Browser Native: Session closed (${msg.reason})`);
|
|
382
|
+
if (this._eventHandlers.closed) this._eventHandlers.closed(msg);
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
// Match response to pending request by id
|
|
387
|
+
if (msg.id != null && this._pending.has(msg.id)) {
|
|
388
|
+
const { resolve, reject } = this._pending.get(msg.id);
|
|
389
|
+
this._pending.delete(msg.id);
|
|
390
|
+
if (msg.type === 'error') {
|
|
391
|
+
reject(new Error(msg.message));
|
|
392
|
+
} else {
|
|
393
|
+
if (msg.backend) this.backend = msg.backend;
|
|
394
|
+
resolve(msg.data);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
|
|
399
|
+
this._ws.onerror = (err) => {
|
|
400
|
+
onError(new Error(err.message || 'WebSocket connection failed'));
|
|
401
|
+
};
|
|
402
|
+
|
|
403
|
+
this._ws.onclose = (event) => {
|
|
404
|
+
// Reject all pending requests
|
|
405
|
+
for (const [, { reject }] of this._pending) {
|
|
406
|
+
reject(new Error(`Session closed: ${event.reason || 'unknown'}`));
|
|
407
|
+
}
|
|
408
|
+
this._pending.clear();
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
_send(action, params = {}) {
|
|
413
|
+
return new Promise((resolve, reject) => {
|
|
414
|
+
if (!this._ws || this._ws.readyState !== 1) {
|
|
415
|
+
return reject(new Error('Session not connected'));
|
|
416
|
+
}
|
|
417
|
+
const id = this._nextId++;
|
|
418
|
+
this._pending.set(id, { resolve, reject });
|
|
419
|
+
this._ws.send(JSON.stringify({ id, action, ...params }));
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
on(event, handler) {
|
|
424
|
+
this._eventHandlers[event] = handler;
|
|
425
|
+
return this;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
async goto(url) { return this._send('goto', { url }); }
|
|
429
|
+
async click(selector, options = {}) { return this._send('click', { selector, ...options }); }
|
|
430
|
+
async type(selector, text, options = {}) { return this._send('type', { selector, text, ...options }); }
|
|
431
|
+
async scroll(direction = 'down', amount = 500) { return this._send('scroll', { direction, amount }); }
|
|
432
|
+
async hover(selector) { return this._send('hover', { selector }); }
|
|
433
|
+
async select(selector, ...values) { return this._send('select', { selector, values }); }
|
|
434
|
+
async pressKey(key) { return this._send('pressKey', { key }); }
|
|
435
|
+
async goBack() { return this._send('goBack'); }
|
|
436
|
+
async goForward() { return this._send('goForward'); }
|
|
437
|
+
async screenshot(options = {}) { return this._send('screenshot', options); }
|
|
438
|
+
async getPageState(options = {}) { return this._send('getPageState', options); }
|
|
439
|
+
async extractContent() { return this._send('extractContent'); }
|
|
440
|
+
async waitFor(selector, timeout) { return this._send('waitFor', { selector, timeout }); }
|
|
441
|
+
async evaluate(fn) { return this._send('evaluate', { fn }); }
|
|
442
|
+
async getCookies() { return this._send('getCookies'); }
|
|
443
|
+
async setCookies(cookies) { return this._send('setCookies', { cookies }); }
|
|
444
|
+
|
|
445
|
+
// Coordinate-based actions (computer-use mode)
|
|
446
|
+
async mouseMove(x, y) { return this._send('mouseMove', { x, y }); }
|
|
447
|
+
async clickAt(x, y, button = 'left') { return this._send('clickAt', { x, y, button }); }
|
|
448
|
+
async doubleClickAt(x, y, button = 'left') { return this._send('doubleClickAt', { x, y, button }); }
|
|
449
|
+
async drag(startX, startY, endX, endY) { return this._send('drag', { startX, startY, endX, endY }); }
|
|
450
|
+
async scrollAt(x, y, direction, amount) { return this._send('scrollAt', { x, y, direction, amount }); }
|
|
451
|
+
async typeText(text) { return this._send('typeText', { text }); }
|
|
452
|
+
async getCursorPosition() { return this._send('getCursorPosition'); }
|
|
453
|
+
async getScreenSize() { return this._send('getScreenSize'); }
|
|
454
|
+
|
|
455
|
+
close() {
|
|
456
|
+
if (this._ws) {
|
|
457
|
+
this._ws.close();
|
|
458
|
+
this._ws = null;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
export { BrowserSession };
|
|
464
|
+
|
|
297
465
|
// Default export for CommonJS compatibility
|
|
298
466
|
export default BrowserNativeClient;
|