Skip to main content
Glama
stable-browser-manager.ts•6.61 kB
/** * Stable Browser Manager * Handles browser automation with crash prevention and recovery */ import playwright, { Browser, Page, BrowserContext } from 'playwright'; import { readFileSync } from 'fs'; import { resolve, dirname } from 'path'; import { fileURLToPath } from 'url'; const currentDir = dirname(fileURLToPath(import.meta.url)); export interface BrowserSession { browser: Browser; context: BrowserContext; page: Page; } export class StableBrowserManager { private jwtToken!: string; private baseURL = 'https://composer.euconquisto.com/#/embed'; private orgId = '36c92686-c494-ec11-a22a-dc984041c95d'; private maxRetries = 3; private retryDelay = 2000; constructor() { this.loadJWTToken(); } private loadJWTToken() { try { const tokenPath = resolve(currentDir, '..', 'correct-jwt-new.txt'); this.jwtToken = readFileSync(tokenPath, 'utf8').trim(); } catch (error) { throw new Error('Failed to load JWT token: ' + error); } } async createSession(options: { headless?: boolean; timeout?: number; slowMo?: number; } = {}): Promise<BrowserSession> { const { headless = false, timeout = 120000, slowMo = 100, } = options; let attempt = 0; while (attempt < this.maxRetries) { try { const browser = await playwright.chromium.launch({ headless, timeout, slowMo, args: [ '--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-web-security', '--disable-blink-features=AutomationControlled', '--disable-extensions', '--no-first-run', '--no-default-browser-check', ], }); const context = await browser.newContext({ viewport: { width: 1366, height: 768 }, userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', extraHTTPHeaders: { 'Accept-Language': 'pt-BR,pt;q=0.9,en;q=0.8', }, }); const page = await context.newPage(); // Set up crash detection page.on('crash', () => { console.log('🚨 Page crashed, attempting recovery...'); }); browser.on('disconnected', () => { console.log('🚨 Browser disconnected'); }); // Navigate to Composer const embedURL = `${this.baseURL}/auth-with-token/pt_br/home/${this.orgId}/${this.jwtToken}`; await page.goto(embedURL, { waitUntil: 'networkidle', timeout: timeout, }); // Wait for page to be ready await this.waitForPageReady(page); return { browser, context, page }; } catch (error) { attempt++; console.log(`šŸ”„ Browser launch attempt ${attempt} failed:`, (error as Error).message); if (attempt < this.maxRetries) { console.log(`ā³ Retrying in ${this.retryDelay}ms...`); await this.sleep(this.retryDelay); } else { throw new Error(`Failed to launch browser after ${this.maxRetries} attempts: ${(error as Error).message}`); } } } throw new Error('Unexpected error in browser launch'); } async safeExecute<T>( session: BrowserSession, operation: (page: Page) => Promise<T>, operationName: string ): Promise<T> { let attempt = 0; while (attempt < this.maxRetries) { try { // Check if page is still active if (session.page.isClosed()) { throw new Error('Page is closed'); } return await operation(session.page); } catch (error) { attempt++; console.log(`šŸ”„ ${operationName} attempt ${attempt} failed:`, (error as Error).message); if (attempt < this.maxRetries) { console.log(`ā³ Retrying ${operationName} in ${this.retryDelay}ms...`); await this.sleep(this.retryDelay); // Try to recover the page if (session.page.isClosed()) { console.log('šŸ”§ Attempting to recover closed page...'); session.page = await session.context.newPage(); const embedURL = `${this.baseURL}/auth-with-token/pt_br/home/${this.orgId}/${this.jwtToken}`; await session.page.goto(embedURL, { waitUntil: 'networkidle' }); await this.waitForPageReady(session.page); } } else { throw new Error(`${operationName} failed after ${this.maxRetries} attempts: ${(error as Error).message}`); } } } throw new Error(`Unexpected error in ${operationName}`); } async waitForPageReady(page: Page): Promise<void> { // Wait for the page to be fully loaded await page.waitForLoadState('networkidle'); // Wait for key elements to be present await page.waitForSelector('body', { timeout: 30000 }); // Wait for any async operations to complete await this.sleep(2000); } async safeClick(page: Page, selector: string): Promise<void> { await page.waitForSelector(selector, { timeout: 30000 }); await page.click(selector); await this.sleep(1000); // Allow UI to update } async safeType(page: Page, selector: string, text: string): Promise<void> { await page.waitForSelector(selector, { timeout: 30000 }); await page.fill(selector, text); await this.sleep(500); } async safeWaitForSelector(page: Page, selector: string, timeout: number = 30000): Promise<void> { await page.waitForSelector(selector, { timeout }); } async cleanupSession(session: BrowserSession): Promise<void> { try { if (session.page && !session.page.isClosed()) { await session.page.close(); } if (session.context) { await session.context.close(); } if (session.browser) { await session.browser.close(); } } catch (error) { console.log('āš ļø Error during session cleanup:', (error as Error).message); } } private sleep(ms: number): Promise<void> { return new Promise(resolve => setTimeout(resolve, ms)); } async withStableSession<T>( operation: (session: BrowserSession) => Promise<T>, options: { headless?: boolean; timeout?: number; slowMo?: number; } = {} ): Promise<T> { const session = await this.createSession(options); try { return await operation(session); } finally { await this.cleanupSession(session); } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/rkm097git/euconquisto-composer-mcp-poc'

If you have feedback or need assistance with the MCP directory API, please join our Discord server