UNPKG

@applitools/execution-grid-tunnel

Version:

Allows user to run tests with exection-grid and navigate to private hosts and ips

145 lines (134 loc) 4.27 kB
'use strict' const fs = require('fs').promises const fetch = require('node-fetch') const {PacError} = require('./pac-errors') /** * Load and compile a PAC source ONCE at startup. * * Resolution priority: pacFile > pacScript > pacUrl > null (no PAC loaded). * * When all three are unset the function returns `null` synchronously * (well, async-returns null) without booting QuickJS. That keeps the * fast path for existing customers zero-cost. * * Returns a compiled `findProxyForURL(url, host)` function on success. * Throws `PacError` on any startup failure (load, fetch, compile). * * The compiled function is bytecode — each call just executes it, * no re-parsing. See AD-14128 comments for the contract. */ const PAC_URL_TIMEOUT_MS = 10000 async function loadPacResolver({pacFile, pacScript, pacUrl, logger}) { // Source resolution (priority order). `resolveSource` throws a typed // PacError on file-read / URL-fetch failure; we log + re-throw so callers // see the same shape regardless of which phase failed. let sourceSpec try { sourceSpec = await resolveSource({pacFile, pacScript, pacUrl}) } catch (err) { logger?.error({ action: 'pac-load-failed', code: err.code, sourceKind: err.sourceKind, source: err.source, error: err.cause, }) throw err } if (!sourceSpec) return null const {sourceKind, source, content} = sourceSpec const sourceLength = content.length // Boot QuickJS + compile via pac-resolver. Lazy-imported so the all-null // fast path doesn't pay the WASM-init cost. Dynamic `import()` is required // because pac-resolver and quickjs-wasi are ESM-only (`"type": "module"`) // and this package is CommonJS. const compileStart = Date.now() let findProxyForURL try { const {QuickJS} = await import('quickjs-wasi') const {createPacResolver} = await import('pac-resolver') const qjs = await QuickJS.create() findProxyForURL = createPacResolver(qjs, content) } catch (cause) { const err = new PacError({ code: 'EG_TUNNEL_PAC_COMPILE_FAILED', sourceKind, source, cause, }) logger?.error({ action: 'pac-load-failed', code: err.code, sourceKind, source, error: cause, }) throw err } const compileTimeMs = Date.now() - compileStart logger?.info({ action: 'pac-loaded', sourceKind, sourceLength, compileTimeMs, }) return findProxyForURL } async function resolveSource({pacFile, pacScript, pacUrl}) { if (pacFile) { let content try { content = await fs.readFile(pacFile, 'utf8') } catch (cause) { throw new PacError({ code: 'EG_TUNNEL_PAC_FILE_LOAD_FAILED', sourceKind: 'file', source: pacFile, cause, }) } return {sourceKind: 'file', source: pacFile, content: stripBom(content)} } if (pacScript) { return {sourceKind: 'script', source: '<inline>', content: stripBom(pacScript)} } if (pacUrl) { const controller = new AbortController() const timeout = setTimeout(() => controller.abort(), PAC_URL_TIMEOUT_MS) let res try { res = await fetch(pacUrl, {signal: controller.signal}) } catch (cause) { clearTimeout(timeout) throw new PacError({ code: 'EG_TUNNEL_PAC_URL_LOAD_FAILED', sourceKind: 'url', source: pacUrl, cause, }) } clearTimeout(timeout) if (!res.ok) { throw new PacError({ code: 'EG_TUNNEL_PAC_URL_LOAD_FAILED', sourceKind: 'url', source: pacUrl, cause: new Error(`HTTP ${res.status} ${res.statusText}`), }) } const content = await res.text() return {sourceKind: 'url', source: pacUrl, content: stripBom(content)} } return null } /** * Strip a leading UTF-8 BOM (``) if present. PAC files saved by some * editors (notably Windows ones) include a BOM; pac-resolver's parser * choked on it in early tests with at least one customer in the wild. * Safe no-op for sources without a BOM. Runs once per service-lifetime — * cost is negligible. */ function stripBom(s) { return typeof s === 'string' && s.charCodeAt(0) === 0xfeff ? s.slice(1) : s } module.exports = {loadPacResolver}