brave-real-browser-mcp-server 2.42.0 → 2.43.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 CHANGED
@@ -681,15 +681,22 @@ console.log(blocker === sameBlocker); // true
681
681
  | `npm run mcp` | Start MCP server (alias) |
682
682
  | `npm run mcp:verbose` | Start MCP server with tool details |
683
683
  | `npm run lsp` | Start LSP server for IDE intelligence |
684
+ | `npm run lsp:tcp` | Start LSP server on TCP port 7777 |
684
685
  | `npm run list` | List all 23 optimized tools with categories |
685
- | `npm install` | Install all dependencies with workspace linking |
686
+ | `npm install` | Install dependencies + auto-build workspaces + Brave check |
686
687
  | `npm test` | Run all tests (CJS + ESM) |
687
688
  | `npm run cjs_test` | Run CommonJS tests only |
688
689
  | `npm run esm_test` | Run ESM tests only |
689
- | `npm run build` | Build root package |
690
- | `npm run build:all` | Build all workspace packages |
690
+ | `npm run build` | Build root package (pre-built lib/) |
691
+ | `npm run build:all` | Build all workspace packages (blocker, launcher, puppeteer-core) |
692
+ | `npm run clean:all` | Clean all workspace build outputs |
693
+ | `npm run test:all` | Run tests for all workspace packages |
694
+ | `npm run update-deps` | Manually check for dependency updates |
695
+ | `npm run upstream-patch` | Update to latest upstream (puppeteer-core, playwright-core) |
691
696
  | `npm run lint` | Run linter |
692
697
 
698
+ > **Note:** `npm install` automatically runs `npm run build:all` to compile TypeScript packages (brave-real-blocker, brave-real-launcher) before the auto-update check. This ensures the browser tools work immediately after installation.
699
+
693
700
  ### Workspace Scripts
694
701
 
695
702
  | Command | Description |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-browser-mcp-server",
3
- "version": "2.42.0",
3
+ "version": "2.43.0",
4
4
  "description": "MCP Server for Brave Real Browser - Puppeteer with Brave Browser, Stealth Mode, Ad Blocker, and Turnstile Auto-Solver for undetectable web automation.",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/esm/index.mjs",
@@ -43,7 +43,7 @@
43
43
  "build:all": "npm run build --workspaces --if-present",
44
44
  "clean:all": "npm run clean --workspaces --if-present",
45
45
  "test:all": "npm run test --workspaces --if-present",
46
- "postinstall": "node scripts/auto-update-deps.js || true",
46
+ "postinstall": "npm run build:all && node scripts/auto-update-deps.js || true",
47
47
  "update-deps": "node scripts/auto-update-deps.js",
48
48
  "upstream-patch": "node scripts/upstream-patcher.js"
49
49
  },
@@ -74,7 +74,7 @@
74
74
  "license": "ISC",
75
75
  "dependencies": {
76
76
  "@modelcontextprotocol/sdk": "^1.25.3",
77
- "brave-real-puppeteer-core": "^24.37.2-brave.4",
77
+ "brave-real-puppeteer-core": "^24.37.2-brave.6",
78
78
  "ghost-cursor": "^1.4.2",
79
79
  "puppeteer-extra": "^3.3.6",
80
80
  "puppeteer-extra-plugin-stealth": "^2.11.2",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-blocker",
3
- "version": "1.18.0",
3
+ "version": "1.19.0",
4
4
  "description": "Advanced uBlock Origin management and stealth features for Brave Real Browser",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -64,7 +64,7 @@
64
64
  "@types/adm-zip": "^0.5.5",
65
65
  "@types/fs-extra": "^11.0.4",
66
66
  "@types/node": "^20.0.0",
67
- "brave-real-puppeteer-core": "^24.37.2-brave.4",
67
+ "brave-real-puppeteer-core": "^24.37.2-brave.6",
68
68
  "mocha": "^10.4.0",
69
69
  "puppeteer-core": ">=24.0.0",
70
70
  "sinon": "^17.0.1",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-launcher",
3
- "version": "1.24.0",
3
+ "version": "1.25.0",
4
4
  "description": "Launch Brave Browser with ease from node. Based on chrome-launcher with Brave-specific support.",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -54,7 +54,7 @@
54
54
  "typescript": "^5.0.0"
55
55
  },
