import { Page } from 'playwright'; import { getLogger } from '@stock-bot/logger'; import { Browser } from './browser'; import type { ScrapingResult } from './types'; interface TabInfo { page: Page; contextId: string; } export class BrowserTabManager { private tabs: Map = new Map(); private logger = getLogger('browser-tab-manager'); async createTab(url?: string): Promise<{ page: Page; tabId: string }> { const tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const { page, contextId } = await Browser.createPageWithProxy(url || 'about:blank'); this.tabs.set(tabId, { page, contextId }); this.logger.debug('Tab created', { tabId, url }); return { page, tabId }; } async createTabWithProxy( url: string, proxy: string ): Promise<{ page: Page; tabId: string; contextId: string }> { const tabId = `tab-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; const { page, contextId } = await Browser.createPageWithProxy(url, proxy); this.tabs.set(tabId, { page, contextId }); this.logger.debug('Tab with proxy created', { tabId, url, proxy }); return { page, tabId, contextId }; } async scrapeUrlsWithProxies( urlProxyPairs: Array<{ url: string; proxy: string }>, extractor: (page: Page) => Promise, options: { concurrency?: number } = {} ): Promise[]> { const { concurrency = 3 } = options; const results: ScrapingResult[] = []; for (let i = 0; i < urlProxyPairs.length; i += concurrency) { const batch = urlProxyPairs.slice(i, i + concurrency); const batchPromises = batch.map(async ({ url, proxy }) => { let tabId: string | undefined; try { const result = await this.createTabWithProxy(url, proxy); tabId = result.tabId; const data = await extractor(result.page); return { data, url, success: true, } as ScrapingResult; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { data: null as T, url, success: false, error: errorMessage, } as ScrapingResult; } finally { if (tabId) { await this.closeTab(tabId); } } }); const batchResults = await Promise.all(batchPromises); results.push(...batchResults); } return results; } async closeTab(tabId: string): Promise { const tab = this.tabs.get(tabId); if (tab) { await tab.page.close(); await Browser.closeContext(tab.contextId); this.tabs.delete(tabId); this.logger.debug('Tab closed', { tabId }); } } getTabCount(): number { return this.tabs.size; } getAllTabIds(): string[] { return Array.from(this.tabs.keys()); } }