Created
April 3, 2025 23:27
-
-
Save colinricardo/f7dd78c082fd49ce7484b672e9846e1a to your computer and use it in GitHub Desktop.
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
// src/filler/browserbase.ts | |
import { BROWSER_BASE_API_KEY, BROWSER_BASE_PROJECT_ID } from "@/config"; | |
import { log, logError } from "@/lib/logger"; | |
import Browserbase from "@browserbasehq/sdk"; | |
import { Browser, Page, chromium } from "playwright"; | |
import { BasePlaywrightComputer } from "./browser"; | |
// You'll need to create a type for the Browserbase instance | |
interface BrowserbaseSession { | |
id: string; | |
connectUrl: string; | |
} | |
export class BrowserbaseBrowser extends BasePlaywrightComputer { | |
private bb: any; | |
private projectId: string; | |
private session: BrowserbaseSession | null = null; | |
private _dimensions: [number, number]; | |
private region: string; | |
private proxy: boolean; | |
private virtualMouse: boolean; | |
private adBlocker: boolean; | |
protected _playwright = chromium; | |
constructor(params?: { | |
headless?: boolean; | |
startUrl?: string; | |
jobId?: string; | |
width?: number; | |
height?: number; | |
region?: string; | |
proxy?: boolean; | |
virtualMouse?: boolean; | |
adBlocker?: boolean; | |
}) { | |
super(params); | |
this.headless = params?.headless ?? false; | |
this.startUrl = params?.startUrl ?? "https://google.com"; | |
// Initialize Browserbase-specific options with defaults | |
const width = params?.width ?? 1024; | |
const height = params?.height ?? 768; | |
this._dimensions = [width, height]; | |
this.region = params?.region ?? "us-west-2"; | |
this.proxy = params?.proxy ?? false; | |
this.virtualMouse = params?.virtualMouse ?? true; | |
this.adBlocker = params?.adBlocker ?? false; | |
// Initialize Browserbase client - no fallback | |
this.bb = new Browserbase({ apiKey: BROWSER_BASE_API_KEY }); | |
this.projectId = BROWSER_BASE_PROJECT_ID; | |
} | |
// Override dimensions getter from parent class | |
get dimensions(): [number, number] { | |
return this._dimensions; | |
} | |
protected async _getBrowserAndPage(): Promise<[Browser, Page]> { | |
// Create a session on Browserbase with specified parameters | |
const [width, height] = this._dimensions; | |
const sessionParams = { | |
projectId: this.projectId, | |
browserSettings: { | |
viewport: { width, height }, | |
blockAds: this.adBlocker, | |
}, | |
region: this.region, | |
proxies: this.proxy, | |
}; | |
try { | |
this.session = await this.bb.sessions.create(sessionParams); | |
// Print the live session URL | |
log("browserbase session created", { | |
sessionId: this.session?.id, | |
liveUrl: `https://www.browserbase.com/sessions/${this.session?.id}`, | |
}); | |
// Connect to the remote session | |
const browser = await this._playwright.connectOverCDP( | |
this.session!.connectUrl, | |
{ timeout: 60000 } | |
); | |
const context = browser.contexts()[0]; | |
// Add event listeners for page creation and closure | |
context.on("page", this._handleNewPage.bind(this)); | |
// Only add the init script if virtualMouse is true | |
if (this.virtualMouse) { | |
await context.addInitScript(` | |
// only run in the top frame | |
if (window.self === window.top) { | |
function initCursor() { | |
const CURSOR_ID = '__cursor__'; | |
// check if cursor element already exists | |
if (document.getElementById(CURSOR_ID)) return; | |
const cursor = document.createElement('div'); | |
cursor.id = CURSOR_ID; | |
Object.assign(cursor.style, { | |
position: 'fixed', | |
top: '0px', | |
left: '0px', | |
width: '20px', | |
height: '20px', | |
backgroundImage: 'url("data:image/svg+xml;utf8,<svg xmlns=\\'http://www.w3.org/2000/svg\\' viewBox=\\'0 0 24 24\\' fill=\\'black\\' stroke=\\'white\\' stroke-width=\\'1\\' stroke-linejoin=\\'round\\' stroke-linecap=\\'round\\'><polygon points=\\'2,2 2,22 8,16 14,22 17,19 11,13 20,13\\'/></svg>")', | |
backgroundSize: 'cover', | |
pointerEvents: 'none', | |
zIndex: '99999', | |
transform: 'translate(-2px, -2px)', | |
}); | |
document.body.appendChild(cursor); | |
document.addEventListener("mousemove", (e) => { | |
cursor.style.top = e.clientY + "px"; | |
cursor.style.left = e.clientX + "px"; | |
}); | |
} | |
// use requestAnimationFrame for early execution | |
requestAnimationFrame(function checkBody() { | |
if (document.body) { | |
initCursor(); | |
} else { | |
requestAnimationFrame(checkBody); | |
} | |
}); | |
} | |
`); | |
} | |
const page = context.pages()[0]; | |
page.on("close", this._handlePageClose.bind(this)); | |
// Navigate to the initial URL | |
await page.goto(this.startUrl); | |
return [browser, page]; | |
} catch (error) { | |
logError("failed to create browserbase session", error, { | |
jobId: this.jobId, | |
}); | |
throw error; | |
} | |
} | |
private _handleNewPage(page: Page): void { | |
log("new page created"); | |
this._page = page; | |
page.on("close", this._handlePageClose.bind(this)); | |
} | |
private _handlePageClose(page: Page): void { | |
log("page closed"); | |
if (this._page === page) { | |
if (this._browser && this._browser.contexts().length > 0) { | |
const context = this._browser.contexts()[0]; | |
const pages = context.pages(); | |
if (pages.length > 0) { | |
this._page = pages[pages.length - 1]; | |
} else { | |
log("warning: all pages have been closed"); | |
this._page = null; | |
} | |
} else { | |
log("warning: all pages have been closed"); | |
this._page = null; | |
} | |
} | |
} | |
public async close(): Promise<void> { | |
if (this._page) { | |
await this._page | |
.close() | |
.catch((err: Error) => logError("error closing page", err)); | |
} | |
if (this._browser) { | |
await this._browser | |
.close() | |
.catch((err: Error) => logError("error closing browser", err)); | |
} | |
if (this.session) { | |
log("session completed", { | |
sessionId: this.session.id, | |
replayUrl: `https://browserbase.com/sessions/${this.session.id}`, | |
}); | |
} | |
} | |
public async screenshot(): Promise<Buffer | null> { | |
if (!this._page) return null; | |
try { | |
// Try to get CDP session from the page | |
const cdpSession = await this._page.context().newCDPSession(this._page); | |
// Capture screenshot using CDP | |
const result = await cdpSession.send("Page.captureScreenshot", { | |
format: "png", | |
fromSurface: true, | |
}); | |
// Convert base64 to buffer | |
return Buffer.from(result.data, "base64"); | |
} catch (error) { | |
logError( | |
"CDP screenshot failed, falling back to standard screenshot", | |
error | |
); | |
return super.screenshot(); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment