-
Notifications
You must be signed in to change notification settings - Fork 1.1k
fix: handle context destroyed and frame navigation #243
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,8 @@ export class RemoteBrowser { | |
| maxRepeats: 1, | ||
| }; | ||
|
|
||
| private lastEmittedUrl: string | null = null; | ||
|
|
||
| /** | ||
| * {@link WorkflowGenerator} instance specific to the remote browser. | ||
| */ | ||
|
|
@@ -88,6 +90,64 @@ export class RemoteBrowser { | |
| this.generator = new WorkflowGenerator(socket); | ||
| } | ||
|
|
||
| /** | ||
| * Normalizes URLs to prevent navigation loops while maintaining consistent format | ||
| */ | ||
| private normalizeUrl(url: string): string { | ||
| try { | ||
| const parsedUrl = new URL(url); | ||
| // Remove trailing slashes except for root path | ||
| parsedUrl.pathname = parsedUrl.pathname.replace(/\/+$/, '') || '/'; | ||
| // Ensure consistent protocol handling | ||
| parsedUrl.protocol = parsedUrl.protocol.toLowerCase(); | ||
| return parsedUrl.toString(); | ||
| } catch { | ||
| return url; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Determines if a URL change is significant enough to emit | ||
| */ | ||
| private shouldEmitUrlChange(newUrl: string): boolean { | ||
| if (!this.lastEmittedUrl) { | ||
| return true; | ||
| } | ||
| const normalizedNew = this.normalizeUrl(newUrl); | ||
| const normalizedLast = this.normalizeUrl(this.lastEmittedUrl); | ||
| return normalizedNew !== normalizedLast; | ||
| } | ||
|
|
||
| private async setupPageEventListeners(page: Page) { | ||
| page.on('framenavigated', async (frame) => { | ||
| if (frame === page.mainFrame()) { | ||
| const currentUrl = page.url(); | ||
| if (this.shouldEmitUrlChange(currentUrl)) { | ||
| this.lastEmittedUrl = currentUrl; | ||
| this.socket.emit('urlChanged', currentUrl); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| // Handle page load events with retry mechanism | ||
| page.on('load', async () => { | ||
| const injectScript = async (): Promise<boolean> => { | ||
| try { | ||
| await page.waitForLoadState('networkidle', { timeout: 5000 }); | ||
|
|
||
| await page.evaluate(getInjectableScript()); | ||
| return true; | ||
| } catch (error: any) { | ||
| logger.log('warn', `Script injection attempt failed: ${error.message}`); | ||
| return false; | ||
| } | ||
| }; | ||
|
|
||
| const success = await injectScript(); | ||
| console.log("Script injection result:", success); | ||
| }); | ||
| } | ||
|
|
||
|
Comment on lines
+121
to
+150
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Prevent Duplicate Event Listeners on Pages The Consider removing existing event listeners before adding new ones or checking if listeners already exist. Apply this diff to prevent duplicate listeners: + // Remove existing listeners to prevent duplication
+ page.removeListener('framenavigated', this.framenavigatedHandler);
+ page.removeListener('load', this.loadHandler);
+ // Define handlers as class properties to maintain references
+ private framenavigatedHandler = async (frame: Frame) => { /* existing code */ };
+ private loadHandler = async () => { /* existing code */ };
page.on('framenavigated', this.framenavigatedHandler);
page.on('load', this.loadHandler);Alternatively, ensure
|
||
| /** | ||
| * An asynchronous constructor for asynchronously initialized properties. | ||
| * Must be called right after creating an instance of RemoteBrowser class. | ||
|
|
@@ -167,15 +227,7 @@ export class RemoteBrowser { | |
| this.context = await this.browser.newContext(contextOptions); | ||
| this.currentPage = await this.context.newPage(); | ||
|
|
||
| this.currentPage.on('framenavigated', (frame) => { | ||
| if (frame === this.currentPage?.mainFrame()) { | ||
| this.socket.emit('urlChanged', this.currentPage.url()); | ||
| } | ||
| }); | ||
|
|
||
| this.currentPage.on('load', (page) => { | ||
| page.evaluate(getInjectableScript()) | ||
| }) | ||
| await this.setupPageEventListeners(this.currentPage); | ||
|
|
||
| // await this.currentPage.setExtraHTTPHeaders({ | ||
| // 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' | ||
|
|
@@ -375,15 +427,7 @@ export class RemoteBrowser { | |
| await this.stopScreencast(); | ||
| this.currentPage = page; | ||
|
|
||
| this.currentPage.on('framenavigated', (frame) => { | ||
| if (frame === this.currentPage?.mainFrame()) { | ||
| this.socket.emit('urlChanged', this.currentPage.url()); | ||
| } | ||
| }); | ||
|
|
||
| this.currentPage.on('load', (page) => { | ||
| page.evaluate(getInjectableScript()) | ||
| }) | ||
| await this.setupPageEventListeners(this.currentPage); | ||
|
|
||
| //await this.currentPage.setViewportSize({ height: 400, width: 900 }) | ||
| this.client = await this.currentPage.context().newCDPSession(this.currentPage); | ||
|
|
@@ -411,18 +455,8 @@ export class RemoteBrowser { | |
| await this.currentPage?.close(); | ||
| this.currentPage = newPage; | ||
| if (this.currentPage) { | ||
| this.currentPage.on('framenavigated', (frame) => { | ||
| if (frame === this.currentPage?.mainFrame()) { | ||
| this.socket.emit('urlChanged', this.currentPage.url()); | ||
| } | ||
| }); | ||
|
|
||
| this.currentPage.on('load', (page) => { | ||
| page.evaluate(getInjectableScript()) | ||
| }) | ||
| // this.currentPage.on('load', (page) => { | ||
| // this.socket.emit('urlChanged', page.url()); | ||
| // }) | ||
| await this.setupPageEventListeners(this.currentPage); | ||
|
|
||
| this.client = await this.currentPage.context().newCDPSession(this.currentPage); | ||
| await this.subscribeToScreencast(); | ||
| } else { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance Type Safety in
executeActionFunctionThe
executeActionfunction usesanytypes forinvokee,methodName, andargs, which can lead to runtime errors due to lack of type checking. Casting with<any>may hide potential issues if the method does not exist or is incorrectly invoked.Consider specifying more precise types or using generics to improve type safety.
Apply this diff to enhance type safety:
📝 Committable suggestion