// PosthogTelemetryProvider.cs // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors // https://github.com/gnh1201/welsonjs // using System; using System.Collections.Generic; using System.Net.Http; using System.Text; using System.Threading; using System.Threading.Tasks; namespace WelsonJS.Launcher.Telemetry { public sealed class PosthogTelemetryProvider : ITelemetryProvider, IDisposable { private readonly TelemetryOptions _options; private readonly HttpClient _httpClient; private readonly ICompatibleLogger _logger; private bool _disposed; public PosthogTelemetryProvider(TelemetryOptions options, ICompatibleLogger logger = null) { _options = options ?? throw new ArgumentNullException(nameof(options)); if (!_options.Disabled) { if (string.IsNullOrWhiteSpace(_options.ApiKey)) throw new ArgumentException("PostHog API key is missing."); if (string.IsNullOrWhiteSpace(_options.BaseUrl)) throw new ArgumentException("PostHog BaseUrl is missing."); } if (string.IsNullOrWhiteSpace(_options.DistinctId)) _options.DistinctId = $"anon-{Guid.NewGuid():N}"; _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; _logger = logger; } public async Task TrackEventAsync( string eventName, IDictionary properties = null, CancellationToken cancellationToken = default) { if (_disposed) throw new ObjectDisposedException(nameof(PosthogTelemetryProvider)); if (_options.Disabled) return; if (string.IsNullOrWhiteSpace(eventName)) return; string json; using (var ser = new JsSerializer()) { var payload = new Dictionary { ["api_key"] = _options.ApiKey, ["distinct_id"] = _options.DistinctId, ["event"] = eventName, ["properties"] = properties ?? new Dictionary() }; json = ser.Serialize(payload, 0); } var url = $"{_options.BaseUrl.TrimEnd('/')}/i/v0/e"; try { using (var response = await _httpClient.PostAsync( url, new StringContent(json, Encoding.UTF8, "application/json"), cancellationToken )) { response.EnsureSuccessStatusCode(); } } catch (Exception ex) { // Log and swallow for fire-and-forget telemetry _logger?.Error($"Failed to send telemetry event '{eventName}': {ex.Message}"); } } public void Dispose() { if (_disposed) return; _disposed = true; _httpClient.Dispose(); } } }