Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- const WebSocket = require('ws');
- const geckodriver = require('geckodriver');
- const { execSync } = require('child_process');
- const path = require('path');
- const os = require('os');
- const fs = require('fs');
- const WEBDRIVER_PORT = 4444;
- const WEBDRIVER_URL = `http://127.0.0.1:${WEBDRIVER_PORT}`;
- const LOG_FILE = path.join(process.cwd(), 'debug.log');
- // Clear previous log file
- try {
- fs.unlinkSync(LOG_FILE);
- } catch (_e) {
- // File doesn't exist yet
- }
- function logToFile(message) {
- fs.appendFileSync(LOG_FILE, `${message}\n`);
- }
- function log(message) {
- console.log(message);
- logToFile(message);
- }
- function getFirefoxPath() {
- const cacheDir = path.join(os.homedir(), '.cache', 'puppeteer', 'firefox');
- try {
- const fs = require('fs');
- const dirs = fs.readdirSync(cacheDir);
- const nightlyDirs = dirs.filter(d => d.includes('nightly')).sort().reverse();
- if (nightlyDirs.length > 0) {
- const latestNightly = nightlyDirs[0];
- const firefoxPath = path.join(cacheDir, latestNightly, 'firefox', 'firefox.exe');
- if (fs.existsSync(firefoxPath)) {
- return firefoxPath;
- }
- }
- } catch (_error) {
- // Fall back to system Firefox
- }
- return null;
- }
- function delay(ms) {
- return new Promise(resolve => setTimeout(resolve, ms));
- }
- async function waitForDriverReady(timeoutMs = 10000) {
- const startedAt = Date.now();
- while (Date.now() - startedAt < timeoutMs) {
- try {
- const response = await fetch(`${WEBDRIVER_URL}/status`);
- if (response.ok) {
- return;
- }
- } catch (_error) {
- // Wait until the driver starts listening.
- }
- await delay(150);
- }
- throw new Error('geckodriver did not become ready in time.');
- }
- async function createSession() {
- const response = await fetch(`${WEBDRIVER_URL}/session`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({
- capabilities: {
- alwaysMatch: {
- browserName: 'firefox',
- webSocketUrl: true,
- 'moz:firefoxOptions': {
- args: [],
- prefs: {
- 'remote.log.level': 'Trace',
- },
- },
- },
- },
- }),
- });
- if (!response.ok) {
- const body = await response.text();
- throw new Error(`Failed to create WebDriver session: ${response.status} ${body}`);
- }
- const payload = await response.json();
- const value = payload.value || {};
- const sessionId = value.sessionId;
- const webSocketUrl = value.capabilities?.webSocketUrl;
- if (!sessionId || !webSocketUrl) {
- throw new Error(`Session response missing sessionId/webSocketUrl: ${JSON.stringify(payload)}`);
- }
- return { sessionId, webSocketUrl };
- }
- async function deleteSession(sessionId) {
- await fetch(`${WEBDRIVER_URL}/session/${sessionId}`, { method: 'DELETE' }).catch(() => {});
- }
- function makeBidiClient(webSocketUrl) {
- const ws = new WebSocket(webSocketUrl);
- let nextId = 1;
- const pending = new Map();
- ws.on('message', raw => {
- const message = JSON.parse(raw.toString());
- logToFile('[bidi-message] ' + JSON.stringify(message));
- if (message.type === 'success' || message.type === 'error') {
- const waiter = pending.get(message.id);
- if (waiter) {
- pending.delete(message.id);
- if (message.type === 'success') {
- waiter.resolve(message.result);
- } else {
- waiter.reject(new Error(`${message.error}: ${message.message}`));
- }
- }
- return;
- }
- if (message.type === 'event' && message.method === 'log.entryAdded') {
- const text = message.params?.text ?? '';
- const msg = `[page] ${text}`;
- console.log(msg);
- logToFile(msg);
- }
- });
- const ready = new Promise((resolve, reject) => {
- ws.once('open', resolve);
- ws.once('error', reject);
- });
- function send(method, params = {}) {
- const id = nextId++;
- const payload = { id, method, params };
- return new Promise((resolve, reject) => {
- pending.set(id, { resolve, reject });
- ws.send(JSON.stringify(payload), error => {
- if (error) {
- pending.delete(id);
- reject(error);
- }
- });
- });
- }
- return { ws, ready, send };
- }
- async function main() {
- log('[setup] Installing latest Firefox nightly...');
- try {
- execSync('npx puppeteer browsers install firefox@nightly', { stdio: 'inherit' });
- } catch (error) {
- log('[setup] Failed to install Firefox nightly: ' + error.message);
- process.exit(1);
- }
- const firefoxPath = getFirefoxPath();
- if (firefoxPath) {
- log(`[setup] Using Firefox nightly: ${firefoxPath}`);
- } else {
- log('[setup] Firefox nightly not found in cache, geckodriver will use system Firefox');
- }
- const geckodriverProcess = await geckodriver.start({
- port: WEBDRIVER_PORT,
- spawnOpts: {
- stdio: ['ignore', 'pipe', 'pipe'],
- windowsHide: false,
- env: { ...process.env, MOZ_FIREFOX_BIN: firefoxPath || undefined },
- },
- });
- geckodriverProcess.stdout.on('data', data => {
- const msg = `[geckodriver] ${data}`;
- process.stdout.write(msg);
- logToFile(msg);
- });
- geckodriverProcess.stderr.on('data', data => {
- const msg = `[geckodriver] ${data}`;
- process.stderr.write(msg);
- logToFile(msg);
- });
- await waitForDriverReady();
- const { sessionId, webSocketUrl } = await createSession();
- const bidi = makeBidiClient(webSocketUrl);
- await bidi.ready;
- await bidi.send('session.subscribe', { events: ['log.entryAdded'] });
- const created = await bidi.send('browsingContext.create', { type: 'tab' });
- const context = created.context;
- const preloadFunction = `
- () => {
- window.myInjected = {
- printVisible(text = 'Hello from myInjected.printVisible()') {
- console.log('[myInjected] ' + text);
- const banner = document.createElement('div');
- banner.textContent = text;
- banner.style.position = 'fixed';
- banner.style.top = '16px';
- banner.style.right = '16px';
- banner.style.zIndex = '2147483647';
- banner.style.padding = '10px 14px';
- banner.style.borderRadius = '8px';
- banner.style.background = '#4caf50';
- banner.style.color = '#fff';
- banner.style.fontSize = '16px';
- banner.style.fontWeight = '700';
- banner.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.25)';
- document.body.appendChild(banner);
- setTimeout(() => banner.remove(), 3000);
- return "hello from inject " + window.location.href
- },
- };
- }
- `;
- await bidi.send('script.addPreloadScript', {
- functionDeclaration: preloadFunction,
- contexts: [context],
- });
- const wikipediaUrls = [
- 'https://www.wikipedia.org/',
- 'https://en.wikipedia.org/wiki/WebDriver',
- 'https://en.wikipedia.org/wiki/Firefox',
- 'https://en.wikipedia.org/wiki/Browser_engine',
- ];
- for (const url of wikipediaUrls) {
- log(`[nav] ${url}`);
- await bidi.send('browsingContext.navigate', {
- context,
- url,
- wait: 'interactive',
- });
- const res = await bidi.send('script.evaluate', {
- expression: `window.myInjected && window.myInjected.printVisible(${JSON.stringify(`Visited ${url}`)})`,
- awaitPromise: true,
- target: { context },
- });
- console.log(res)
- }
- log('[complete] All logs saved to: ' + LOG_FILE);
- const shutdown = async () => {
- bidi.ws.close();
- await deleteSession(sessionId);
- if (geckodriverProcess.pid) {
- geckodriverProcess.kill();
- }
- };
- process.once('SIGINT', async () => {
- await shutdown();
- process.exit(0);
- });
- process.once('SIGTERM', async () => {
- await shutdown();
- process.exit(0);
- });
- }
- main().catch(error => {
- log('[error] ' + error.message);
- log('[error] Stack: ' + error.stack);
- process.exit(1);
- })
Advertisement
Add Comment
Please, Sign In to add comment