{"version":3,"file":"common/browser-agent.mjs","sources":["../../../src/common/browser-agent.ts"],"sourcesContent":["import { Agent as CoreAgent } from '@midscene/core/agent';\nimport type { AbstractInterface } from '@midscene/core/device';\nimport type { DebugFunction } from '@midscene/shared/logger';\nimport { isRetryableBrowserNavigationError } from './browser-agent-utils';\n\nexport type BrowserAgentPageScope = 'page' | 'browser';\n\nexport type BrowserAgentAdapter<Page, NewPageEvent> = {\n  pages(): Page[] | Promise<Page[]>;\n  newPage(): Promise<Page>;\n  isPageClosed(page: Page): boolean;\n  isPageAllowed?(page: Page): boolean;\n  bringToFront(page: Page): Promise<void> | void;\n  onNewPage(handler: (event: NewPageEvent) => void): void;\n  offNewPage(handler: (event: NewPageEvent) => void): void;\n  resolveNewPage(event: NewPageEvent): Page | Promise<Page | null> | null;\n  isNewPageEvent?: (event: NewPageEvent) => boolean;\n};\n\nexport type BrowserPageManagerOptions<Page, NewPageEvent> = {\n  agentName: string;\n  adapter: BrowserAgentAdapter<Page, NewPageEvent>;\n  getActivePage(): Page;\n  setActivePageValue(page: Page): void;\n  autoFollowNewPage: boolean;\n  newPageTimeout: number;\n  debug: DebugFunction;\n};\n\nexport type BrowserAgentRuntimeOptions = {\n  agentName: string;\n  pageScope: BrowserAgentPageScope;\n  forceSameTabNavigation?: boolean;\n  autoFollowNewPage?: boolean;\n  newPageTimeout?: number;\n};\n\nexport type ResolvedBrowserAgentRuntimeOptions = {\n  pageScope: BrowserAgentPageScope;\n  forceSameTabNavigation: boolean;\n  autoFollowNewPage: boolean;\n  newPageTimeout: number;\n};\n\nexport abstract class WebAgentCore<\n  InterfaceType extends AbstractInterface,\n> extends CoreAgent<InterfaceType> {\n  protected isRetryableContextError(error: unknown): boolean {\n    return isRetryableBrowserNavigationError(error);\n  }\n}\n\nconst DEFAULT_NEW_PAGE_TIMEOUT = 5000;\n\nexport function resolveBrowserAgentRuntimeOptions({\n  agentName,\n  pageScope,\n  forceSameTabNavigation,\n  autoFollowNewPage,\n  newPageTimeout = DEFAULT_NEW_PAGE_TIMEOUT,\n}: BrowserAgentRuntimeOptions): ResolvedBrowserAgentRuntimeOptions {\n  if (pageScope === 'page') {\n    if (autoFollowNewPage) {\n      throw new Error(\n        `[midscene] autoFollowNewPage requires browser mode for ${agentName}. Use BrowserAgent when one agent should follow newly opened pages.`,\n      );\n    }\n\n    return {\n      pageScope,\n      forceSameTabNavigation: forceSameTabNavigation ?? true,\n      autoFollowNewPage: false,\n      newPageTimeout,\n    };\n  }\n\n  if (typeof forceSameTabNavigation !== 'undefined') {\n    throw new Error(\n      `[midscene] forceSameTabNavigation cannot be used in browser mode for ${agentName}. Use PageAgent when same-tab navigation is required.`,\n    );\n  }\n\n  return {\n    pageScope,\n    forceSameTabNavigation: false,\n    autoFollowNewPage: autoFollowNewPage ?? false,\n    newPageTimeout,\n  };\n}\n\nexport class BrowserPageManager<Page, NewPageEvent> {\n  private readonly agentName: string;\n  private readonly adapter: BrowserAgentAdapter<Page, NewPageEvent>;\n  private readonly getActivePageValue: () => Page;\n  private readonly setActivePageValue: (page: Page) => void;\n  private readonly newPageTimeout: number;\n  private readonly debug: DebugFunction;\n\n  private readonly newPageHandler = (event: NewPageEvent) => {\n    void this.followNewPage(event);\n  };\n\n  constructor(options: BrowserPageManagerOptions<Page, NewPageEvent>) {\n    this.agentName = options.agentName;\n    this.adapter = options.adapter;\n    this.getActivePageValue = options.getActivePage;\n    this.setActivePageValue = options.setActivePageValue;\n    this.newPageTimeout = options.newPageTimeout;\n    this.debug = options.debug;\n\n    if (options.autoFollowNewPage) {\n      this.adapter.onNewPage(this.newPageHandler);\n    }\n  }\n\n  get activePage() {\n    return this.getActivePageValue();\n  }\n\n  pages() {\n    return this.adapter.pages();\n  }\n\n  async newPage() {\n    const page = await this.adapter.newPage();\n    await this.setActivePage(page);\n    return page;\n  }\n\n  async setActivePage(page: Page) {\n    if (!page || this.adapter.isPageClosed(page)) {\n      throw new Error(\n        `[midscene] Cannot set ${this.agentName} active page to a closed or invalid page.`,\n      );\n    }\n    if (this.adapter.isPageAllowed?.(page) === false) {\n      throw new Error(\n        `[midscene] Cannot set ${this.agentName} active page to an out-of-scope page.`,\n      );\n    }\n\n    this.setActivePageValue(page);\n    try {\n      await this.adapter.bringToFront(page);\n    } catch (error) {\n      this.debug(`failed to bring page to front: ${error}`);\n    }\n  }\n\n  async waitForNewPage(\n    action?: () => Promise<unknown> | unknown,\n    opts?: { timeout?: number },\n  ) {\n    const waiter = this.createNewPageWaiter(opts?.timeout);\n\n    try {\n      await action?.();\n      return await waiter.promise;\n    } catch (error) {\n      waiter.dispose();\n      throw error;\n    }\n  }\n\n  destroy() {\n    this.adapter.offNewPage(this.newPageHandler);\n  }\n\n  private async followNewPage(event: NewPageEvent) {\n    if (!this.isNewPageEvent(event)) {\n      return;\n    }\n\n    try {\n      const page = await this.adapter.resolveNewPage(event);\n      if (page) {\n        await this.setActivePage(page);\n      }\n    } catch (error) {\n      this.debug(`failed to follow new page: ${error}`);\n    }\n  }\n\n  private isNewPageEvent(event: NewPageEvent) {\n    return this.adapter.isNewPageEvent?.(event) ?? true;\n  }\n\n  private createNewPageWaiter(timeout = this.newPageTimeout) {\n    let settled = false;\n\n    const dispose = () => {\n      this.adapter.offNewPage(handler);\n      clearTimeout(timer);\n    };\n\n    const handler = async (event: NewPageEvent) => {\n      if (settled || !this.isNewPageEvent(event)) {\n        return;\n      }\n\n      settled = true;\n      dispose();\n\n      try {\n        const page = await this.adapter.resolveNewPage(event);\n        if (!page) {\n          throw new Error('new target did not resolve to a page');\n        }\n        resolvePage(page);\n      } catch (error) {\n        rejectPage(error);\n      }\n    };\n\n    let resolvePage!: (page: Page) => void;\n    let rejectPage!: (error: unknown) => void;\n    const promise = new Promise<Page>((resolve, reject) => {\n      resolvePage = resolve;\n      rejectPage = reject;\n    });\n\n    const timer = setTimeout(() => {\n      if (settled) {\n        return;\n      }\n      settled = true;\n      dispose();\n      rejectPage(\n        new Error(\n          `[midscene] Timed out waiting for a new ${this.agentName} page after ${timeout}ms.`,\n        ),\n      );\n    }, timeout);\n\n    this.adapter.onNewPage(handler);\n\n    return { promise, dispose };\n  }\n}\n"],"names":["WebAgentCore","CoreAgent","error","isRetryableBrowserNavigationError","DEFAULT_NEW_PAGE_TIMEOUT","resolveBrowserAgentRuntimeOptions","agentName","pageScope","forceSameTabNavigation","autoFollowNewPage","newPageTimeout","Error","BrowserPageManager","page","action","opts","waiter","event","timeout","settled","dispose","handler","clearTimeout","timer","resolvePage","rejectPage","promise","Promise","resolve","reject","setTimeout","options"],"mappings":"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"}