using System; using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using FinlyticCore.Models.Settings; using FinlyticCore.Services; using Microsoft.Playwright; namespace FinlyticCore.Services.PlaywrightScrapper; public interface IPlaywrightBrowserFactory : IAsyncDisposable, IDisposable { /// /// Stellt sicher, dass die IBrowser-Instanz verbunden ist. /// Task GetBrowserAsync(CancellationToken cancellationToken = default); /// /// Erstellt einen isolierten, vorkonfigurierten Browser-Kontext. /// Task CreateContextAsync(BrowserNewContextOptions? options = null, CancellationToken cancellationToken = default); } public class PlaywrightBrowserFactory : IPlaywrightBrowserFactory { private readonly IFinlyticLogger _finlyticLogger; private readonly SemaphoreSlim _browserLock = new(1, 1); private IPlaywright? _playwright; private IBrowser? _browser; public PlaywrightBrowserFactory(IFinlyticLogger finlyticLogger) { _finlyticLogger = finlyticLogger; } public async Task GetBrowserAsync(CancellationToken cancellationToken = default) { if (_browser != null && _browser.IsConnected) return _browser; await _browserLock.WaitAsync(cancellationToken); try { if (_browser != null && _browser.IsConnected) return _browser; _playwright ??= await Playwright.CreateAsync(); _browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = true, Args = new[] { "--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu" } }); await _finlyticLogger.LogInfoAsync(CoreSettingKeys.PlaywrightChannel, "[PlaywrightFactory] Shared Chromium Instance successfully launched."); return _browser; } finally { _browserLock.Release(); } } public async Task CreateContextAsync(BrowserNewContextOptions? options = null, CancellationToken cancellationToken = default) { var browser = await GetBrowserAsync(cancellationToken); options ??= GetDefaultContextOptions(); return await browser.NewContextAsync(options); } public static BrowserNewContextOptions GetDefaultContextOptions() => new() { UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36", ViewportSize = new ViewportSize { Width = 1280, Height = 900 }, Locale = "en-US", ExtraHTTPHeaders = new Dictionary { ["Accept-Language"] = "en-US,en;q=0.9" } }; public void Dispose() { try { if (_browser != null) { _browser.CloseAsync().GetAwaiter().GetResult(); _browser.DisposeAsync().GetAwaiter().GetResult(); } } catch { // Ignore any sync disposal timeouts } finally { _playwright?.Dispose(); _browserLock.Dispose(); GC.SuppressFinalize(this); } } public async ValueTask DisposeAsync() { if (_browser != null) { try { await _browser.CloseAsync(); await _browser.DisposeAsync(); } catch { // Ignore disposal errors } } _playwright?.Dispose(); _browserLock.Dispose(); GC.SuppressFinalize(this); } }