diff --git a/package.json b/package.json index 4a40bba..4d7b4ac 100644 --- a/package.json +++ b/package.json @@ -7,6 +7,7 @@ "postinstall": "playwright install chromium", "setup": "playwright install chromium", "setup:ubuntu": "playwright install --with-deps chromium", + "test:bikegear-proxy": "node scripts/testBikegearProxy.js", "start": "node server.js", "dev": "node server.js" }, diff --git a/scripts/testBikegearProxy.js b/scripts/testBikegearProxy.js new file mode 100644 index 0000000..26e3e6d --- /dev/null +++ b/scripts/testBikegearProxy.js @@ -0,0 +1,133 @@ +require("dotenv").config(); + +const path = require("node:path"); +const fs = require("node:fs/promises"); +const { chromium } = require("playwright"); + +const DEFAULT_URL = "https://bikegear.in/acerbis"; +const TEST_URL = process.argv[2] || process.env.BIKEGEAR_PROXY_TEST_URL || DEFAULT_URL; +const PROFILE_PATH = path.resolve("data/sources/bikegear/.browser-profile-test"); + +const STEALTH_SCRIPT = ` + (() => { + Object.defineProperty(navigator, 'webdriver', { get: () => false }); + const fakePlugin = (name, file, mimes) => { + const p = Object.create(Plugin.prototype); + Object.defineProperty(p, 'name', { get: () => name }); + Object.defineProperty(p, 'filename', { get: () => file }); + Object.defineProperty(p, 'length', { get: () => mimes.length }); + return p; + }; + Object.defineProperty(navigator, 'plugins', { + get: () => [ + fakePlugin('Chrome PDF Plugin', 'internal-pdf-viewer', ['application/x-google-chrome-pdf']), + fakePlugin('Chrome PDF Viewer', 'mhjfbmdgcfjbbpaeojofohoefgiehjai', ['application/pdf']), + fakePlugin('Native Client', 'internal-nacl-plugin', ['application/x-nacl']), + ], + }); + if (!window.chrome) window.chrome = { runtime: {}, loadTimes: () => {}, csi: () => {}, app: {} }; + Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] }); + })(); +`; + +function getProxyUrl() { + if (process.env.BIKEGEAR_PROXY_URL) { + return process.env.BIKEGEAR_PROXY_URL; + } + + const host = process.env.BIKEGEAR_PROXY_HOST; + const port = process.env.BIKEGEAR_PROXY_PORT; + if (!host || !port) { + return null; + } + + const protocol = String(process.env.BIKEGEAR_PROXY_PROTOCOL || "http").replace(/:$/, ""); + const username = process.env.BIKEGEAR_PROXY_USERNAME || ""; + const password = process.env.BIKEGEAR_PROXY_PASSWORD || ""; + const auth = username + ? `${encodeURIComponent(username)}:${encodeURIComponent(password)}@` + : ""; + + return `${protocol}://${auth}${host}:${port}`; +} + +function maskProxyUrl(proxyUrl) { + if (!proxyUrl) return "none"; + try { + const u = new URL(proxyUrl); + return `${u.protocol}//${u.username ? "@" : ""}${u.host}`; + } catch { + return ""; + } +} + +function buildProxyOptions(proxyUrl) { + if (!proxyUrl) return {}; + const u = new URL(proxyUrl); + return { + proxy: { + server: `${u.protocol}//${u.host}`, + ...(u.username + ? { + username: decodeURIComponent(u.username), + password: decodeURIComponent(u.password), + } + : {}), + }, + }; +} + +async function main() { + const proxyUrl = getProxyUrl(); + await fs.mkdir(PROFILE_PATH, { recursive: true }); + + console.log(`[BIKEGEAR-PROXY-TEST] url=${TEST_URL}`); + console.log(`[BIKEGEAR-PROXY-TEST] proxy=${maskProxyUrl(proxyUrl)}`); + + const context = await chromium.launchPersistentContext(PROFILE_PATH, { + headless: true, + args: [ + "--no-sandbox", + "--disable-setuid-sandbox", + "--disable-blink-features=AutomationControlled", + ], + userAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", + locale: "en-US", + viewport: { width: 1280, height: 800 }, + extraHTTPHeaders: { "Accept-Language": "en-US,en;q=0.9" }, + ...buildProxyOptions(proxyUrl), + }); + + try { + await context.addInitScript(STEALTH_SCRIPT); + const page = await context.newPage(); + const response = await page.goto(TEST_URL, { waitUntil: "domcontentloaded", timeout: 45000 }); + await page.waitForTimeout(8000); + + const status = response?.status() || 0; + const title = await page.title().catch(() => ""); + const html = await page.content(); + const productLinks = (html.match(/class="product-img/g) || []).length; + const hasCloudflare = /cloudflare|cf-error|cf-browser-verification|Just a moment/i.test(html); + + console.log(`[BIKEGEAR-PROXY-TEST] status=${status}`); + console.log(`[BIKEGEAR-PROXY-TEST] title=${title || "-"}`); + console.log(`[BIKEGEAR-PROXY-TEST] productLinks=${productLinks}`); + console.log(`[BIKEGEAR-PROXY-TEST] cloudflarePage=${hasCloudflare}`); + + if (status === 200 && productLinks > 0 && !hasCloudflare) { + console.log("[BIKEGEAR-PROXY-TEST] PASS browser can read BikeGear listing through this proxy."); + return; + } + + process.exitCode = 1; + console.error("[BIKEGEAR-PROXY-TEST] FAIL proxy/browser did not reach a usable BikeGear listing."); + } finally { + await context.close().catch(() => {}); + } +} + +main().catch((error) => { + process.exitCode = 1; + console.error(`[BIKEGEAR-PROXY-TEST] ERROR ${error.message}`); +});