Merge pull request #498 from getmaxun/context-fix
fix: handling unexpected browser context closed error
This commit is contained in:
@@ -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();
|
||||||
|
|||||||
Reference in New Issue
Block a user