brave-real-browser-mcp-server 2.27.32 → 2.28.1

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.
@@ -1707,6 +1707,7 @@ export async function handleExtractJson(page, args) {
1707
1707
  try {
1708
1708
  const key = args.decryptAES.key || "kiemtienmua911ca";
1709
1709
  const ivList = args.decryptAES.ivList || ["1234567890oiuytr", "0123456789abcdef"];
1710
+ const autoIV = args.decryptAES.autoIV !== false; // Default to true for hubstream-style encryption
1710
1711
  let input = args.decryptAES.input || "";
1711
1712
  // Option: Fetch encrypted data from API URL directly (recommended for hubstream)
1712
1713
  if (!input && args.decryptAES.fetchUrl) {
@@ -1733,24 +1734,43 @@ export async function handleExtractJson(page, args) {
1733
1734
  }
1734
1735
  return bytes;
1735
1736
  };
1736
- for (const iv of params.ivList) {
1737
+ // Build IV list - if autoIV, try extracting from first 16 bytes first
1738
+ const ivsToTry = [];
1739
+ if (params.autoIV && encryptedHex.length >= 32) {
1740
+ // First 32 hex chars = first 16 bytes = IV (hubstream style)
1741
+ const ivHex = encryptedHex.substring(0, 32);
1742
+ ivsToTry.push({ iv: hexToBytes(ivHex), label: 'autoIV (first 16 bytes)' });
1743
+ }
1744
+ // Add manual IV list
1745
+ for (const ivStr of params.ivList) {
1746
+ ivsToTry.push({ iv: new TextEncoder().encode(ivStr), label: ivStr });
1747
+ }
1748
+ for (const { iv: ivData, label: ivLabel } of ivsToTry) {
1737
1749
  try {
1738
1750
  const keyData = new TextEncoder().encode(params.key);
1739
- const ivData = new TextEncoder().encode(iv);
1740
- const encryptedData = hexToBytes(encryptedHex);
1751
+ // If using autoIV, encrypted data starts after first 32 hex chars (16 bytes)
1752
+ const isAutoIV = ivLabel.startsWith('autoIV');
1753
+ const dataHex = isAutoIV ? encryptedHex.substring(32) : encryptedHex;
1754
+ const encryptedData = hexToBytes(dataHex);
1741
1755
  const cryptoKey = await crypto.subtle.importKey("raw", keyData, "AES-CBC", false, ["decrypt"]);
1742
1756
  const decrypted = await crypto.subtle.decrypt({ name: "AES-CBC", iv: ivData }, cryptoKey, encryptedData);
1743
1757
  const decryptedText = new TextDecoder().decode(decrypted);
1744
- // Extract source URL from decrypted JSON
1758
+ // Extract URLs from decrypted JSON
1745
1759
  const sourceMatch = /"source":"([^"]+)"/.exec(decryptedText);
1760
+ const mp4Match = /"mp4":"([^"]+)"/.exec(decryptedText);
1761
+ const hlsMatch = /"hls":"([^"]+)"/.exec(decryptedText);
1746
1762
  const streamUrl = sourceMatch ? sourceMatch[1].replace(/\\\//g, '/') : null;
1763
+ const mp4Url = mp4Match ? mp4Match[1].replace(/\\\//g, '/') : null;
1764
+ const hlsUrl = hlsMatch ? hlsMatch[1].replace(/\\\//g, '/') : null;
1747
1765
  return {
1748
1766
  success: true,
1749
- iv: iv,
1767
+ iv: ivLabel,
1750
1768
  decrypted: decryptedText,
1751
1769
  decryptedLength: decryptedText.length,
1752
1770
  extractedStreamUrl: streamUrl,
1753
- isStreamUrl: streamUrl ? (streamUrl.includes('m3u8') || streamUrl.includes('.mp4')) : false,
1771
+ extractedMp4Url: mp4Url,
1772
+ extractedHlsUrl: hlsUrl,
1773
+ isStreamUrl: (streamUrl && (streamUrl.includes('m3u8') || streamUrl.includes('.mp4'))) || !!mp4Url,
1754
1774
  inputLength: encryptedHex.length
1755
1775
  };
1756
1776
  }
@@ -1763,7 +1783,7 @@ export async function handleExtractJson(page, args) {
1763
1783
  catch (e) {
1764
1784
  return { success: false, error: String(e) };
1765
1785
  }
1766
- }, { input, key, ivList, fetchUrl: args.decryptAES.fetchUrl });
1786
+ }, { input, key, ivList, autoIV, fetchUrl: args.decryptAES.fetchUrl });
1767
1787
  decoded = {
1768
1788
  source: 'decryptAES',
1769
1789
  ...result,
@@ -4801,9 +4821,16 @@ export async function handleExecuteJs(page, args) {
4801
4821
  const waitTime = args.waitForResult || 5000;
4802
4822
  try {
4803
4823
  let result;
4824
+ // Auto-detect if code contains await and wrap with async IIFE
4825
+ const hasAwait = /\bawait\b/.test(args.code);
4826
+ const isAlreadyAsync = /^\s*\(async\s+function|\(\s*async\s*\(|\(async\s*\(\)\s*=>/.test(args.code.trim());
4827
+ // Wrap code with async IIFE if it contains await but isn't already async
4828
+ const codeToExecute = (hasAwait && !isAlreadyAsync)
4829
+ ? `(async () => { ${args.code} })()`
4830
+ : args.code;
4804
4831
  if (args.context === 'isolated') {
4805
4832
  // Execute in isolated context (sandboxed)
4806
- result = await page.evaluate(args.code);
4833
+ result = await page.evaluate(codeToExecute);
4807
4834
  }
4808
4835
  else {
4809
4836
  // Execute in page context with full access
@@ -4815,7 +4842,7 @@ export async function handleExecuteJs(page, args) {
4815
4842
  catch (e) {
4816
4843
  return { error: String(e) };
4817
4844
  }
4818
- }, args.code);
4845
+ }, codeToExecute);
4819
4846
  }
4820
4847
  // Handle async results
4821
4848
  if (result instanceof Promise) {
@@ -5,6 +5,10 @@ import { getProgressNotifier } from '../transport/progress-notifier.js';
5
5
  import { getSharedEventBus } from '../transport/index.js';
6
6
  // Get event bus instance
7
7
  const eventBus = getSharedEventBus();
8
+ // Helper to detect API endpoints
9
+ function isApiEndpoint(url) {
10
+ return /\/api\/|\.json(\?|$)|\?.*=.*&|\/v\d+\//.test(url);
11
+ }
8
12
  // Navigation handler with real-time progress
9
13
  export async function handleNavigate(args) {
10
14
  const progressNotifier = getProgressNotifier();
@@ -42,6 +46,37 @@ export async function handleNavigate(args) {
42
46
  }
43
47
  catch (error) {
44
48
  lastError = error instanceof Error ? error : new Error(String(error));
49
+ // Check if this is an API endpoint that failed with ERR_ABORTED
50
+ const isAborted = lastError.message.includes('ERR_ABORTED') ||
51
+ lastError.message.includes('net::ERR_');
52
+ if (isAborted && isApiEndpoint(url)) {
53
+ // Fallback: Use fetch to get API response directly
54
+ tracker.setProgress(50, '📡 Detected API endpoint, using fetch fallback...');
55
+ try {
56
+ const apiResponse = await pageInstance.evaluate(async (apiUrl) => {
57
+ const response = await fetch(apiUrl);
58
+ const text = await response.text();
59
+ return {
60
+ status: response.status,
61
+ contentType: response.headers.get('content-type'),
62
+ data: text
63
+ };
64
+ }, url);
65
+ tracker.complete('📡 API endpoint fetched successfully');
66
+ return {
67
+ content: [
68
+ {
69
+ type: 'text',
70
+ text: `API Response from ${url}\n\nStatus: ${apiResponse.status}\nContent-Type: ${apiResponse.contentType}\n\nData:\n${apiResponse.data}`,
71
+ },
72
+ ],
73
+ };
74
+ }
75
+ catch (fetchError) {
76
+ // Fetch also failed, continue with normal retry
77
+ tracker.setProgress(55, '❌ Fetch fallback failed, retrying navigation...');
78
+ }
79
+ }
45
80
  tracker.setProgress(20 + attempt * 20, `❌ Attempt ${attempt} failed: ${lastError.message}`);
46
81
  if (attempt < maxRetries) {
47
82
  const delay = 1000 * Math.pow(2, attempt - 1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.27.32",
3
+ "version": "2.28.1",
4
4
  "description": "🦁 MCP server for Brave Real Browser - NPM Workspaces Monorepo with anti-detection features, SSE streaming, and LSP compatibility",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -50,7 +50,7 @@
50
50
  "dependencies": {
51
51
  "@modelcontextprotocol/sdk": "latest",
52
52
  "@types/turndown": "latest",
53
- "brave-real-browser": "^2.8.32",
53
+ "brave-real-browser": "^2.8.33",
54
54
  "puppeteer-core": "^24.35.0",
55
55
  "turndown": "latest",
56
56
  "vscode-languageserver": "^9.0.1",