Merge pull request #498 from getmaxun/context-fix

fix: handling unexpected browser context closed error
This commit is contained in:
Karishma Shukla
2025-03-27 16:02:24 +05:30
committed by GitHub

View File

@@ -268,105 +268,150 @@ export class RemoteBrowser {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
public initialize = async (userId: string): Promise<void> => { public initialize = async (userId: string): Promise<void> => {
this.browser = <Browser>(await chromium.launch({ const MAX_RETRIES = 3;
headless: true, let retryCount = 0;
args: [ let success = false;
"--disable-blink-features=AutomationControlled",
"--disable-web-security", while (!success && retryCount < MAX_RETRIES) {
"--disable-features=IsolateOrigins,site-per-process", try {
"--disable-site-isolation-trials", this.browser = <Browser>(await chromium.launch({
"--disable-extensions", headless: true,
"--no-sandbox", args: [
"--disable-dev-shm-usage", "--disable-blink-features=AutomationControlled",
"--force-color-profile=srgb", "--disable-web-security",
"--force-device-scale-factor=2", "--disable-features=IsolateOrigins,site-per-process",
], "--disable-site-isolation-trials",
})); "--disable-extensions",
const proxyConfig = await getDecryptedProxyConfig(userId); "--no-sandbox",
let proxyOptions: { server: string, username?: string, password?: string } = { server: '' }; "--disable-dev-shm-usage",
if (proxyConfig.proxy_url) { "--force-color-profile=srgb",
proxyOptions = { "--force-device-scale-factor=2",
server: proxyConfig.proxy_url, ],
...(proxyConfig.proxy_username && proxyConfig.proxy_password && { }));
username: proxyConfig.proxy_username,
password: proxyConfig.proxy_password, if (!this.browser || this.browser.isConnected() === false) {
}), throw new Error('Browser failed to launch or is not connected');
}; }
}
const contextOptions: any = { const proxyConfig = await getDecryptedProxyConfig(userId);
// viewport: { height: 400, width: 900 }, let proxyOptions: { server: string, username?: string, password?: string } = { server: '' };
// recordVideo: { dir: 'videos/' }
// Force reduced motion to prevent animation issues if (proxyConfig.proxy_url) {
reducedMotion: 'reduce', proxyOptions = {
// Force JavaScript to be enabled server: proxyConfig.proxy_url,
javaScriptEnabled: true, ...(proxyConfig.proxy_username && proxyConfig.proxy_password && {
// Set a reasonable timeout username: proxyConfig.proxy_username,
timeout: 50000, password: proxyConfig.proxy_password,
// Disable hardware acceleration }),
forcedColors: 'none', };
isMobile: false, }
hasTouch: false,
userAgent: this.getUserAgent(), const contextOptions: any = {
deviceScaleFactor: 2, // viewport: { height: 400, width: 900 },
}; // recordVideo: { dir: 'videos/' }
// Force reduced motion to prevent animation issues
if (proxyOptions.server) { reducedMotion: 'reduce',
contextOptions.proxy = { // Force JavaScript to be enabled
server: proxyOptions.server, javaScriptEnabled: true,
username: proxyOptions.username ? proxyOptions.username : undefined, // Set a reasonable timeout
password: proxyOptions.password ? proxyOptions.password : undefined, timeout: 50000,
}; // Disable hardware acceleration
} forcedColors: 'none',
isMobile: false,
this.context = await this.browser.newContext(contextOptions); hasTouch: false,
await this.context.addInitScript( userAgent: this.getUserAgent(),
`const defaultGetter = Object.getOwnPropertyDescriptor( deviceScaleFactor: 2,
Navigator.prototype, };
"webdriver"
).get; if (proxyOptions.server) {
defaultGetter.apply(navigator); contextOptions.proxy = {
defaultGetter.toString(); server: proxyOptions.server,
Object.defineProperty(Navigator.prototype, "webdriver", { username: proxyOptions.username ? proxyOptions.username : undefined,
set: undefined, password: proxyOptions.password ? proxyOptions.password : undefined,
enumerable: true, };
configurable: true, }
get: new Proxy(defaultGetter, {
apply: (target, thisArg, args) => { await new Promise(resolve => setTimeout(resolve, 500));
Reflect.apply(target, thisArg, args);
return false; const contextPromise = this.browser.newContext(contextOptions);
}, this.context = await Promise.race([
}), contextPromise,
}); new Promise<never>((_, reject) => {
const patchedGetter = Object.getOwnPropertyDescriptor( setTimeout(() => reject(new Error('Context creation timed out after 15s')), 15000);
Navigator.prototype, })
"webdriver" ]) as BrowserContext;
).get;
patchedGetter.apply(navigator); await this.context.addInitScript(
patchedGetter.toString();` `const defaultGetter = Object.getOwnPropertyDescriptor(
); Navigator.prototype,
this.currentPage = await this.context.newPage(); "webdriver"
).get;
await this.setupPageEventListeners(this.currentPage); defaultGetter.apply(navigator);
defaultGetter.toString();
const viewportSize = await this.currentPage.viewportSize(); Object.defineProperty(Navigator.prototype, "webdriver", {
if (viewportSize) { set: undefined,
this.socket.emit('viewportInfo', { enumerable: true,
width: viewportSize.width, configurable: true,
height: viewportSize.height, get: new Proxy(defaultGetter, {
userId: this.userId apply: (target, thisArg, args) => {
}); Reflect.apply(target, thisArg, args);
} return false;
},
try { }),
const blocker = await PlaywrightBlocker.fromLists(fetch, ['https://easylist.to/easylist/easylist.txt']); });
await blocker.enableBlockingInPage(this.currentPage); const patchedGetter = Object.getOwnPropertyDescriptor(
this.client = await this.currentPage.context().newCDPSession(this.currentPage); Navigator.prototype,
await blocker.disableBlockingInPage(this.currentPage); "webdriver"
console.log('Adblocker initialized'); ).get;
} catch (error: any) { patchedGetter.apply(navigator);
console.warn('Failed to initialize adblocker, continuing without it:', error.message); patchedGetter.toString();`
// Still need to set up the CDP session even if blocker fails );
this.client = await this.currentPage.context().newCDPSession(this.currentPage);
this.currentPage = await this.context.newPage();
await this.setupPageEventListeners(this.currentPage);
const viewportSize = await this.currentPage.viewportSize();
if (viewportSize) {
this.socket.emit('viewportInfo', {
width: viewportSize.width,
height: viewportSize.height,
userId: this.userId
});
}
try {
const blocker = await PlaywrightBlocker.fromLists(fetch, ['https://easylist.to/easylist/easylist.txt']);
await blocker.enableBlockingInPage(this.currentPage);
this.client = await this.currentPage.context().newCDPSession(this.currentPage);
await blocker.disableBlockingInPage(this.currentPage);
console.log('Adblocker initialized');
} catch (error: any) {
console.warn('Failed to initialize adblocker, continuing without it:', error.message);
// Still need to set up the CDP session even if blocker fails
this.client = await this.currentPage.context().newCDPSession(this.currentPage);
}
success = true;
logger.log('debug', `Browser initialized successfully for user ${userId}`);
} catch (error: any) {
retryCount++;
logger.log('error', `Browser initialization failed (attempt ${retryCount}/${MAX_RETRIES}): ${error.message}`);
if (this.browser) {
try {
await this.browser.close();
} catch (closeError) {
logger.log('warn', `Failed to close browser during cleanup: ${closeError}`);
}
this.browser = null;
}
if (retryCount >= MAX_RETRIES) {
throw new Error(`Failed to initialize browser after ${MAX_RETRIES} attempts: ${error.message}`);
}
await new Promise(resolve => setTimeout(resolve, 1000));
}
} }
this.initializeMemoryManagement(); this.initializeMemoryManagement();