brave-real-browser-mcp-server 2.9.21 → 2.11.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.
@@ -3,7 +3,7 @@
3
3
  // @ts-nocheck
4
4
  import { getCurrentPage } from '../browser-manager.js';
5
5
  import { validateWorkflow } from '../workflow-validation.js';
6
- import { withErrorHandling } from '../system-utils.js';
6
+ import { withErrorHandling, sleep } from '../system-utils.js';
7
7
  /**
8
8
  * Video Link Finder - Find all video links on page
9
9
  */
@@ -184,7 +184,7 @@ export async function handleVideoDownloadButton(args) {
184
184
  const selector = customSelector || 'a[download], button[download]';
185
185
  try {
186
186
  await page.click(selector);
187
- await page.waitForTimeout(2000);
187
+ await sleep(2000);
188
188
  return {
189
189
  content: [{
190
190
  type: 'text',
@@ -252,7 +252,7 @@ export async function handleVideoPlayPushSource(args) {
252
252
  }
253
253
  }
254
254
  // Wait for sources to load
255
- await page.waitForTimeout(3000);
255
+ await sleep(3000);
256
256
  page.off('response', responseHandler);
257
257
  return {
258
258
  content: [{
@@ -370,84 +370,154 @@ export async function handleUrlRedirectTraceEndpoints(args) {
370
370
  * Network Recording Finder - Find and analyze network recordings
371
371
  */
372
372
  export async function handleNetworkRecordingFinder(args) {
373
- return await withErrorHandling(async () => {
374
- validateWorkflow('network_recording_finder', {
373
+ try {
374
+ const validation = validateWorkflow('network_recording_finder', {
375
375
  requireBrowser: true,
376
376
  requirePage: true,
377
377
  });
378
+ if (!validation.isValid) {
379
+ return {
380
+ content: [{
381
+ type: 'text',
382
+ text: `⚠️ ${validation.errorMessage || 'Workflow validation failed'}`,
383
+ }],
384
+ isError: true,
385
+ };
386
+ }
378
387
  const page = getCurrentPage();
379
388
  const duration = args.duration || 10000;
380
389
  const filterType = args.filterType || 'video'; // video, audio, media
390
+ const navigateTo = args.navigateTo; // Optional URL to navigate to
391
+ const verbose = args.verbose !== false; // Default true for detailed logging
381
392
  const recordings = [];
393
+ let totalResponses = 0;
394
+ let matchedResponses = 0;
382
395
  const responseHandler = async (response) => {
383
- const url = response.url();
384
- const contentType = response.headers()['content-type'] || '';
385
- const resourceType = response.request().resourceType();
386
- let shouldRecord = false;
387
- if (filterType === 'video' && (contentType.includes('video') || resourceType === 'media')) {
388
- shouldRecord = true;
389
- }
390
- else if (filterType === 'audio' && contentType.includes('audio')) {
391
- shouldRecord = true;
392
- }
393
- else if (filterType === 'media' && (contentType.includes('video') || contentType.includes('audio'))) {
394
- shouldRecord = true;
395
- }
396
- if (shouldRecord) {
397
- try {
398
- const buffer = await response.buffer();
399
- recordings.push({
400
- url,
401
- contentType,
402
- size: buffer.length,
403
- status: response.status(),
404
- timestamp: new Date().toISOString(),
405
- });
396
+ try {
397
+ totalResponses++;
398
+ const url = response.url();
399
+ const contentType = response.headers()['content-type'] || '';
400
+ const resourceType = response.request().resourceType();
401
+ if (verbose && totalResponses % 10 === 0) {
402
+ console.log(`[Network Recording] Processed ${totalResponses} responses, ${matchedResponses} matched`);
406
403
  }
407
- catch (e) {
408
- recordings.push({
409
- url,
410
- contentType,
411
- status: response.status(),
412
- error: 'Could not capture buffer',
413
- });
404
+ let shouldRecord = false;
405
+ if (filterType === 'video' && (contentType.includes('video') || resourceType === 'media')) {
406
+ shouldRecord = true;
407
+ }
408
+ else if (filterType === 'audio' && contentType.includes('audio')) {
409
+ shouldRecord = true;
410
+ }
411
+ else if (filterType === 'media' && (contentType.includes('video') || contentType.includes('audio'))) {
412
+ shouldRecord = true;
413
+ }
414
+ if (shouldRecord) {
415
+ matchedResponses++;
416
+ if (verbose) {
417
+ console.log(`[Network Recording] ✅ Matched ${filterType}: ${url.substring(0, 100)}`);
418
+ }
419
+ try {
420
+ const buffer = await response.buffer();
421
+ recordings.push({
422
+ url,
423
+ contentType,
424
+ size: buffer.length,
425
+ status: response.status(),
426
+ timestamp: new Date().toISOString(),
427
+ });
428
+ }
429
+ catch (e) {
430
+ recordings.push({
431
+ url,
432
+ contentType,
433
+ status: response.status(),
434
+ error: 'Could not capture buffer',
435
+ });
436
+ }
414
437
  }
415
438
  }
439
+ catch (e) {
440
+ // Ignore individual response errors
441
+ }
416
442
  };
443
+ console.log(`[Network Recording] 🎬 Starting monitoring for ${filterType} (${duration}ms)${navigateTo ? ` + navigating to ${navigateTo}` : ''}`);
417
444
  page.on('response', responseHandler);
418
- await page.waitForTimeout(duration);
445
+ // If navigateTo is provided, navigate first, then wait
446
+ if (navigateTo) {
447
+ try {
448
+ await page.goto(navigateTo, { waitUntil: 'networkidle2', timeout: 30000 });
449
+ console.log(`[Network Recording] ✅ Navigation complete, continuing monitoring...`);
450
+ }
451
+ catch (e) {
452
+ console.log(`[Network Recording] ⚠️ Navigation error (continuing anyway): ${e}`);
453
+ }
454
+ }
455
+ await sleep(duration);
419
456
  page.off('response', responseHandler);
457
+ console.log(`[Network Recording] 🛑 Monitoring stopped. Total: ${totalResponses}, Matched: ${matchedResponses}, Recorded: ${recordings.length}`);
458
+ if (recordings.length === 0) {
459
+ return {
460
+ content: [{
461
+ type: 'text',
462
+ text: `ℹ️ No ${filterType} recordings found\n\n📊 Statistics:\n • Total responses checked: ${totalResponses}\n • Matched ${filterType} responses: ${matchedResponses}\n • Duration: ${duration}ms\n • Navigation: ${navigateTo || 'None'}\n\n💡 Suggestions:\n ${navigateTo ? '• Try longer duration if page loads slowly\n • Check if page actually has video/media content' : '• Use navigateTo parameter to capture requests during page load\n • Example: {"navigateTo": "https://example.com", "duration": 15000}'}\n • Consider 'advanced_video_extraction' for analyzing loaded content`,
463
+ }],
464
+ };
465
+ }
466
+ return {
467
+ content: [{
468
+ type: 'text',
469
+ text: `✅ Network Recordings Found: ${recordings.length}\n\n📊 Statistics:\n • Total responses: ${totalResponses}\n • Matched: ${matchedResponses}\n • Recorded: ${recordings.length}\n\n${JSON.stringify(recordings, null, 2)}`,
470
+ }],
471
+ };
472
+ }
473
+ catch (error) {
420
474
  return {
421
475
  content: [{
422
476
  type: 'text',
423
- text: `✅ Network Recordings Found: ${recordings.length}\n\n${JSON.stringify(recordings, null, 2)}`,
477
+ text: `❌ Network recording finder failed: ${error instanceof Error ? error.message : String(error)}`,
424
478
  }],
479
+ isError: true,
425
480
  };
426
- }, 'Failed to find network recordings');
481
+ }
427
482
  }
428
483
  /**
429
484
  * Network Recording Extractors - Extract data from network recordings
430
485
  */
431
486
  export async function handleNetworkRecordingExtractors(args) {
432
- return await withErrorHandling(async () => {
433
- validateWorkflow('network_recording_extractors', {
487
+ try {
488
+ const validation = validateWorkflow('network_recording_extractors', {
434
489
  requireBrowser: true,
435
490
  requirePage: true,
436
491
  });
492
+ if (!validation.isValid) {
493
+ return {
494
+ content: [{
495
+ type: 'text',
496
+ text: `⚠️ ${validation.errorMessage || 'Workflow validation failed'}`,
497
+ }],
498
+ isError: true,
499
+ };
500
+ }
437
501
  const page = getCurrentPage();
438
502
  const duration = args.duration || 10000;
503
+ const navigateTo = args.navigateTo; // Optional URL to navigate to
504
+ const verbose = args.verbose !== false; // Default true
439
505
  const extractedData = {
440
506
  videos: [],
441
507
  audio: [],
442
508
  manifests: [],
443
509
  apis: [],
444
510
  };
511
+ let totalResponses = 0;
445
512
  const responseHandler = async (response) => {
513
+ totalResponses++;
446
514
  const url = response.url();
447
515
  const contentType = response.headers()['content-type'] || '';
448
516
  try {
449
517
  // Video files
450
518
  if (contentType.includes('video') || url.includes('.mp4') || url.includes('.webm')) {
519
+ if (verbose)
520
+ console.log(`[Extractor] 🎥 Video found: ${url.substring(0, 80)}`);
451
521
  extractedData.videos.push({
452
522
  url,
453
523
  contentType,
@@ -456,6 +526,8 @@ export async function handleNetworkRecordingExtractors(args) {
456
526
  }
457
527
  // Audio files
458
528
  if (contentType.includes('audio') || url.includes('.mp3') || url.includes('.m4a')) {
529
+ if (verbose)
530
+ console.log(`[Extractor] 🎵 Audio found: ${url.substring(0, 80)}`);
459
531
  extractedData.audio.push({
460
532
  url,
461
533
  contentType,
@@ -463,6 +535,8 @@ export async function handleNetworkRecordingExtractors(args) {
463
535
  }
464
536
  // Manifest files (HLS, DASH)
465
537
  if (url.includes('.m3u8') || url.includes('.mpd')) {
538
+ if (verbose)
539
+ console.log(`[Extractor] 📜 Manifest found: ${url.substring(0, 80)}`);
466
540
  const text = await response.text();
467
541
  extractedData.manifests.push({
468
542
  url,
@@ -472,6 +546,8 @@ export async function handleNetworkRecordingExtractors(args) {
472
546
  }
473
547
  // API responses with video data
474
548
  if (contentType.includes('json') && (url.includes('video') || url.includes('media'))) {
549
+ if (verbose)
550
+ console.log(`[Extractor] 📡 API found: ${url.substring(0, 80)}`);
475
551
  const json = await response.json();
476
552
  extractedData.apis.push({
477
553
  url,
@@ -483,16 +559,47 @@ export async function handleNetworkRecordingExtractors(args) {
483
559
  // Response not available
484
560
  }
485
561
  };
562
+ console.log(`[Extractor] 🎬 Starting extraction (${duration}ms)${navigateTo ? ` + navigating to ${navigateTo}` : ''}`);
486
563
  page.on('response', responseHandler);
487
- await page.waitForTimeout(duration);
564
+ // If navigateTo is provided, navigate first, then wait
565
+ if (navigateTo) {
566
+ try {
567
+ await page.goto(navigateTo, { waitUntil: 'networkidle2', timeout: 30000 });
568
+ console.log(`[Extractor] ✅ Navigation complete, continuing extraction...`);
569
+ }
570
+ catch (e) {
571
+ console.log(`[Extractor] ⚠️ Navigation error (continuing): ${e}`);
572
+ }
573
+ }
574
+ await sleep(duration);
488
575
  page.off('response', responseHandler);
576
+ const totalFound = extractedData.videos.length + extractedData.audio.length +
577
+ extractedData.manifests.length + extractedData.apis.length;
578
+ console.log(`[Extractor] 🛑 Extraction complete. Total responses: ${totalResponses}, Extracted: ${totalFound}`);
579
+ if (totalFound === 0) {
580
+ return {
581
+ content: [{
582
+ type: 'text',
583
+ text: `ℹ️ No media content extracted\n\n📊 Statistics:\n • Total responses checked: ${totalResponses}\n • Duration: ${duration}ms\n • Navigation: ${navigateTo || 'None'}\n\n💡 Suggestions:\n ${navigateTo ? '• Try longer duration (15000-20000ms)\n • Verify page actually contains video/media' : '• Add navigateTo parameter: {"navigateTo": "https://example.com", "duration": 15000}'}\n • Use 'advanced_video_extraction' for analyzing loaded content\n • Check browser console logs for detailed monitoring`,
584
+ }],
585
+ };
586
+ }
587
+ return {
588
+ content: [{
589
+ type: 'text',
590
+ text: `✅ Network Recording Extraction Complete\n\n📊 Results:\n • Videos: ${extractedData.videos.length}\n • Audio: ${extractedData.audio.length}\n • Manifests: ${extractedData.manifests.length}\n • APIs: ${extractedData.apis.length}\n • Total responses: ${totalResponses}\n\n${JSON.stringify(extractedData, null, 2)}`,
591
+ }],
592
+ };
593
+ }
594
+ catch (error) {
489
595
  return {
490
596
  content: [{
491
597
  type: 'text',
492
- text: `✅ Network Recording Extraction Complete\n\nVideos: ${extractedData.videos.length}\nAudio: ${extractedData.audio.length}\nManifests: ${extractedData.manifests.length}\nAPIs: ${extractedData.apis.length}\n\n${JSON.stringify(extractedData, null, 2)}`,
598
+ text: `❌ Network recording extraction failed: ${error instanceof Error ? error.message : String(error)}`,
493
599
  }],
600
+ isError: true,
494
601
  };
495
- }, 'Failed to extract network recordings');
602
+ }
496
603
  }
497
604
  /**
498
605
  * Video Links Finders - Advanced video link detection
@@ -1,6 +1,7 @@
1
1
  // @ts-nocheck
2
2
  import { getPageInstance } from '../browser-manager.js';
3
3
  import axios from 'axios';
4
+ import { sleep } from '../system-utils.js';
4
5
  /**
5
6
  * REST API Endpoint Finder - Discover REST API endpoints
6
7
  */
@@ -46,7 +47,7 @@ export async function handleRESTAPIEndpointFinder(args) {
46
47
  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });
47
48
  }
48
49
  // Wait for additional requests
49
- await page.waitForTimeout(scanDuration);
50
+ await sleep(scanDuration);
50
51
  page.off('request', requestHandler);
51
52
  }
52
53
  // Also scan page content for API endpoints
@@ -1,6 +1,7 @@
1
1
  // @ts-nocheck
2
2
  import { getPageInstance } from '../browser-manager.js';
3
3
  import Tesseract from 'tesseract.js';
4
+ import { sleep } from '../system-utils.js';
4
5
  /**
5
6
  * OCR Engine - Extract text from captcha images using OCR
6
7
  */
@@ -207,7 +208,7 @@ export async function handlePuzzleCaptchaHandler(args) {
207
208
  const stepSize = targetDistance / steps;
208
209
  for (let i = 0; i < steps; i++) {
209
210
  await page.mouse.move(box.x + box.width / 2 + (stepSize * i), box.y + box.height / 2, { steps: 5 });
210
- await page.waitForTimeout(50 + Math.random() * 50); // Random delay for human-like behavior
211
+ await sleep(50 + Math.random() * 50); // Random delay for human-like behavior
211
212
  }
212
213
  await page.mouse.up();
213
214
  result.attemptedSolve = true;
@@ -3,7 +3,7 @@
3
3
  // @ts-nocheck
4
4
  import { getCurrentPage } from '../browser-manager.js';
5
5
  import { validateWorkflow } from '../workflow-validation.js';
6
- import { withErrorHandling } from '../system-utils.js';
6
+ import { withErrorHandling, sleep } from '../system-utils.js';
7
7
  /**
8
8
  * Shadow DOM Extractor - Extract content from Shadow DOM
9
9
  */
@@ -291,11 +291,20 @@ export async function handleFormAutoFill(args) {
291
291
  * AJAX Content Waiter - Wait for dynamic content to load
292
292
  */
293
293
  export async function handleAjaxContentWaiter(args) {
294
- return await withErrorHandling(async () => {
295
- validateWorkflow('ajax_content_waiter', {
294
+ try {
295
+ const validation = validateWorkflow('ajax_content_waiter', {
296
296
  requireBrowser: true,
297
297
  requirePage: true,
298
298
  });
299
+ if (!validation.isValid) {
300
+ return {
301
+ content: [{
302
+ type: 'text',
303
+ text: `⚠️ ${validation.errorMessage || 'Workflow validation failed'}`,
304
+ }],
305
+ isError: true,
306
+ };
307
+ }
299
308
  const page = getCurrentPage();
300
309
  const waitFor = args.waitFor || 'selector'; // selector, xhr, timeout
301
310
  const value = args.value;
@@ -320,7 +329,7 @@ export async function handleAjaxContentWaiter(args) {
320
329
  }
321
330
  }
322
331
  if (waitFor === 'xhr') {
323
- const duration = value || 5000;
332
+ const duration = parseInt(value) || 5000;
324
333
  let xhrCount = 0;
325
334
  const requestHandler = (request) => {
326
335
  if (request.resourceType() === 'xhr' || request.resourceType() === 'fetch') {
@@ -328,8 +337,16 @@ export async function handleAjaxContentWaiter(args) {
328
337
  }
329
338
  };
330
339
  page.on('request', requestHandler);
331
- await page.waitForTimeout(duration);
340
+ await sleep(duration);
332
341
  page.off('request', requestHandler);
342
+ if (xhrCount === 0) {
343
+ return {
344
+ content: [{
345
+ type: 'text',
346
+ text: `ℹ️ Waited ${duration}ms - No XHR/Fetch requests detected.\n\n💡 Note: Monitoring captures NEW requests made during wait period. Page already loaded before monitoring started.`,
347
+ }],
348
+ };
349
+ }
333
350
  return {
334
351
  content: [{
335
352
  type: 'text',
@@ -338,8 +355,8 @@ export async function handleAjaxContentWaiter(args) {
338
355
  };
339
356
  }
340
357
  if (waitFor === 'timeout') {
341
- const duration = value || 3000;
342
- await page.waitForTimeout(duration);
358
+ const duration = parseInt(value) || 3000;
359
+ await sleep(duration);
343
360
  return {
344
361
  content: [{
345
362
  type: 'text',
@@ -347,8 +364,23 @@ export async function handleAjaxContentWaiter(args) {
347
364
  }],
348
365
  };
349
366
  }
350
- throw new Error(`Unknown waitFor type: ${waitFor}`);
351
- }, 'Failed AJAX content waiter');
367
+ return {
368
+ content: [{
369
+ type: 'text',
370
+ text: `❌ Unknown waitFor type: ${waitFor}. Valid types: 'selector', 'xhr', 'timeout'`,
371
+ }],
372
+ isError: true,
373
+ };
374
+ }
375
+ catch (error) {
376
+ return {
377
+ content: [{
378
+ type: 'text',
379
+ text: `❌ AJAX content waiter failed: ${error instanceof Error ? error.message : String(error)}`,
380
+ }],
381
+ isError: true,
382
+ };
383
+ }
352
384
  }
353
385
  /**
354
386
  * Modal Popup Handler - Handle modal popups
@@ -3,7 +3,7 @@
3
3
  // @ts-nocheck
4
4
  import { getCurrentPage } from '../browser-manager.js';
5
5
  import { validateWorkflow } from '../workflow-validation.js';
6
- import { withErrorHandling } from '../system-utils.js';
6
+ import { withErrorHandling, sleep } from '../system-utils.js';
7
7
  import * as xml2js from 'xml2js';
8
8
  /**
9
9
  * "Next" button automatically detect और click करके सभी pages से data collect करता है
@@ -51,14 +51,14 @@ export async function handleAutoPagination(args) {
51
51
  }
52
52
  // Click next button
53
53
  await nextButton.click();
54
- await page.waitForTimeout(waitBetweenPages);
54
+ await sleep(waitBetweenPages);
55
55
  // Wait for navigation or content load
56
56
  try {
57
57
  await page.waitForNavigation({ timeout: 5000, waitUntil: 'domcontentloaded' });
58
58
  }
59
59
  catch (e) {
60
60
  // No navigation occurred, content loaded dynamically
61
- await page.waitForTimeout(1000);
61
+ await sleep(1000);
62
62
  }
63
63
  currentPage++;
64
64
  }
@@ -114,7 +114,7 @@ export async function handleInfiniteScroll(args) {
114
114
  window.scrollTo(0, document.body.scrollHeight);
115
115
  });
116
116
  // Wait for new content to load
117
- await page.waitForTimeout(scrollDelay);
117
+ await sleep(scrollDelay);
118
118
  scrollCount++;
119
119
  }
120
120
  return {
@@ -145,7 +145,7 @@ export async function handleMultiPageScraper(args) {
145
145
  const url = urls[i];
146
146
  try {
147
147
  await page.goto(url, { waitUntil: 'domcontentloaded' });
148
- await page.waitForTimeout(waitBetweenPages);
148
+ await sleep(waitBetweenPages);
149
149
  const pageData = await page.evaluate((selector) => {
150
150
  const elements = document.querySelectorAll(selector);
151
151
  return Array.from(elements).map((el) => ({
@@ -3,7 +3,7 @@
3
3
  // @ts-nocheck
4
4
  import { getCurrentPage } from '../browser-manager.js';
5
5
  import { validateWorkflow } from '../workflow-validation.js';
6
- import { withErrorHandling } from '../system-utils.js';
6
+ import { withErrorHandling, sleep } from '../system-utils.js';
7
7
  /**
8
8
  * HTML Elements Extractor - Extract all HTML elements with complete details
9
9
  */
@@ -203,7 +203,7 @@ export async function handleAjaxExtractor(args) {
203
203
  if (url && page.url() !== url) {
204
204
  await page.goto(url, { waitUntil: 'networkidle2' });
205
205
  }
206
- await page.waitForTimeout(duration);
206
+ await sleep(duration);
207
207
  page.off('request', requestHandler);
208
208
  return {
209
209
  content: [{
@@ -247,7 +247,7 @@ export async function handleFetchXHR(args) {
247
247
  }
248
248
  };
249
249
  page.on('response', responseHandler);
250
- await page.waitForTimeout(duration);
250
+ await sleep(duration);
251
251
  page.off('response', responseHandler);
252
252
  return {
253
253
  content: [{
@@ -296,7 +296,7 @@ export async function handleNetworkRecorder(args) {
296
296
  };
297
297
  page.on('request', requestHandler);
298
298
  page.on('response', responseHandler);
299
- await page.waitForTimeout(duration);
299
+ await sleep(duration);
300
300
  page.off('request', requestHandler);
301
301
  page.off('response', responseHandler);
302
302
  return {
@@ -4,6 +4,7 @@ import * as fs from 'fs/promises';
4
4
  import * as path from 'path';
5
5
  import pixelmatch from 'pixelmatch';
6
6
  import { PNG } from 'pngjs';
7
+ import { sleep } from '../system-utils.js';
7
8
  /**
8
9
  * Full Page Screenshot - Capture entire page
9
10
  */
@@ -236,7 +237,7 @@ export async function handleVideoRecording(args) {
236
237
  const framePath = path.join(framesDir, `frame_${i.toString().padStart(4, '0')}.png`);
237
238
  await page.screenshot({ path: framePath });
238
239
  frames.push(framePath);
239
- await page.waitForTimeout(frameDelay);
240
+ await sleep(frameDelay);
240
241
  }
241
242
  }
242
243
  return {