@monostate/browsernative-client 1.2.1 → 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.
Files changed (5) hide show
  1. package/README.md +98 -386
  2. package/index.d.ts +175 -7
  3. package/index.js +232 -1
  4. package/package.json +2 -2
  5. package/LICENSE +0 -21
package/README.md CHANGED
@@ -1,461 +1,173 @@
1
1
  # @monostate/browsernative-client
2
2
 
3
- > **Official JavaScript/TypeScript client for Browser Native API**
3
+ > JavaScript/TypeScript client for the Browser Native web scraping API
4
4
 
5
- [![npm version](https://badge.fury.io/js/%40monostate%2Fbrowsernative-client.svg)](https://badge.fury.io/js/%40monostate%2Fbrowsernative-client)
5
+ [![npm](https://img.shields.io/npm/v/@monostate/browsernative-client.svg)](https://www.npmjs.com/package/@monostate/browsernative-client)
6
+ [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/monostate/browsernative-client/blob/main/LICENSE)
6
7
  [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg)](https://www.typescriptlang.org/)
7
- [![License](https://img.shields.io/badge/License-MIT-yellow.svg)](../../LICENSE)
8
8
 
9
- A lightweight, fast, and reliable client for the Browser Native web scraping and content extraction API. Works in browsers, Node.js, Deno, and edge environments.
9
+ Zero-dependency client for scraping, screenshots, and AI analysis. Works in Node.js, browsers, and edge environments.
10
10
 
11
- ## 🚀 Quick Start
12
-
13
- ### Installation
11
+ ## Install
14
12
 
15
13
  ```bash
16
14
  npm install @monostate/browsernative-client
17
- # or
18
- yarn add @monostate/browsernative-client
19
- # or
20
- pnpm add @monostate/browsernative-client
21
15
  ```
22
16
 
23
- ### Get Your API Key
24
-
25
- 1. Sign up at [bnca.monostate.ai](https://bnca.monostate.ai)
26
- 2. Get your API key from the dashboard
27
- 3. Start scraping!
17
+ Get an API key at [bnca.monostate.ai](https://bnca.monostate.ai).
28
18
 
29
- ### Basic Usage
19
+ ## Usage
30
20
 
31
21
  ```javascript
32
22
  import { BrowserNativeClient } from '@monostate/browsernative-client';
33
23
 
34
24
  const client = new BrowserNativeClient('your-api-key');
35
25
 
36
- // Scrape any website
26
+ // Scrape
37
27
  const result = await client.scrape('https://example.com');
38
- console.log(result.data.title);
39
- console.log(result.data.content);
40
-
41
- // Quick screenshot capture (optimized for speed)
42
- const screenshot = await client.quickshot('https://example.com');
43
- console.log(screenshot.screenshot); // Base64 image
44
- ```
45
-
46
- ### Quick Functions
47
-
48
- ```javascript
49
- import { quickScrape, quickScreenshot, quickShot, quickAnalyze } from '@monostate/browsernative-client';
50
-
51
- // One-line scraping
52
- const content = await quickScrape('https://example.com', 'your-api-key');
53
28
 
54
- // Take a screenshot (with content extraction)
55
- const screenshot = await quickScreenshot('https://example.com', 'your-api-key');
56
-
57
- // Quick screenshot only (fastest option)
58
- const quickScreenshot = await quickShot('https://example.com', 'your-api-key');
59
-
60
- // AI-powered analysis
61
- const analysis = await quickAnalyze(
62
- 'https://news.ycombinator.com',
63
- 'What are the top 3 trending topics?',
64
- 'your-api-key'
65
- );
66
- ```
29
+ // Force a specific scraping method
30
+ const result = await client.scrape('https://example.com', { method: 'lightpanda' });
67
31
 
68
- ## 📋 API Reference
32
+ // Screenshot
33
+ const screenshot = await client.screenshot('https://example.com');
69
34
 
70
- ### Client Initialization
35
+ // Quick screenshot (faster, no content extraction)
36
+ const quick = await client.quickshot('https://example.com');
71
37
 
72
- ```javascript
73
- const client = new BrowserNativeClient(apiKey, options);
38
+ // AI Q&A
39
+ const analysis = await client.analyze('https://example.com', 'What is this site about?');
74
40
  ```
75
41
 
76
- **Options:**
77
- - `baseUrl` (string): API base URL (default: `https://bnca-api.fly.dev`)
78
- - `timeout` (number): Request timeout in ms (default: `30000`)
79
- - `retries` (number): Number of retry attempts (default: `2`)
80
- - `verbose` (boolean): Enable logging (default: `false`)
81
-
82
- ### Methods
83
-
84
- #### `client.scrape(url, options)`
85
-
86
- Extract structured content from any webpage.
42
+ ### Convenience functions
87
43
 
88
44
  ```javascript
89
- const result = await client.scrape('https://example.com', {
90
- includeScreenshot: true,
91
- waitForSelector: '.main-content',
92
- extractMetadata: true,
93
- userAgent: 'Custom Bot 1.0'
94
- });
95
-
96
- console.log(result.data.title); // Page title
97
- console.log(result.data.content); // Main content
98
- console.log(result.data.metadata); // Meta tags, etc.
99
- console.log(result.screenshot); // Base64 image (if requested)
100
- console.log(result.method); // Scraping method used
101
- ```
102
-
103
- **Options:**
104
- - `includeScreenshot` (boolean): Include page screenshot
105
- - `waitForSelector` (string): CSS selector to wait for
106
- - `userAgent` (string): Custom user agent
107
- - `viewport` (object): `{ width: number, height: number }`
108
- - `extractMetadata` (boolean): Extract meta tags and structured data
109
-
110
- #### `client.screenshot(url, options)`
111
-
112
- Take high-quality screenshots of webpages with content extraction.
45
+ import { quickScrape, quickScreenshot, quickShot, quickAnalyze, bulkScrape } from '@monostate/browsernative-client';
113
46
 
114
- ```javascript
115
- const result = await client.screenshot('https://example.com', {
116
- fullPage: true,
117
- format: 'png',
118
- viewport: { width: 1920, height: 1080 }
119
- });
120
-
121
- // Use the base64 image
122
- const img = `data:image/png;base64,${result.screenshot}`;
47
+ const content = await quickScrape('https://example.com', 'your-api-key');
48
+ const shot = await quickShot('https://example.com', 'your-api-key');
49
+ const answer = await quickAnalyze('https://example.com', 'What is this?', 'your-api-key');
123
50
  ```
124
51
 
125
- **Options:**
126
- - `fullPage` (boolean): Capture full page scroll
127
- - `format` (string): `'png'`, `'jpeg'`, or `'webp'`
128
- - `quality` (number): JPEG quality (1-100)
129
- - All scrape options are also available
130
-
131
- #### `client.quickshot(url, options)`
132
-
133
- Optimized screenshot capture for maximum speed (no content extraction).
52
+ ### Bulk scraping
134
53
 
135
54
  ```javascript
136
- const result = await client.quickshot('https://example.com');
137
-
138
- // Returns screenshot immediately
139
- if (result.success && result.screenshot) {
140
- const img = result.screenshot; // Already includes data:image/png;base64,
141
- }
142
- ```
143
-
144
- **Benefits:**
145
- - 2-3x faster than regular screenshot
146
- - Optimized for visual capture only
147
- - Perfect for thumbnails and previews
148
-
149
- #### `client.analyze(url, question, options)`
150
-
151
- AI-powered content analysis and question answering.
152
-
153
- ```javascript
154
- const result = await client.analyze(
155
- 'https://techcrunch.com',
156
- 'What are the latest AI developments mentioned?',
157
- {
158
- language: 'en',
159
- style: 'detailed'
160
- }
161
- );
162
-
163
- console.log(result.analysis.answer); // AI response
164
- console.log(result.analysis.confidence); // Confidence score
165
- ```
166
-
167
- **Options:**
168
- - `language` (string): Response language (`'en'`, `'pt'`, `'es'`, `'fr'`, `'de'`, `'auto'`)
169
- - `style` (string): Response style (`'concise'`, `'detailed'`, `'technical'`)
170
- - All scrape options are also available
171
-
172
- #### `client.getUsage(days)`
173
-
174
- Get your account usage statistics.
55
+ const urls = ['https://site1.com', 'https://site2.com', 'https://site3.com'];
175
56
 
176
- ```javascript
177
- const usage = await client.getUsage(30); // Last 30 days
57
+ const { results, stats } = await client.bulkScrape(urls, {
58
+ concurrency: 5,
59
+ continueOnError: true,
60
+ progressCallback: (p) => console.log(`${p.percentage.toFixed(1)}%`),
61
+ });
178
62
 
179
- console.log(usage.data.monthlyTotal.totalRequests);
180
- console.log(usage.data.currentUsage);
181
- console.log(usage.data.usageLimit);
63
+ console.log(`${stats.successful}/${stats.total} succeeded in ${stats.totalTime}ms`);
182
64
  ```
183
65
 
184
- #### `client.healthCheck()`
66
+ ## API Reference
185
67
 
186
- Check API status and your account health.
68
+ ### Client options
187
69
 
188
70
  ```javascript
189
- const health = await client.healthCheck();
190
-
191
- console.log(health.data.status); // 'healthy', 'degraded', or 'unhealthy'
192
- console.log(health.data.services); // Service status breakdown
71
+ const client = new BrowserNativeClient(apiKey, {
72
+ baseUrl: 'https://bnca-api.fly.dev', // API endpoint
73
+ timeout: 30000, // Request timeout (ms)
74
+ retries: 2, // Retry attempts
75
+ verbose: false, // Debug logging
76
+ });
193
77
  ```
194
78
 
195
- ## 🌐 Framework Integration
196
-
197
- ### React Hook
198
-
199
- ```javascript
200
- // hooks/useBrowserNative.js
201
- import { useState } from 'react';
202
- import { BrowserNativeClient } from '@monostate/browsernative-client';
79
+ ### Methods
203
80
 
204
- export function useBrowserNative(apiKey) {
205
- const [loading, setLoading] = useState(false);
206
- const [data, setData] = useState(null);
207
- const [error, setError] = useState(null);
208
-
209
- const client = new BrowserNativeClient(apiKey);
210
-
211
- const scrape = async (url, options = {}) => {
212
- setLoading(true);
213
- setError(null);
214
-
215
- try {
216
- const result = await client.scrape(url, options);
217
- if (result.success) {
218
- setData(result.data);
219
- } else {
220
- setError(result.error);
221
- }
222
- } catch (err) {
223
- setError(err.message);
224
- } finally {
225
- setLoading(false);
226
- }
227
- };
228
-
229
- return { scrape, loading, data, error };
81
+ | Method | Description |
82
+ |--------|-------------|
83
+ | `scrape(url, opts?)` | Extract structured content |
84
+ | `screenshot(url, opts?)` | Screenshot with content extraction |
85
+ | `quickshot(url, opts?)` | Fast screenshot only |
86
+ | `analyze(url, question, opts?)` | AI-powered Q&A |
87
+ | `bulkScrape(urls, opts?)` | Scrape multiple URLs concurrently |
88
+ | `getUsage(days?)` | Account usage statistics |
89
+ | `healthCheck()` | API health status |
90
+
91
+ ### Scrape options
92
+
93
+ ```javascript
94
+ {
95
+ method: 'auto', // 'auto' | 'direct' | 'lightpanda' | 'puppeteer'
96
+ includeScreenshot: false,
97
+ waitForSelector: '.content',
98
+ userAgent: 'Custom Bot',
99
+ viewport: { width: 1920, height: 1080 },
100
+ extractMetadata: true,
230
101
  }
231
102
  ```
232
103
 
233
- ### Vue Composable
104
+ ### Response shape
234
105
 
235
106
  ```javascript
236
- // composables/useBrowserNative.js
237
- import { ref } from 'vue';
238
- import { BrowserNativeClient } from '@monostate/browsernative-client';
239
-
240
- export function useBrowserNative(apiKey) {
241
- const loading = ref(false);
242
- const data = ref(null);
243
- const error = ref(null);
244
-
245
- const client = new BrowserNativeClient(apiKey);
246
-
247
- const scrape = async (url, options = {}) => {
248
- loading.value = true;
249
- error.value = null;
250
-
251
- try {
252
- const result = await client.scrape(url, options);
253
- if (result.success) {
254
- data.value = result.data;
255
- } else {
256
- error.value = result.error;
257
- }
258
- } catch (err) {
259
- error.value = err.message;
260
- } finally {
261
- loading.value = false;
262
- }
263
- };
264
-
265
- return { scrape, loading, data, error };
107
+ {
108
+ success: true,
109
+ data: { title, content, metadata, ... },
110
+ responseTime: 1234,
111
+ attempt: 1,
266
112
  }
267
113
  ```
268
114
 
269
- ### Next.js API Route
115
+ ### Browser sessions
270
116
 
271
- ```javascript
272
- // pages/api/scrape.js
273
- import { BrowserNativeClient } from '@monostate/browsernative-client';
274
-
275
- const client = new BrowserNativeClient(process.env.BROWSER_NATIVE_API_KEY);
276
-
277
- export default async function handler(req, res) {
278
- if (req.method !== 'POST') {
279
- return res.status(405).json({ error: 'Method not allowed' });
280
- }
281
-
282
- try {
283
- const { url, question } = req.body;
284
-
285
- let result;
286
- if (question) {
287
- result = await client.analyze(url, question);
288
- } else {
289
- result = await client.scrape(url);
290
- }
291
-
292
- res.status(200).json(result);
293
- } catch (error) {
294
- res.status(500).json({ error: error.message });
295
- }
296
- }
297
- ```
298
-
299
- ### Express.js
117
+ Persistent browser sessions with real-time control over WebSocket:
300
118
 
301
119
  ```javascript
302
- import express from 'express';
303
- import { BrowserNativeClient } from '@monostate/browsernative-client';
304
-
305
- const app = express();
306
- const client = new BrowserNativeClient(process.env.BROWSER_NATIVE_API_KEY);
307
-
308
- app.post('/scrape', async (req, res) => {
309
- try {
310
- const { url } = req.body;
311
- const result = await client.scrape(url);
312
- res.json(result);
313
- } catch (error) {
314
- res.status(500).json({ error: error.message });
315
- }
316
- });
317
- ```
318
-
319
- ## 🔒 Environment Variables
320
-
321
- Create a `.env` file:
322
-
323
- ```bash
324
- BROWSER_NATIVE_API_KEY=your_api_key_here
325
- ```
120
+ const session = await client.createSession({ mode: 'auto' });
326
121
 
327
- Then use in your code:
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();
328
127
 
329
- ```javascript
330
- const client = new BrowserNativeClient(process.env.BROWSER_NATIVE_API_KEY);
331
- ```
128
+ // Listen for backend fallback events
129
+ session.on('fallback', (e) => console.log(`${e.from} -> ${e.to}: ${e.reason}`));
332
130
 
333
- ## 📱 Browser Usage
334
-
335
- You can use this client directly in browsers, but be careful with API keys:
336
-
337
- ```html
338
- <!DOCTYPE html>
339
- <html>
340
- <head>
341
- <title>Browser Native Demo</title>
342
- </head>
343
- <body>
344
- <script type="module">
345
- import { BrowserNativeClient } from 'https://cdn.skypack.dev/@monostate/browsernative-client';
346
-
347
- // ⚠️ Never expose your API key in client-side code!
348
- // Use a proxy server or environment variables
349
- const client = new BrowserNativeClient(await getApiKeyFromServer());
350
-
351
- const result = await client.scrape('https://example.com');
352
- console.log(result);
353
- </script>
354
- </body>
355
- </html>
131
+ session.close();
356
132
  ```
357
133
 
358
- **Security Note**: Never expose your API key in client-side code. Use a backend proxy or server-side rendering.
134
+ Modes: `auto` (LightPanda + Chrome fallback), `headless`, `visual`, `computer-use`.
359
135
 
360
- ## Performance Tips
136
+ #### Computer use mode
361
137
 
362
- 1. **Reuse Client Instances**: Create one client instance and reuse it
363
- 2. **Enable Retries**: The client automatically retries failed requests
364
- 3. **Use Appropriate Timeouts**: Adjust timeout based on your use case
365
- 4. **Batch Requests**: Process multiple URLs concurrently
138
+ For AI agents that control the browser by pixel coordinates:
366
139
 
367
140
  ```javascript
368
- const client = new BrowserNativeClient(apiKey, {
369
- timeout: 45000, // Longer timeout for complex sites
370
- retries: 3, // More retries for reliability
371
- verbose: true // Enable logging for debugging
372
- });
141
+ const session = await client.createSession({ mode: 'computer-use', screenWidth: 1280, screenHeight: 800 });
373
142
 
374
- // Concurrent scraping
375
- const urls = ['https://site1.com', 'https://site2.com', 'https://site3.com'];
376
- const results = await Promise.all(
377
- urls.map(url => client.scrape(url))
378
- );
379
- ```
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();
380
152
 
381
- ## 🔧 Error Handling
153
+ // VNC streaming URL
154
+ console.log(session.vncUrl);
382
155
 
383
- ```javascript
384
- try {
385
- const result = await client.scrape(url);
386
-
387
- if (result.success) {
388
- console.log('Success:', result.data);
389
- } else {
390
- console.error('Scraping failed:', result.error);
391
- }
392
- } catch (error) {
393
- console.error('Request failed:', error.message);
394
- }
156
+ session.close();
395
157
  ```
396
158
 
397
- Common error scenarios:
398
- - **Invalid API Key**: Check your API key and account status
399
- - **Rate Limited**: Upgrade your plan or reduce request frequency
400
- - **Timeout**: Increase timeout or try a simpler extraction method
401
- - **Invalid URL**: Ensure the URL is accessible and properly formatted
159
+ ## TypeScript
402
160
 
403
- ## 🚀 TypeScript Support
404
-
405
- Full TypeScript support with comprehensive type definitions:
161
+ Full type definitions included.
406
162
 
407
163
  ```typescript
408
- import { BrowserNativeClient, ScrapeResult, AnalyzeResult } from '@monostate/browsernative-client';
409
-
410
- const client: BrowserNativeClient = new BrowserNativeClient('your-api-key');
411
-
412
- const result: ScrapeResult = await client.scrape('https://example.com');
413
- const analysis: AnalyzeResult = await client.analyze(url, question);
164
+ import { BrowserNativeClient, ScrapeResult, BulkScrapeResult } from '@monostate/browsernative-client';
414
165
  ```
415
166
 
416
- ## 📊 Rate Limits
417
-
418
- | Plan | Requests/Month | Rate Limit |
419
- |------|----------------|------------|
420
- | **Free** | 1,000 | 10/minute |
421
- | **Starter** | 10,000 | 60/minute |
422
- | **Pro** | 100,000 | 300/minute |
423
- | **Enterprise** | Unlimited | Custom |
424
-
425
- ## 📋 Changelog
426
-
427
- ### v1.2.0 (Latest)
428
- - 🔧 **Timeout Improvements**: Enhanced timeout handling and request reliability
429
- - 📝 **Documentation Updates**: Comprehensive API documentation and examples
430
- - 🏷️ **Package Naming**: Proper package name consistency across all imports
431
- - ⚡ **Performance Optimizations**: Better error handling and response processing
432
- - 🌐 **Framework Integration**: Improved React, Vue, and Next.js examples
433
-
434
- ### v1.1.2
435
- - Bug fixes and stability improvements
436
- - Enhanced error handling
437
-
438
- ### v1.1.1
439
- - Initial TypeScript support
440
- - Basic API client functionality
441
-
442
- ## 🤝 Support
443
-
444
- - 📧 **Email**: [support@bnca.monostate.ai](mailto:support@bnca.monostate.ai)
445
- - 📖 **Documentation**: [bnca.monostate.ai/docs](https://bnca.monostate.ai/docs)
446
- - 🐛 **Issues**: [GitHub Issues](https://github.com/browsernative/client-sdk/issues)
447
- - 💬 **Discord**: [Join our community](https://discord.gg/browsernative)
448
-
449
- ## 📄 License
450
-
451
- MIT License - see [LICENSE](../../LICENSE) file for details.
452
-
453
- ---
454
-
455
- <div align="center">
167
+ ## Changelog
456
168
 
457
- **Built with ❤️ for developers who value speed and reliability**
169
+ See [CHANGELOG.md](./CHANGELOG.md).
458
170
 
459
- [🌐 Website](https://bnca.monostate.ai) | [📖 Docs](https://bnca.monostate.ai/docs) | [🚀 Get API Key](https://bnca.monostate.ai/signup)
171
+ ## License
460
172
 
461
- </div>
173
+ MIT
package/index.d.ts CHANGED
@@ -14,6 +14,8 @@ export interface ClientOptions {
14
14
  }
15
15
 
16
16
  export interface ScrapeOptions {
17
+ /** Force a specific scraping method */
18
+ method?: 'auto' | 'direct' | 'lightpanda' | 'puppeteer';
17
19
  /** Include screenshot in the response */
18
20
  includeScreenshot?: boolean;
19
21
  /** Wait for specific selector before extracting content */
@@ -29,6 +31,26 @@ export interface ScrapeOptions {
29
31
  extractMetadata?: boolean;
30
32
  }
31
33
 
34
+ export interface BulkScrapeOptions extends ScrapeOptions {
35
+ /** Number of concurrent requests (default: 5) */
36
+ concurrency?: number;
37
+ /** Continue on error (default: true) */
38
+ continueOnError?: boolean;
39
+ /** Progress callback */
40
+ progressCallback?: (progress: { processed: number; total: number; percentage: number }) => void;
41
+ }
42
+
43
+ export interface BulkScrapeResult {
44
+ results: Array<ScrapeResult & { url: string }>;
45
+ stats: {
46
+ total: number;
47
+ successful: number;
48
+ failed: number;
49
+ totalTime: number;
50
+ averageTime: number;
51
+ };
52
+ }
53
+
32
54
  export interface ScreenshotOptions extends ScrapeOptions {
33
55
  /** Force full page screenshot */
34
56
  fullPage?: boolean;
@@ -74,14 +96,33 @@ export interface ScrapeResult {
74
96
  attempt?: number;
75
97
  }
76
98
 
77
- export interface AnalyzeResult extends ScrapeResult {
78
- /** AI analysis response */
79
- analysis?: {
80
- answer: string;
81
- confidence: number;
82
- processingTime: number;
83
- language: string;
99
+ export interface AnalyzeResult {
100
+ /** Whether the request was successful */
101
+ success: boolean;
102
+ /** API response data */
103
+ data?: {
104
+ /** Whether the inner request was successful */
105
+ success?: boolean;
106
+ /** The answer from AI analysis */
107
+ answer?: string;
108
+ /** Metadata about the request */
109
+ metadata?: {
110
+ url: string;
111
+ question: string;
112
+ method: string;
113
+ timestamp: string;
114
+ performance: {
115
+ totalTime: number;
116
+ };
117
+ screenshot?: string | null;
118
+ };
84
119
  };
120
+ /** Error message (if failed) */
121
+ error?: string;
122
+ /** Response time in milliseconds */
123
+ responseTime: number;
124
+ /** Attempt number that succeeded */
125
+ attempt?: number;
85
126
  }
86
127
 
87
128
  export interface UsageResult {
@@ -127,6 +168,114 @@ export interface HealthResult {
127
168
  responseTime: number;
128
169
  }
129
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
+
130
279
  /**
131
280
  * Browser Native API Client
132
281
  */
@@ -153,11 +302,21 @@ export declare class BrowserNativeClient {
153
302
  */
154
303
  analyze(url: string, question: string, options?: AnalyzeOptions): Promise<AnalyzeResult>;
155
304
 
305
+ /**
306
+ * Scrape multiple URLs with concurrency control
307
+ */
308
+ bulkScrape(urls: string[], options?: BulkScrapeOptions): Promise<BulkScrapeResult>;
309
+
156
310
  /**
157
311
  * Get account usage statistics
158
312
  */
159
313
  getUsage(days?: number): Promise<UsageResult>;
160
314
 
315
+ /**
316
+ * Create a persistent browser session via WebSocket
317
+ */
318
+ createSession(options?: SessionOptions): Promise<BrowserSession>;
319
+
161
320
  /**
162
321
  * Check API health and your account status
163
322
  */
@@ -201,4 +360,13 @@ export declare function quickShot(
201
360
  options?: ScreenshotOptions
202
361
  ): Promise<ScrapeResult>;
203
362
 
363
+ /**
364
+ * Convenience function for bulk scraping multiple URLs
365
+ */
366
+ export declare function bulkScrape(
367
+ urls: string[],
368
+ apiKey: string,
369
+ options?: BulkScrapeOptions
370
+ ): Promise<BulkScrapeResult>;
371
+
204
372
  export default BrowserNativeClient;
package/index.js CHANGED
@@ -83,6 +83,57 @@ export class BrowserNativeClient {
83
83
  return this._makeRequest('/aireply', payload);
84
84
  }
85
85
 
86
+ /**
87
+ * Scrape multiple URLs with concurrency control
88
+ * @param {string[]} urls - URLs to scrape
89
+ * @param {object} options - Bulk scraping options
90
+ * @returns {Promise<object>} Aggregated results
91
+ */
92
+ async bulkScrape(urls, options = {}) {
93
+ const concurrency = options.concurrency || 5;
94
+ const continueOnError = options.continueOnError !== false;
95
+ const results = [];
96
+ const startTime = Date.now();
97
+ let processed = 0;
98
+
99
+ const queue = [...urls];
100
+ const workers = Array.from({ length: Math.min(concurrency, urls.length) }, async () => {
101
+ while (queue.length > 0) {
102
+ const url = queue.shift();
103
+ if (!url) break;
104
+ try {
105
+ const result = await this.scrape(url, options);
106
+ results.push({ url, ...result });
107
+ } catch (error) {
108
+ results.push({ url, success: false, error: error.message });
109
+ if (!continueOnError) throw error;
110
+ }
111
+ processed++;
112
+ if (options.progressCallback) {
113
+ options.progressCallback({
114
+ processed,
115
+ total: urls.length,
116
+ percentage: (processed / urls.length) * 100,
117
+ });
118
+ }
119
+ }
120
+ });
121
+
122
+ await Promise.all(workers);
123
+
124
+ const successful = results.filter(r => r.success).length;
125
+ return {
126
+ results,
127
+ stats: {
128
+ total: urls.length,
129
+ successful,
130
+ failed: urls.length - successful,
131
+ totalTime: Date.now() - startTime,
132
+ averageTime: Math.round((Date.now() - startTime) / urls.length),
133
+ },
134
+ };
135
+ }
136
+
86
137
  /**
87
138
  * Get account usage statistics
88
139
  * @param {number} days - Number of days to fetch (max 30)
@@ -92,6 +143,30 @@ export class BrowserNativeClient {
92
143
  return this._makeRequest('/stats', {}, 'GET');
93
144
  }
94
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
+
95
170
  /**
96
171
  * Check API health and your account status
97
172
  * @returns {Promise<object>} Health check result
@@ -121,7 +196,7 @@ export class BrowserNativeClient {
121
196
  headers: {
122
197
  'x-api-key': this.apiKey,
123
198
  'Content-Type': 'application/json',
124
- 'User-Agent': 'Browser Native Client SDK/1.2.1'
199
+ 'User-Agent': 'Browser Native Client SDK/2.0.0'
125
200
  }
126
201
  };
127
202
 
@@ -231,5 +306,161 @@ export async function quickShot(url, apiKey, options = {}) {
231
306
  return client.quickshot(url, options);
232
307
  }
233
308
 
309
+ /**
310
+ * Convenience function for bulk scraping multiple URLs
311
+ * @param {string[]} urls - URLs to scrape
312
+ * @param {string} apiKey - Your API key
313
+ * @param {object} options - Bulk options (concurrency, continueOnError, progressCallback)
314
+ * @returns {Promise<object>} Aggregated results
315
+ */
316
+ export async function bulkScrape(urls, apiKey, options = {}) {
317
+ const client = new BrowserNativeClient(apiKey, options);
318
+ return client.bulkScrape(urls, options);
319
+ }
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
+
234
465
  // Default export for CommonJS compatibility
235
466
  export default BrowserNativeClient;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monostate/browsernative-client",
3
- "version": "1.2.1",
3
+ "version": "2.2.0",
4
4
  "description": "Browser Native client SDK for web scraping and content extraction API",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -33,7 +33,7 @@
33
33
  "license": "MIT",
34
34
  "dependencies": {},
35
35
  "engines": {
36
- "node": ">=16.0.0"
36
+ "node": ">=18.0.0"
37
37
  },
38
38
  "repository": {
39
39
  "type": "git",
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2025 BNCA Team
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.