Spaces:
Sleeping
Sleeping
File size: 2,614 Bytes
9e212e3 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 | import { chromium, Browser, BrowserContext } from 'playwright';
import { config } from '../config';
import { logger } from '../utils/logger';
let browser: Browser | null = null;
let browserLaunchPromise: Promise<Browser> | null = null;
/**
* Get or launch the shared browser instance.
* Multiple callers waiting concurrently will share the same launch promise.
*/
export async function getBrowser(): Promise<Browser> {
if (browser && browser.isConnected()) {
return browser;
}
if (browserLaunchPromise) {
return browserLaunchPromise;
}
browserLaunchPromise = launchBrowser();
try {
browser = await browserLaunchPromise;
return browser;
} finally {
browserLaunchPromise = null;
}
}
async function launchBrowser(): Promise<Browser> {
logger.info('Launching Chromium browser', { headless: config.headless });
const b = await chromium.launch({
headless: config.headless,
args: [
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
'--disable-gpu',
],
});
b.on('disconnected', () => {
logger.warn('Browser disconnected');
browser = null;
});
logger.info('Browser launched successfully');
return b;
}
/**
* Create a new isolated browser context for a job.
* Optionally restore storage state from a previous session.
*/
export async function createContext(
storageState?: string
): Promise<BrowserContext> {
const b = await getBrowser();
const contextOptions: Parameters<Browser['newContext']>[0] = {
viewport: { width: 1280, height: 720 },
userAgent:
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36',
acceptDownloads: true,
};
if (storageState) {
try {
contextOptions.storageState = JSON.parse(storageState);
} catch {
logger.warn('Failed to parse storage state, starting fresh');
}
}
const context = await b.newContext(contextOptions);
// Set reasonable timeouts
context.setDefaultTimeout(30000);
context.setDefaultNavigationTimeout(60000);
return context;
}
/**
* Gracefully close the shared browser.
*/
export async function closeBrowser(): Promise<void> {
if (browser) {
try {
await browser.close();
} catch {
// Ignore close errors
}
browser = null;
logger.info('Browser closed');
}
}
/**
* Get count of active contexts (approximation of active jobs).
*/
export async function getActiveContextCount(): Promise<number> {
if (!browser || !browser.isConnected()) return 0;
return browser.contexts().length;
}
|