56
56
  "dependencies": {
57
- "brave-real-blocker": "^1.18.0",
57
+ "brave-real-blocker": "^1.19.0",
58
58
  "escape-string-regexp": "^5.0.0",
59
59
  "is-wsl": "^3.1.0",
60
60
  "which": "^6.0.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-playwright-core",
3
- "version": "1.60.0",
3
+ "version": "1.61.0",
4
4
  "description": "Brave-optimized Playwright Core (v1.57.0) with comprehensive stealth patches and error stack sanitization",
5
5
  "keywords": [
6
6
  "playwright",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brave-real-puppeteer-core",
3
- "version": "24.37.2-brave.4",
3
+ "version": "24.37.2-brave.6",
4
4
  "description": "🦁 Brave Real-World Optimized Puppeteer & Playwright Core with 1-5ms ultra-fast timing, 50+ professional stealth features, intelligent browser auto-detection, and 100% bot detection bypass. Features cross-platform Brave browser integration, comprehensive anti-detection, and breakthrough performance improvements.",
5
5
  "keywords": [
6
6
  "automation",
@@ -135,7 +135,7 @@
135
135
  },
136
136
  "dependencies": {
137
137
  "@puppeteer/browsers": "2.12.0",
138
- "brave-real-launcher": "^1.24.0",
138
+ "brave-real-launcher": "^1.25.0",
139
139
  "chromium-bidi": "13.1.1",
140
140
  "debug": "^4.4.3",
141
141
  "devtools-protocol": "0.0.1566079",
@@ -793,17 +793,209 @@ const handlers = {
793
793
  return { success: true, type, value };
794
794
  },
795
795
 
796
- // 5. Click
796
+ // 5. Click (ENHANCED: iframe + hover + auto video player detection)
797
797
  async click(params) {
798
798
  const { page } = requireBrowser();
799
- const { selector, humanLike = true, clickCount = 1, delay = 0, autoAcceptDialogs = true } = params;
799
+ const {
800
+ selector,
801
+ humanLike = true,
802
+ clickCount = 1,
803
+ delay = 0,
804
+ autoAcceptDialogs = true,
805
+ retries = 3,
806
+ timeout = 60000,
807
+ // Hover support for video player dynamic controls
808
+ hoverFirst = false,
809
+ hoverOnly = false,
810
+ hoverDuration = 500,
811
+ // iframe support
812
+ iframe,
813
+ iframeSelector,
814
+ // Additional options
815
+ scrollIntoView = true,
816
+ forceClick = false,
817
+ // NEW: Auto Video Player Detection & Control
818
+ autoDetectPlayer = false,
819
+ usePlayerAPI = true,
820
+ waitForPlay = false,
821
+ playerTimeout = 15000
822
+ } = params;
823
+
824
+ notifyProgress('click', 'started', `${hoverOnly ? 'Hovering' : 'Clicking'}: ${selector}${iframe !== undefined ? ` (iframe ${iframe})` : ''}${autoDetectPlayer ? ' (auto-detect player)' : ''}`);
800
825
 
801
- notifyProgress('click', 'started', `Clicking: ${selector}`);
826
+ // Get the correct context (page or iframe)
827
+ let context = page;
828
+ let frameInfo = null;
829
+ let detectedPlayer = null;
830
+
831
+ // ═══════════════════════════════════════════════════════════════
832
+ // AUTO DETECT VIDEO PLAYER - Scan all iframes for video players
833
+ // Supports: JWPlayer, VideoJS, Plyr, VidStack, DooPlayer, HTML5
834
+ // ═══════════════════════════════════════════════════════════════
835
+ if (autoDetectPlayer) {
836
+ notifyProgress('click', 'progress', '🔍 Scanning all iframes for video players...');
837
+
838
+ const frames = page.frames();
839
+
840
+ for (let i = 0; i < frames.length; i++) {
841
+ try {
842
+ const frame = frames[i];
843
+ const frameUrl = frame.url();
844
+
845
+ // Skip blank frames
846
+ if (frameUrl === 'about:blank' || !frameUrl) continue;
847
+
848
+ // Detect player in this frame
849
+ const playerInfo = await frame.evaluate(() => {
850
+ const result = {
851
+ hasPlayer: false,
852
+ playerType: null,
853
+ hasVideo: false,
854
+ videoState: null,
855
+ controls: [],
856
+ downloadButton: null
857
+ };
858
+
859
+ // Check for video element
860
+ const video = document.querySelector('video');
861
+ if (video) {
862
+ result.hasVideo = true;
863
+ result.videoState = {
864
+ paused: video.paused,
865
+ currentTime: video.currentTime,
866
+ duration: video.duration,
867
+ readyState: video.readyState
868
+ };
869
+ }
870
+
871
+ // 1. JWPlayer Detection
872
+ if (window.jwplayer && typeof window.jwplayer === 'function') {
873
+ try {
874
+ const jw = window.jwplayer();
875
+ if (jw && jw.getState) {
876
+ result.hasPlayer = true;
877
+ result.playerType = 'jwplayer';
878
+ result.playerState = jw.getState();
879
+ result.controls.push('.jw-icon-display', '.jw-icon-playback', '[aria-label="Play"]');
880
+
881
+ // Find download button in JWPlayer
882
+ const dlBtn = document.querySelector('[aria-label="Download"], .jw-icon-download, [class*="download"]');
883
+ if (dlBtn) result.downloadButton = '[aria-label="Download"]';
884
+ }
885
+ } catch (e) {}
886
+ }
887
+
888
+ // 2. VideoJS Detection
889
+ if (window.videojs || document.querySelector('.video-js')) {
890
+ result.hasPlayer = true;
891
+ result.playerType = result.playerType || 'videojs';
892
+ result.controls.push('.vjs-big-play-button', '.vjs-play-control');
893
+ }
894
+
895
+ // 3. Plyr Detection
896
+ if (window.Plyr || document.querySelector('.plyr')) {
897
+ result.hasPlayer = true;
898
+ result.playerType = result.playerType || 'plyr';
899
+ result.controls.push('.plyr__control--play', '[data-plyr="play"]');
900
+ }
901
+
902
+ // 4. VidStack Detection
903
+ if (window.VidStack || document.querySelector('media-player')) {
904
+ result.hasPlayer = true;
905
+ result.playerType = result.playerType || 'vidstack';
906
+ result.controls.push('media-play-button', '[data-media-play]');
907
+ }
908
+
909
+ // 5. DooPlayer Detection
910
+ if (window.DooPlay || document.querySelector('#dooplay') || document.querySelector('.dooplay')) {
911
+ result.hasPlayer = true;
912
+ result.playerType = result.playerType || 'dooplayer';
913
+ result.controls.push('.play-btn', '.dooplay-play');
914
+ }
915
+
916
+ // 6. Generic HTML5 Video
917
+ if (result.hasVideo && !result.hasPlayer) {
918
+ result.hasPlayer = true;
919
+ result.playerType = 'html5';
920
+ result.controls.push('video');
921
+ }
922
+
923
+ // Find any download button
924
+ if (!result.downloadButton) {
925
+ const dlSelectors = [
926
+ '[aria-label="Download"]', '[aria-label*="download"]',
927
+ '.download-btn', '.download', '[class*="download"]',
928
+ 'a[download]', 'button[class*="download"]'
929
+ ];
930
+ for (const sel of dlSelectors) {
931
+ if (document.querySelector(sel)) {
932
+ result.downloadButton = sel;
933
+ break;
934
+ }
935
+ }
936
+ }
937
+
938
+ return result;
939
+ }).catch(() => ({ hasPlayer: false }));
940
+
941
+ if (playerInfo.hasPlayer) {
942
+ context = frame;
943
+ frameInfo = {
944
+ index: i,
945
+ url: frameUrl,
946
+ autoDetected: true
947
+ };
948
+ detectedPlayer = {
949
+ type: playerInfo.playerType,
950
+ state: playerInfo.playerState || playerInfo.videoState,
951
+ controls: playerInfo.controls,
952
+ downloadButton: playerInfo.downloadButton
953
+ };
954
+
955
+ notifyProgress('click', 'progress',
956
+ `✅ Found ${playerInfo.playerType.toUpperCase()} in iframe ${i}: ${frameUrl.substring(0, 50)}...`);
957
+ break;
958
+ }
959
+ } catch (e) {
960
+ // Skip frames that can't be accessed
961
+ continue;
962
+ }
963
+ }
964
+
965
+ if (!detectedPlayer) {
966
+ notifyProgress('click', 'progress', '⚠️ No video player found in any iframe, using main page');
967
+ }
968
+ }
969
+
970
+ // Manual iframe selection (if not auto-detected)
971
+ if (!autoDetectPlayer && (iframe !== undefined || iframeSelector)) {
972
+ try {
973
+ const frames = page.frames();
974
+
975
+ if (iframe !== undefined && frames[iframe]) {
976
+ context = frames[iframe];
977
+ frameInfo = { index: iframe, url: frames[iframe].url() };
978
+ notifyProgress('click', 'progress', `Switched to iframe ${iframe}: ${frames[iframe].url().substring(0, 50)}...`);
979
+ } else if (iframeSelector) {
980
+ const iframeHandle = await page.$(iframeSelector);
981
+ if (iframeHandle) {
982
+ const frame = await iframeHandle.contentFrame();
983
+ if (frame) {
984
+ context = frame;
985
+ frameInfo = { selector: iframeSelector, url: frame.url() };
986
+ notifyProgress('click', 'progress', `Switched to iframe by selector: ${iframeSelector}`);
987
+ }
988
+ }
989
+ }
990
+ } catch (e) {
991
+ notifyProgress('click', 'progress', `Warning: Could not switch to iframe - ${e.message}`);
992
+ }
993
+ }
802
994
 
803
995
  // Auto-close any blocking modals before clicking
804
996
  await handlers._handleBlockingModals(page);
805
997
 
806
- // Auto-handle dialogs (alerts, confirms, prompts) to prevent blocking
998
+ // Auto-handle dialogs
807
999
  let dialogHandled = false;
808
1000
  const dialogHandler = async (dialog) => {
809
1001
  dialogHandled = true;
@@ -812,64 +1004,326 @@ const handlers = {
812
1004
  notifyProgress('click', 'progress', `🔔 Auto-accepting ${type}: ${message.substring(0, 50)}...`);
813
1005
  try {
814
1006
  await dialog.accept();
815
- } catch (e) {
816
- // Ignore if already handled by global handler
817
- }
1007
+ } catch (e) { }
818
1008
  };
819
1009
 
820
1010
  if (autoAcceptDialogs) {
821
1011
  page.on('dialog', dialogHandler);
822
1012
  }
823
1013
 
1014
+ let lastError = null;
1015
+ let playerResult = null;
1016
+
824
1017
  try {
825
- if (humanLike) {
826
- try {
827
- const { createCursor } = require('ghost-cursor');
828
- const cursor = createCursor(page);
829
- await cursor.click(selector);
830
- notifyProgress('click', 'progress', 'Used human-like cursor movement');
831
- } catch (e) {
832
- await page.click(selector, { clickCount, delay });
1018
+ // ═══════════════════════════════════════════════════════════════
1019
+ // USE PLAYER API - More reliable than DOM click for video players
1020
+ // ═══════════════════════════════════════════════════════════════
1021
+ if (usePlayerAPI && detectedPlayer && (selector === 'video' || selector.includes('play') || selector.includes('Play'))) {
1022
+ notifyProgress('click', 'progress', `🎬 Using ${detectedPlayer.type} API for playback...`);
1023
+
1024
+ playerResult = await context.evaluate((playerType) => {
1025
+ const result = { success: false, method: null, state: null };
1026
+
1027
+ try {
1028
+ if (playerType === 'jwplayer' && window.jwplayer) {
1029
+ const jw = window.jwplayer();
1030
+ const stateBefore = jw.getState();
1031
+ jw.play();
1032
+ result.success = true;
1033
+ result.method = 'jwplayer.play()';
1034
+ result.stateBefore = stateBefore;
1035
+ result.stateAfter = jw.getState();
1036
+ } else if (playerType === 'videojs' && window.videojs) {
1037
+ const player = window.videojs.getPlayers()[Object.keys(window.videojs.getPlayers())[0]];
1038
+ if (player) {
1039
+ player.play();
1040
+ result.success = true;
1041
+ result.method = 'videojs.play()';
1042
+ }
1043
+ } else if (playerType === 'plyr' && window.Plyr) {
1044
+ const plyr = document.querySelector('.plyr')?.__plyr;
1045
+ if (plyr) {
1046
+ plyr.play();
1047
+ result.success = true;
1048
+ result.method = 'plyr.play()';
1049
+ }
1050
+ } else {
1051
+ // Fallback to HTML5 video
1052
+ const video = document.querySelector('video');
1053
+ if (video) {
1054
+ video.play();
1055
+ result.success = true;
1056
+ result.method = 'video.play()';
1057
+ }
1058
+ }
1059
+ } catch (e) {
1060
+ result.error = e.message;
1061
+ }
1062
+
1063
+ return result;
1064
+ }, detectedPlayer.type).catch(e => ({ success: false, error: e.message }));
1065
+
1066
+ if (playerResult.success) {
1067
+ notifyProgress('click', 'progress', `✅ ${playerResult.method} executed`);
1068
+
1069
+ // Wait for play if requested
1070
+ if (waitForPlay) {
1071
+ notifyProgress('click', 'progress', '⏳ Waiting for video to start playing...');
1072
+
1073
+ const startTime = Date.now();
1074
+ let isPlaying = false;
1075
+
1076
+ while (Date.now() - startTime < playerTimeout) {
1077
+ const state = await context.evaluate(() => {
1078
+ const video = document.querySelector('video');
1079
+ if (video) {
1080
+ return {
1081
+ paused: video.paused,
1082
+ currentTime: video.currentTime,
1083
+ playing: !video.paused && video.currentTime > 0
1084
+ };
1085
+ }
1086
+ if (window.jwplayer) {
1087
+ const jw = window.jwplayer();
1088
+ return { playing: jw.getState() === 'playing', jwState: jw.getState() };
1089
+ }
1090
+ return { playing: false };
1091
+ }).catch(() => ({ playing: false }));
1092
+
1093
+ if (state.playing || state.currentTime > 0) {
1094
+ isPlaying = true;
1095
+ notifyProgress('click', 'progress', `▶️ Video is now playing (${state.currentTime?.toFixed(1) || 0}s)`);
1096
+ break;
1097
+ }
1098
+
1099
+ await new Promise(r => setTimeout(r, 500));
1100
+ }
1101
+
1102
+ if (!isPlaying) {
1103
+ notifyProgress('click', 'progress', '⚠️ Video may still be buffering');
1104
+ }
1105
+ }
1106
+
1107
+ notifyProgress('click', 'completed', `Video playback started via ${playerResult.method}`, {
1108
+ selector,
1109
+ clicked: true,
1110
+ playerAPI: true,
1111
+ detectedPlayer,
1112
+ iframe: frameInfo,
1113
+ playerResult
1114
+ });
1115
+
1116
+ return {
1117
+ success: true,
1118
+ selector,
1119
+ clicked: true,
1120
+ playerAPI: true,
1121
+ detectedPlayer,
1122
+ iframe: frameInfo,
1123
+ playerResult,
1124
+ dialogHandled
1125
+ };
833
1126
  }
834
- } else {
835
- await page.click(selector, { clickCount, delay });
836
1127
  }
837
1128
 
838
- // Small wait to allow dialogs to appear and be handled
839
- await new Promise(r => setTimeout(r, 300));
1129
+ // Retry loop for regular click
1130
+ for (let attempt = 1; attempt <= retries; attempt++) {
1131
+ try {
1132
+ // Wait for selector with timeout
1133
+ try {
1134
+ await context.waitForSelector(selector, { timeout: Math.min(timeout / retries, 10000) });
1135
+ } catch (e) {
1136
+ if (attempt < retries) {
1137
+ notifyProgress('click', 'progress', `Selector not found, retry ${attempt}/${retries}...`);
1138
+ await new Promise(r => setTimeout(r, 1000));
1139
+ continue;
1140
+ }
1141
+ throw new Error(`Selector not found: ${selector}`);
1142
+ }
1143
+
1144
+ // Scroll into view if needed
1145
+ if (scrollIntoView) {
1146
+ await context.evaluate((sel) => {
1147
+ const el = document.querySelector(sel);
1148
+ if (el) el.scrollIntoView({ behavior: 'smooth', block: 'center' });
1149
+ }, selector);
1150
+ await new Promise(r => setTimeout(r, 300));
1151
+ }
1152
+
1153
+ // HOVER functionality (for video player dynamic controls)
1154
+ if (hoverFirst || hoverOnly) {
1155
+ notifyProgress('click', 'progress', `Hovering over ${selector}...`);
1156
+
1157
+ try {
1158
+ await context.hover(selector);
1159
+ notifyProgress('click', 'progress', `Hover successful, waiting ${hoverDuration}ms for controls...`);
1160
+ await new Promise(r => setTimeout(r, hoverDuration));
1161
+ } catch (hoverErr) {
1162
+ notifyProgress('click', 'progress', `Standard hover failed, trying mouse movement...`);
1163
+ const element = await context.$(selector);
1164
+ if (element) {
1165
+ const box = await element.boundingBox();
1166
+ if (box) {
1167
+ await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
1168
+ await new Promise(r => setTimeout(r, hoverDuration));
1169
+ }
1170
+ }
1171
+ }
1172
+
1173
+ if (hoverOnly) {
1174
+ notifyProgress('click', 'completed', `Hover completed: ${selector}`, { selector, hovered: true, iframe: frameInfo });
1175
+ return { success: true, selector, hovered: true, clicked: false, iframe: frameInfo, detectedPlayer };
1176
+ }
1177
+ }
1178
+
1179
+ // CLICK functionality
1180
+ if (forceClick) {
1181
+ await context.evaluate((sel) => {
1182
+ const el = document.querySelector(sel);
1183
+ if (el) el.click();
1184
+ }, selector);
1185
+ notifyProgress('click', 'progress', 'Used force click (JS)');
1186
+ } else if (humanLike) {
1187
+ try {
1188
+ const { createCursor } = require('ghost-cursor');
1189
+ const cursor = createCursor(page);
1190
+
1191
+ if (context !== page) {
1192
+ const element = await context.$(selector);
1193
+ if (element) {
1194
+ const box = await element.boundingBox();
1195
+ if (box) {
1196
+ await cursor.moveTo({ x: box.x + box.width / 2, y: box.y + box.height / 2 });
1197
+ await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2, { clickCount, delay });
1198
+ }
1199
+ }
1200
+ } else {
1201
+ await cursor.click(selector);
1202
+ }
1203
+ notifyProgress('click', 'progress', 'Used human-like cursor movement');
1204
+ } catch (e) {
1205
+ await context.click(selector, { clickCount, delay });
1206
+ }
1207
+ } else {
1208
+ await context.click(selector, { clickCount, delay });
1209
+ }
1210
+
1211
+ await new Promise(r => setTimeout(r, 300));
1212
+
1213
+ notifyProgress('click', 'completed',
1214
+ `${hoverFirst ? 'Hovered+' : ''}Clicked: ${selector}${dialogHandled ? ' (dialog auto-accepted)' : ''}`,
1215
+ { selector, humanLike, dialogHandled, iframe: frameInfo, detectedPlayer, attempts: attempt }
1216
+ );
1217
+
1218
+ return {
1219
+ success: true,
1220
+ selector,
1221
+ clicked: true,
1222
+ dialogHandled,
1223
+ iframe: frameInfo,
1224
+ detectedPlayer,
1225
+ attempts: attempt
1226
+ };
1227
+
1228
+ } catch (attemptError) {
1229
+ lastError = attemptError;
1230
+ if (attempt < retries) {
1231
+ notifyProgress('click', 'progress', `Attempt ${attempt} failed: ${attemptError.message}, retrying...`);
1232
+ await new Promise(r => setTimeout(r, 1000));
1233
+ }
1234
+ }
1235
+ }
840
1236
 
841
- notifyProgress('click', 'completed', `Clicked: ${selector}${dialogHandled ? ' (dialog auto-accepted)' : ''}`, { selector, humanLike, dialogHandled });
1237
+ throw lastError || new Error('Click failed after all retries');
842
1238
 
843
- return { success: true, selector, clicked: true, dialogHandled };
844
1239
  } finally {
845
- // Remove dialog handler to prevent memory leaks
846
1240
  if (autoAcceptDialogs) {
847
1241
  page.off('dialog', dialogHandler);
848
1242
  }
849
1243
  }
850
1244
  },
851
1245
 
852
- // 6. Type
1246
+ // 6. Type (ENHANCED: iframe support)
853
1247
  async type(params) {
854
1248
  const { page } = requireBrowser();
855
- const { selector, text, delay = 50, clear = false } = params;
1249
+ const {
1250
+ selector,
1251
+ text,
1252
+ delay = 50,
1253
+ clear = false,
1254
+ // NEW: iframe support
1255
+ iframe,
1256
+ iframeSelector,
1257
+ // NEW: Additional options
1258
+ pressEnter = false,
1259
+ waitForSelector = true
1260
+ } = params;
1261
+
1262
+ notifyProgress('type', 'started', `Typing ${text.length} characters into ${selector}${iframe !== undefined ? ` (iframe ${iframe})` : ''}`);
856
1263
 
857
- notifyProgress('type', 'started', `Typing ${text.length} characters into ${selector}`);
1264
+ // Get the correct context (page or iframe)
1265
+ let context = page;
1266
+ let frameInfo = null;
1267
+
1268
+ if (iframe !== undefined || iframeSelector) {
1269
+ try {
1270
+ const frames = page.frames();
1271
+
1272
+ if (iframe !== undefined && frames[iframe]) {
1273
+ context = frames[iframe];
1274
+ frameInfo = { index: iframe, url: frames[iframe].url() };
1275
+ notifyProgress('type', 'progress', `Switched to iframe ${iframe}`);
1276
+ } else if (iframeSelector) {
1277
+ const iframeHandle = await page.$(iframeSelector);
1278
+ if (iframeHandle) {
1279
+ const frame = await iframeHandle.contentFrame();
1280
+ if (frame) {
1281
+ context = frame;
1282
+ frameInfo = { selector: iframeSelector, url: frame.url() };
1283
+ notifyProgress('type', 'progress', `Switched to iframe by selector`);
1284
+ }
1285
+ }
1286
+ }
1287
+ } catch (e) {
1288
+ notifyProgress('type', 'progress', `Warning: Could not switch to iframe - ${e.message}`);
1289
+ }
1290
+ }
858
1291
 
859
1292
  // Auto-close any blocking modals before typing
860
1293
  await handlers._handleBlockingModals(page);
861
1294
 
1295
+ // Wait for selector if enabled
1296
+ if (waitForSelector) {
1297
+ try {
1298
+ await context.waitForSelector(selector, { timeout: 10000 });
1299
+ } catch (e) {
1300
+ notifyProgress('type', 'error', `Selector not found: ${selector}`);
1301
+ return { success: false, error: `Selector not found: ${selector}` };
1302
+ }
1303
+ }
1304
+
1305
+ // Clear existing text if needed
862
1306
  if (clear) {
863
- await page.click(selector, { clickCount: 3 });
864
- await page.keyboard.press('Backspace');
1307
+ await context.click(selector, { clickCount: 3 });
1308
+ await context.evaluate((sel) => {
1309
+ const el = document.querySelector(sel);
1310
+ if (el) el.value = '';
1311
+ }, selector);
865
1312
  notifyProgress('type', 'progress', 'Cleared existing text');
866
1313
  }
867
1314
 
868
- await page.type(selector, text, { delay });
1315
+ // Type text with human-like delays
1316
+ await context.type(selector, text, { delay });
1317
+
1318
+ // Press Enter if requested
1319
+ if (pressEnter) {
1320
+ await context.keyboard.press('Enter');
1321
+ notifyProgress('type', 'progress', 'Pressed Enter');
1322
+ }
869
1323
 
870
- notifyProgress('type', 'completed', `Typed ${text.length} characters`, { selector, textLength: text.length });
1324
+ notifyProgress('type', 'completed', `Typed ${text.length} characters`, { selector, textLength: text.length, iframe: frameInfo });
871
1325
 
872
- return { success: true, selector, textLength: text.length };
1326
+ return { success: true, selector, textLength: text.length, iframe: frameInfo };
873
1327
  },
874
1328
 
875
1329
  // 7. Browser Close
@@ -2295,18 +2749,88 @@ const handlers = {
2295
2749
  return { success: true, selector, content };
2296
2750
  },
2297
2751
 
2298
- // 26. Execute JS
2752
+ // 26. Execute JS (ENHANCED: iframe context support - FIXED)
2299
2753
  async execute_js(params) {
2300
2754
  const { page } = requireBrowser();
2301
- const { code, returnValue = true } = params;
2755
+ const {
2756
+ code,
2757
+ returnValue = true,
2758
+ // NEW: iframe support (FIXED)
2759
+ iframe,
2760
+ iframeSelector,
2761
+ waitForIframe = true,
2762
+ timeout = 30000
2763
+ } = params;
2764
+
2765
+ notifyProgress('execute_js', 'started', `Executing JavaScript...${iframe !== undefined ? ` (iframe ${iframe})` : ''}`);
2766
+
2767
+ // Get the correct context (page or iframe)
2768
+ let context = page;
2769
+ let frameInfo = null;
2770
+
2771
+ if (iframe !== undefined || iframeSelector) {
2772
+ try {
2773
+ const frames = page.frames();
2774
+
2775
+ if (iframe !== undefined) {
2776
+ // iframe index provided
2777
+ if (iframe === 0) {
2778
+ // index 0 = main frame
2779
+ context = page.mainFrame();
2780
+ frameInfo = { index: 0, url: page.url(), isMain: true };
2781
+ } else if (frames[iframe]) {
2782
+ context = frames[iframe];
2783
+ frameInfo = { index: iframe, url: frames[iframe].url() };
2784
+ notifyProgress('execute_js', 'progress', `Switched to iframe ${iframe}: ${frames[iframe].url().substring(0, 50)}...`);
2785
+ } else {
2786
+ notifyProgress('execute_js', 'error', `iframe index ${iframe} not found. Total frames: ${frames.length}`);
2787
+ return { success: false, error: `iframe index ${iframe} not found. Available: 0-${frames.length - 1}` };
2788
+ }
2789
+ } else if (iframeSelector) {
2790
+ // Find iframe by selector
2791
+ const iframeHandle = await page.$(iframeSelector);
2792
+ if (iframeHandle) {
2793
+ const frame = await iframeHandle.contentFrame();
2794
+ if (frame) {
2795
+ context = frame;
2796
+ frameInfo = { selector: iframeSelector, url: frame.url() };
2797
+ notifyProgress('execute_js', 'progress', `Switched to iframe by selector: ${iframeSelector}`);
2798
+ }
2799
+ } else {
2800
+ return { success: false, error: `iframe selector not found: ${iframeSelector}` };
2801
+ }
2802
+ }
2803
+
2804
+ // Wait for iframe to be ready if needed
2805
+ if (waitForIframe && context !== page) {
2806
+ try {
2807
+ await context.waitForFunction(() => document.readyState === 'complete', { timeout: 5000 });
2808
+ } catch (e) {
2809
+ notifyProgress('execute_js', 'progress', 'Warning: iframe may not be fully loaded');
2810
+ }
2811
+ }
2302
2812
 
2303
- notifyProgress('execute_js', 'started', 'Executing JavaScript...');
2813
+ } catch (e) {
2814
+ notifyProgress('execute_js', 'error', `iframe switch failed: ${e.message}`);
2815
+ return { success: false, error: `iframe switch failed: ${e.message}` };
2816
+ }
2817
+ }
2304
2818
 
2305
- const result = await page.evaluate(code);
2819
+ try {
2820
+ // Execute the code in the correct context
2821
+ const result = await context.evaluate(code);
2822
+
2823
+ notifyProgress('execute_js', 'completed', 'JavaScript executed', {
2824
+ hasResult: result !== undefined,
2825
+ iframe: frameInfo
2826
+ });
2306
2827
 
2307
- notifyProgress('execute_js', 'completed', 'JavaScript executed', { hasResult: result !== undefined });
2828
+ return { success: true, result: returnValue ? result : undefined, iframe: frameInfo };
2308
2829
 
2309
- return { success: true, result: returnValue ? result : undefined };
2830
+ } catch (evalError) {
2831
+ notifyProgress('execute_js', 'error', `Execution error: ${evalError.message}`);
2832
+ return { success: false, error: evalError.message, iframe: frameInfo };
2833
+ }
2310
2834
  },
2311
2835
 
2312
2836
  // 27. Player API Hook (ENHANCED - searches iframes, detects more player types)
@@ -111,12 +111,12 @@ const TOOLS = [
111
111
  }
112
112
  },
113
113
 
114
- // 5. Click
114
+ // 5. Click (ENHANCED: iframe + hover + auto video player detection)
115
115
  {
116
116
  name: 'click',
117
117
  emoji: '👆',
118
- description: 'Human-like click with AI healing and auto-retry',
119
- descriptionHindi: 'क्लिक करना (AI healing + human-like)',
118
+ description: 'Human-like click with AI healing, iframe support, hover for dynamic controls, and auto video player detection',
119
+ descriptionHindi: 'क्लिक करना (AI healing + iframe + auto video player detection)',
120
120
  category: 'interaction',
121
121
  requiresBrowser: true,
122
122
  requiresPage: true,
@@ -127,20 +127,36 @@ const TOOLS = [
127
127
  humanLike: { type: 'boolean', default: true, description: 'Ghost cursor human movement' },
128
128
  aiHeal: { type: 'boolean', default: true, description: 'Auto-find alternative selector if broken' },
129
129
  autoAcceptDialogs: { type: 'boolean', default: true, description: 'Auto-accept alerts/confirms to prevent blocking' },
130
- retries: { type: 'number', default: 3 },
130
+ retries: { type: 'number', default: 3, description: 'Auto-retry on failure' },
131
131
  clickCount: { type: 'number', default: 1 },
132
- delay: { type: 'number', default: 0 }
132
+ delay: { type: 'number', default: 0 },
133
+ timeout: { type: 'number', default: 60000, description: 'Timeout for element to appear' },
134
+ // Hover support for video player dynamic controls
135
+ hoverFirst: { type: 'boolean', default: false, description: 'Hover before click (for dynamic controls like video players)' },
136
+ hoverOnly: { type: 'boolean', default: false, description: 'Only hover, do not click (to reveal hidden controls)' },
137
+ hoverDuration: { type: 'number', default: 500, description: 'Wait time after hover before click (ms)' },
138
+ // iframe support
139
+ iframe: { type: 'number', description: 'Execute in specific iframe index (use media_extractor list_iframes to get index)' },
140
+ iframeSelector: { type: 'string', description: 'Alternative: iframe CSS selector instead of index' },
141
+ // Scroll into view
142
+ scrollIntoView: { type: 'boolean', default: true, description: 'Auto-scroll element into view before click' },
143
+ forceClick: { type: 'boolean', default: false, description: 'Force click even if element not visible (use JS click)' },
144
+ // NEW: Auto Video Player Detection & Control
145
+ autoDetectPlayer: { type: 'boolean', default: false, description: 'Auto-detect video player iframe (JWPlayer, VideoJS, Plyr, VidStack, DooPlayer)' },
146
+ usePlayerAPI: { type: 'boolean', default: true, description: 'Use player API (jwplayer.play()) instead of DOM click for reliable playback' },
147
+ waitForPlay: { type: 'boolean', default: false, description: 'Wait until video actually starts playing' },
148
+ playerTimeout: { type: 'number', default: 15000, description: 'Max wait time for video to start playing (ms)' }
133
149
  },
134
150
  required: ['selector']
135
151
  }
136
152
  },
137
153
 
138
- // 6. Type
154
+ // 6. Type (ENHANCED: iframe support)
139
155
  {
140
156
  name: 'type',
141
157
  emoji: '⌨️',
142
- description: 'Type text with human speed variation and smart clearing',
143
- descriptionHindi: 'टेक्स्ट टाइप करना (human speed)',
158
+ description: 'Type text with human speed variation, smart clearing, and iframe support',
159
+ descriptionHindi: 'टेक्स्ट टाइप करना (human speed + iframe support)',
144
160
  category: 'interaction',
145
161
  requiresBrowser: true,
146
162
  requiresPage: true,
@@ -151,7 +167,13 @@ const TOOLS = [
151
167
  text: { type: 'string' },
152
168
  delay: { type: 'number', default: 50, description: 'Keystroke delay with natural variation' },
153
169
  clear: { type: 'boolean', default: true },
154
- aiHeal: { type: 'boolean', default: true }
170
+ aiHeal: { type: 'boolean', default: true },
171
+ // NEW: iframe support
172
+ iframe: { type: 'number', description: 'Execute in specific iframe index' },
173
+ iframeSelector: { type: 'string', description: 'Alternative: iframe CSS selector' },
174
+ // NEW: Additional options
175
+ pressEnter: { type: 'boolean', default: false, description: 'Press Enter after typing' },
176
+ waitForSelector: { type: 'boolean', default: true, description: 'Wait for selector before typing' }
155
177
  },
156
178
  required: ['selector', 'text']
157
179
  }
@@ -520,12 +542,12 @@ const TOOLS = [
520
542
  }
521
543
  },
522
544
 
523
- // 22. Execute JS
545
+ // 22. Execute JS (ENHANCED: iframe context fix)
524
546
  {
525
547
  name: 'execute_js',
526
548
  emoji: '💻',
527
- description: 'Execute custom JavaScript with async support and error handling',
528
- descriptionHindi: 'कस्टम JS चलाना (async + error handling)',
549
+ description: 'Execute custom JavaScript with async support, error handling, and iframe context',
550
+ descriptionHindi: 'कस्टम JS चलाना (async + iframe context support)',
529
551
  category: 'interaction',
530
552
  requiresBrowser: true,
531
553
  requiresPage: true,
@@ -536,7 +558,10 @@ const TOOLS = [
536
558
  returnValue: { type: 'boolean', default: true },
537
559
  async: { type: 'boolean', default: false, description: 'Execute async code' },
538
560
  timeout: { type: 'number', default: 30000 },
539
- iframe: { type: 'number', description: 'Execute in specific iframe index' }
561
+ // FIXED: iframe context now works properly
562
+ iframe: { type: 'number', description: 'Execute in specific iframe index (0=main, 1+=iframes)' },
563
+ iframeSelector: { type: 'string', description: 'Alternative: iframe CSS selector' },
564
+ waitForIframe: { type: 'boolean', default: true, description: 'Wait for iframe to be ready' }
540
565
  },
541
566
  required: ['code']
542
567
  }