Created
July 18, 2025 21:53
-
-
Save sachinraja/f1125b230680849a76c6d06b4b790591 to your computer and use it in GitHub Desktop.
browser-in-sandbox
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { Sandbox } from "@vercel/sandbox"; | |
import z from 'zod'; | |
import { chromium } from 'playwright' | |
export async function retryWithBackoff<T>(fn: () => Promise<T>, retries = 3) { | |
let delay = 1000; | |
for (let i = 0; i < retries; i++) { | |
try { | |
return await fn(); | |
} catch { | |
await new Promise((resolve) => setTimeout(resolve, delay)); | |
delay *= 2; | |
} | |
} | |
throw new Error("Failed to execute function after retries"); | |
} | |
const cdpVersionSchema = z.object({ | |
webSocketDebuggerUrl: z.string(), | |
}); | |
const BROWSERS_DIR = "browsers"; | |
const PORT = 9222; | |
async function setupSandbox(sandbox: Sandbox) { | |
console.log('Creating browsers directory...'); | |
await sandbox.mkDir(BROWSERS_DIR); | |
const chromeRpmPath = `${BROWSERS_DIR}/google-chrome-stable_current_x86_64.rpm`; | |
console.log("Downloading Chrome RPM..."); | |
const downloadRpm = await sandbox.runCommand({ | |
cmd: "curl", | |
args: [ | |
"https://dl.google.com/linux/direct/google-chrome-stable_current_x86_64.rpm", | |
"--output", | |
chromeRpmPath, | |
], | |
}); | |
if (downloadRpm.exitCode !== 0) { | |
console.error("Failed to download Chrome RPM:", downloadRpm.stderr); | |
throw new Error("Chrome RPM download failed"); | |
} | |
console.log("Installing Chrome...") | |
const installChrome = await sandbox.runCommand({ | |
cmd: "dnf", | |
args: ["install", "-y", chromeRpmPath], | |
sudo: true, | |
}); | |
if (installChrome.exitCode !== 0) { | |
console.error("Failed to install Chrome:", installChrome.stderr); | |
throw new Error("Chrome installation failed"); | |
} | |
console.log("Starting Chrome...") | |
await sandbox.runCommand({ | |
cmd: "google-chrome", | |
args: [ | |
"--headless", | |
"--no-sandbox", | |
`--remote-debugging-port=${PORT}`, | |
"--remote-debugging-address=0.0.0.0", | |
], | |
detached: true, | |
}); | |
console.log("Waiting for Chrome to start..."); | |
// health check until the browser is ready | |
const fetchVersion = await retryWithBackoff(async () => { | |
const result = await sandbox.runCommand({ | |
cmd: "curl", | |
args: [`-s`, `http://localhost:${PORT}/json/version`], | |
}); | |
if (result.exitCode !== 0) { | |
throw new Error("Failed to fetch version"); | |
} | |
return result; | |
}); | |
const versionOutput = await fetchVersion.output(); | |
const data = JSON.parse(versionOutput); | |
const { webSocketDebuggerUrl } = cdpVersionSchema.parse(data); | |
const url = new URL(webSocketDebuggerUrl); | |
const sandboxUrl = new URL(sandbox.domain(PORT)); | |
const externalUrl = `wss://${sandboxUrl.host}${url.pathname}`; | |
console.log("Chrome started successfully. WebSocket URL:", externalUrl); | |
return externalUrl; | |
} | |
export async function createBrowserSandbox() { | |
console.log("Setting up sandbox..."); | |
const sandbox = await Sandbox.create({ | |
timeout: 300000, | |
ports: [PORT], | |
}); | |
try { | |
const webSocketDebuggerUrl = await setupSandbox(sandbox); | |
return { sandbox, webSocketDebuggerUrl }; | |
} catch (e) { | |
await sandbox.stop(); | |
throw e; | |
} | |
} | |
async function main() { | |
const { sandbox, webSocketDebuggerUrl } = await createBrowserSandbox() | |
const browser = await chromium.connectOverCDP(webSocketDebuggerUrl) | |
const page = await browser.newPage() | |
await page.goto("https://nytimes.com") | |
await page.screenshot({ path: "headlines.png" }) | |
await sandbox.stop() | |
} | |
main().catch(console.error); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment