using Microsoft.Playwright; namespace PrismaticSync.Infrastructure; /// /// A headless Chromium session with a realistic desktop fingerprint (UA, viewport, locale, /// timezone) — matching the original scraper's settings to look like a normal browser. /// public sealed class BrowserSession : IAsyncDisposable { private IPlaywright? _pw; private IBrowser? _browser; private IBrowserContext? _context; public IPage Page { get; private set; } = null!; public static async Task CreateAsync(bool headed) { var session = new BrowserSession(); session._pw = await Playwright.CreateAsync(); session._browser = await session._pw.Chromium.LaunchAsync(new BrowserTypeLaunchOptions { Headless = !headed }); session._context = await session._browser.NewContextAsync(new BrowserNewContextOptions { UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " + "(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36", ViewportSize = new ViewportSize { Width = 1365, Height = 900 }, Locale = "en-US", TimezoneId = "America/New_York" }); session.Page = await session._context.NewPageAsync(); return session; } public async ValueTask DisposeAsync() { if (_context is not null) await _context.CloseAsync(); if (_browser is not null) await _browser.CloseAsync(); _pw?.Dispose(); } }