Skip to content

Instantly share code, notes, and snippets.

@colinricardo
Created April 3, 2025 23:27
Show Gist options
  • Save colinricardo/f7dd78c082fd49ce7484b672e9846e1a to your computer and use it in GitHub Desktop.
Save colinricardo/f7dd78c082fd49ce7484b672e9846e1a to your computer and use it in GitHub Desktop.
// 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