Guest User

Untitled

a guest
Jun 11th, 2026
47
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. const WebSocket = require('ws');
  2. const geckodriver = require('geckodriver');
  3. const { execSync } = require('child_process');
  4. const path = require('path');
  5. const os = require('os');
  6. const fs = require('fs');
  7.  
  8. const WEBDRIVER_PORT = 4444;
  9. const WEBDRIVER_URL = `http://127.0.0.1:${WEBDRIVER_PORT}`;
  10. const LOG_FILE = path.join(process.cwd(), 'debug.log');
  11.  
  12. // Clear previous log file
  13. try {
  14.   fs.unlinkSync(LOG_FILE);
  15. } catch (_e) {
  16.   // File doesn't exist yet
  17. }
  18.  
  19. function logToFile(message) {
  20.   fs.appendFileSync(LOG_FILE, `${message}\n`);
  21. }
  22.  
  23. function log(message) {
  24.   console.log(message);
  25.   logToFile(message);
  26. }
  27.  
  28. function getFirefoxPath() {
  29.   const cacheDir = path.join(os.homedir(), '.cache', 'puppeteer', 'firefox');
  30.   try {
  31.     const fs = require('fs');
  32.     const dirs = fs.readdirSync(cacheDir);
  33.     const nightlyDirs = dirs.filter(d => d.includes('nightly')).sort().reverse();
  34.     if (nightlyDirs.length > 0) {
  35.       const latestNightly = nightlyDirs[0];
  36.       const firefoxPath = path.join(cacheDir, latestNightly, 'firefox', 'firefox.exe');
  37.       if (fs.existsSync(firefoxPath)) {
  38.         return firefoxPath;
  39.       }
  40.     }
  41.   } catch (_error) {
  42.     // Fall back to system Firefox
  43.   }
  44.   return null;
  45. }
  46.  
  47. function delay(ms) {
  48.   return new Promise(resolve => setTimeout(resolve, ms));
  49. }
  50.  
  51. async function waitForDriverReady(timeoutMs = 10000) {
  52.   const startedAt = Date.now();
  53.   while (Date.now() - startedAt < timeoutMs) {
  54.     try {
  55.       const response = await fetch(`${WEBDRIVER_URL}/status`);
  56.       if (response.ok) {
  57.         return;
  58.       }
  59.     } catch (_error) {
  60.       // Wait until the driver starts listening.
  61.     }
  62.     await delay(150);
  63.   }
  64.   throw new Error('geckodriver did not become ready in time.');
  65. }
  66.  
  67. async function createSession() {
  68.   const response = await fetch(`${WEBDRIVER_URL}/session`, {
  69.     method: 'POST',
  70.     headers: { 'Content-Type': 'application/json' },
  71.     body: JSON.stringify({
  72.       capabilities: {
  73.         alwaysMatch: {
  74.           browserName: 'firefox',
  75.           webSocketUrl: true,
  76.           'moz:firefoxOptions': {
  77.             args: [],
  78.             prefs: {
  79.               'remote.log.level': 'Trace',
  80.             },
  81.           },
  82.         },
  83.       },
  84.     }),
  85.   });
  86.  
  87.   if (!response.ok) {
  88.     const body = await response.text();
  89.     throw new Error(`Failed to create WebDriver session: ${response.status} ${body}`);
  90.   }
  91.  
  92.   const payload = await response.json();
  93.   const value = payload.value || {};
  94.   const sessionId = value.sessionId;
  95.   const webSocketUrl = value.capabilities?.webSocketUrl;
  96.   if (!sessionId || !webSocketUrl) {
  97.     throw new Error(`Session response missing sessionId/webSocketUrl: ${JSON.stringify(payload)}`);
  98.   }
  99.  
  100.   return { sessionId, webSocketUrl };
  101. }
  102.  
  103. async function deleteSession(sessionId) {
  104.   await fetch(`${WEBDRIVER_URL}/session/${sessionId}`, { method: 'DELETE' }).catch(() => {});
  105. }
  106.  
  107. function makeBidiClient(webSocketUrl) {
  108.   const ws = new WebSocket(webSocketUrl);
  109.   let nextId = 1;
  110.   const pending = new Map();
  111.  
  112.   ws.on('message', raw => {
  113.     const message = JSON.parse(raw.toString());
  114.     logToFile('[bidi-message] ' + JSON.stringify(message));
  115.     if (message.type === 'success' || message.type === 'error') {
  116.       const waiter = pending.get(message.id);
  117.       if (waiter) {
  118.         pending.delete(message.id);
  119.         if (message.type === 'success') {
  120.           waiter.resolve(message.result);
  121.         } else {
  122.           waiter.reject(new Error(`${message.error}: ${message.message}`));
  123.         }
  124.       }
  125.       return;
  126.     }
  127.  
  128.     if (message.type === 'event' && message.method === 'log.entryAdded') {
  129.       const text = message.params?.text ?? '';
  130.       const msg = `[page] ${text}`;
  131.       console.log(msg);
  132.       logToFile(msg);
  133.     }
  134.   });
  135.  
  136.   const ready = new Promise((resolve, reject) => {
  137.     ws.once('open', resolve);
  138.     ws.once('error', reject);
  139.   });
  140.  
  141.   function send(method, params = {}) {
  142.     const id = nextId++;
  143.     const payload = { id, method, params };
  144.     return new Promise((resolve, reject) => {
  145.       pending.set(id, { resolve, reject });
  146.       ws.send(JSON.stringify(payload), error => {
  147.         if (error) {
  148.           pending.delete(id);
  149.           reject(error);
  150.         }
  151.       });
  152.     });
  153.   }
  154.  
  155.   return { ws, ready, send };
  156. }
  157.  
  158. async function main() {
  159.   log('[setup] Installing latest Firefox nightly...');
  160.   try {
  161.     execSync('npx puppeteer browsers install firefox@nightly', { stdio: 'inherit' });
  162.   } catch (error) {
  163.     log('[setup] Failed to install Firefox nightly: ' + error.message);
  164.     process.exit(1);
  165.   }
  166.  
  167.   const firefoxPath = getFirefoxPath();
  168.   if (firefoxPath) {
  169.     log(`[setup] Using Firefox nightly: ${firefoxPath}`);
  170.   } else {
  171.     log('[setup] Firefox nightly not found in cache, geckodriver will use system Firefox');
  172.   }
  173.  
  174.   const geckodriverProcess = await geckodriver.start({
  175.     port: WEBDRIVER_PORT,
  176.     spawnOpts: {
  177.       stdio: ['ignore', 'pipe', 'pipe'],
  178.       windowsHide: false,
  179.       env: { ...process.env, MOZ_FIREFOX_BIN: firefoxPath || undefined },
  180.     },
  181.   });
  182.  
  183.   geckodriverProcess.stdout.on('data', data => {
  184.     const msg = `[geckodriver] ${data}`;
  185.     process.stdout.write(msg);
  186.     logToFile(msg);
  187.   });
  188.   geckodriverProcess.stderr.on('data', data => {
  189.     const msg = `[geckodriver] ${data}`;
  190.     process.stderr.write(msg);
  191.     logToFile(msg);
  192.   });
  193.  
  194.   await waitForDriverReady();
  195.   const { sessionId, webSocketUrl } = await createSession();
  196.   const bidi = makeBidiClient(webSocketUrl);
  197.   await bidi.ready;
  198.  
  199.   await bidi.send('session.subscribe', { events: ['log.entryAdded'] });
  200.   const created = await bidi.send('browsingContext.create', { type: 'tab' });
  201.   const context = created.context;
  202.  
  203.   const preloadFunction = `
  204.     () => {
  205.       window.myInjected = {
  206.         printVisible(text = 'Hello from myInjected.printVisible()') {
  207.           console.log('[myInjected] ' + text);
  208.           const banner = document.createElement('div');
  209.           banner.textContent = text;
  210.           banner.style.position = 'fixed';
  211.           banner.style.top = '16px';
  212.           banner.style.right = '16px';
  213.           banner.style.zIndex = '2147483647';
  214.           banner.style.padding = '10px 14px';
  215.           banner.style.borderRadius = '8px';
  216.           banner.style.background = '#4caf50';
  217.           banner.style.color = '#fff';
  218.           banner.style.fontSize = '16px';
  219.           banner.style.fontWeight = '700';
  220.           banner.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.25)';
  221.           document.body.appendChild(banner);
  222.           setTimeout(() => banner.remove(), 3000);
  223.           return "hello from inject " + window.location.href
  224.         },
  225.       };
  226.     }
  227.   `;
  228.  
  229.   await bidi.send('script.addPreloadScript', {
  230.     functionDeclaration: preloadFunction,
  231.     contexts: [context],
  232.   });
  233.  
  234.   const wikipediaUrls = [
  235.     'https://www.wikipedia.org/',
  236.     'https://en.wikipedia.org/wiki/WebDriver',
  237.     'https://en.wikipedia.org/wiki/Firefox',
  238.     'https://en.wikipedia.org/wiki/Browser_engine',
  239.   ];
  240.  
  241.   for (const url of wikipediaUrls) {
  242.     log(`[nav] ${url}`);
  243.     await bidi.send('browsingContext.navigate', {
  244.       context,
  245.       url,
  246.       wait: 'interactive',
  247.     });
  248.     const res = await bidi.send('script.evaluate', {
  249.       expression: `window.myInjected && window.myInjected.printVisible(${JSON.stringify(`Visited ${url}`)})`,
  250.       awaitPromise: true,
  251.       target: { context },
  252.     });
  253.     console.log(res)
  254.   }
  255.  
  256.   log('[complete] All logs saved to: ' + LOG_FILE);
  257.  
  258.   const shutdown = async () => {
  259.     bidi.ws.close();
  260.     await deleteSession(sessionId);
  261.     if (geckodriverProcess.pid) {
  262.       geckodriverProcess.kill();
  263.     }
  264.   };
  265.  
  266.   process.once('SIGINT', async () => {
  267.     await shutdown();
  268.     process.exit(0);
  269.   });
  270.   process.once('SIGTERM', async () => {
  271.     await shutdown();
  272.     process.exit(0);
  273.   });
  274. }
  275.  
  276. main().catch(error => {
  277.   log('[error] ' + error.message);
  278.   log('[error] Stack: ' + error.stack);
  279.   process.exit(1);
  280. })
Advertisement
Add Comment
Please, Sign In to add comment