Name Message Date
📁 Properties Add web dashboard for viewing monitor status and detections. 5 hours ago
📁 Protos Initialize project 1 month ago
📁 wwwroot Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 .containerfile Containerize 10 days ago
📄 .dockerignore Containerize 10 days ago
📄 .editorconfig Initialize project 1 month ago
📄 .gitignore Remove database file from repository 10 days ago
📄 appsettings.Development.json Fix Claudes mess 11 days ago
📄 appsettings.json Fix Claudes mess 11 days ago
📄 BfiMonitor.csproj Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 BfiMonitor.slnx Initialize project 1 month ago
📄 CheckMonitoringJob.cs Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 dotnet-tools.json Initialize project 1 month ago
📄 global.json Fix Claudes mess 11 days ago
📄 MonitoringCheckScheduler.cs Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 MonitorOptions.cs Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 OpenTelemetryExtensions.cs Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 packages.lock.json Add web dashboard for viewing monitor status and detections. 5 hours ago
📄 PlaywrightBrowserService.cs Use Playwright settings from Lukas 10 days ago
📄 Program.cs Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 ScheduleMonitoringChecksJob.cs Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 ScreeningRepository.cs Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 SendSmsJob.cs Add tracing 10 days ago
📄 Tracing.cs Add multi-monitoring management with scan triggers and editing. 5 hours ago
📄 PlaywrightBrowserService.cs
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Options;
using Microsoft.Playwright;

internal sealed class PlaywrightBrowserService(IOptions<MonitorOptions> options) : IHostedService, IAsyncDisposable
{
    private IPlaywright? playwright;
    private IBrowser? browser;

    public async Task StartAsync(CancellationToken cancellationToken)
    {
        playwright = await Playwright.CreateAsync();
        browser = await playwright.Chromium.LaunchAsync(
            new BrowserTypeLaunchOptions
            {
                Headless = options.Value.Headless,
                Args =
                [
                    "--disable-blink-features=AutomationControlled",
                    "--no-sandbox",
                    "--disable-dev-shm-usage",
                    "--disable-gpu",
                    // "new" headless mode has a much closer fingerprint to headed Chrome
                    // than the legacy headless implementation, helping bypass bot detection.
                    .. options.Value.Headless ? ["--headless=new"] : Array.Empty<string>(),
                ],
            }
        );
    }

    public async Task StopAsync(CancellationToken cancellationToken) => await DisposeAsync();

    public async Task<IPage> NewPageAsync()
    {
        var ctx = await browser!.NewContextAsync(
            new()
            {
                UserAgent =
                    "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36",
                Locale = "en-GB",
                ViewportSize = new() { Width = 1280, Height = 720 },
            }
        );
        var page = await ctx.NewPageAsync();
        // Patch navigator.webdriver before any page script runs so Cloudflare sees undefined.
        await page.AddInitScriptAsync("Object.defineProperty(navigator,'webdriver',{get:()=>undefined})");
        return page;
    }

    public async ValueTask DisposeAsync()
    {
        if (browser is not null)
        {
            await browser.DisposeAsync();
        }
        playwright?.Dispose();
    }
}