using Microsoft.Extensions.Logging; using System.Collections.Generic; using System.IO; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Reflection; using System.Threading.Tasks; namespace Aptabase.WPF; internal class AptabaseClientBase : IAsyncDisposable { protected static readonly TimeSpan SESSION_TIMEOUT = TimeSpan.FromMinutes(60); private static readonly Random _random = new(); private readonly ILogger? _logger; private readonly HttpClient? _http; private static readonly HttpClient _ipClient = new(); private DateTime _lastTouched = DateTime.UtcNow; private string _sessionId = NewSessionId(); private static readonly SystemInfo _sysInfo = new(); private static readonly Dictionary _hosts = new() { { "US", "https://us.aptabase.com" }, { "EU", "https://eu.aptabase.com" }, { "DEV", "https://localhost:3000" }, { "SH", "" }, }; public AptabaseClientBase(string appKey, AptabaseOptions? options, ILogger? logger) { _logger = logger; var parts = appKey.Split("-"); if (parts.Length != 3 || !_hosts.ContainsKey(parts[1])) { _logger?.LogWarning("The Aptabase App Key {AppKey} is invalid. Tracking will be disabled.", appKey); return; } var region = parts[1]; var baseUrl = GetBaseUrl(parts[1], options); if (baseUrl is null) { return; } _sysInfo.IsDebug = options?.IsDebugMode ?? SystemInfo.IsInDebugMode(Assembly.GetExecutingAssembly()); _http = region == "DEV" ? new(new LocalHttpsClientHandler()) : new(); _http.BaseAddress = new Uri(baseUrl); _http.DefaultRequestHeaders.Add("App-Key", appKey); } internal async Task TrackEvent(EventData eventData) { if (_http is null) { return; } RefreshSession(); eventData.SessionId = _sessionId; eventData.SystemProps = _sysInfo; if (eventData.Props is null) eventData.Props = new Dictionary(); //eventData.Props["ipAddress"] = await GetPublicIpAddress(); 느려서 제외. 필요하면 다른 방법 사용 요망 var body = JsonContent.Create(eventData); var response = await _http.PostAsync("/api/v0/event", body); //var logPath = "aptabase-debug.log"; //파일 관리 안되므로 제거. 필요하면 다른 방법 사용 요망 if (!response.IsSuccessStatusCode) { if (response.StatusCode >= HttpStatusCode.InternalServerError || response.StatusCode == HttpStatusCode.RequestTimeout || response.StatusCode == HttpStatusCode.TooManyRequests) { // throw error, should be retried response.EnsureSuccessStatusCode(); } var responseBody = await response.Content.ReadAsStringAsync(); _logger?.LogError("Failed to perform TrackEvent due to {StatusCode} and response body {Body}", response.StatusCode, responseBody); //File.AppendAllText(logPath, $"{eventData.EventName} Failed to perform TrackEvent due to {response.StatusCode} and response body {responseBody}\n"); } else { var responseBody = await response.Content.ReadAsStringAsync(); //File.AppendAllText(logPath, $"{eventData.EventName} TrackEvent successful with response body {responseBody}\n"); } } private async Task GetPublicIpAddress() { try { return await _ipClient.GetStringAsync("https://api.ipify.org"); } catch { return "unknown"; } } public virtual ValueTask DisposeAsync() { _http?.Dispose(); return ValueTask.CompletedTask; } private void RefreshSession() { var now = DateTime.UtcNow; var timeSince = now.Subtract(_lastTouched); if (timeSince >= SESSION_TIMEOUT) { _sessionId = NewSessionId(); } _lastTouched = now; } private static string NewSessionId() { var epochInSeconds = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); var random = _random.NextInt64(0, 99999999); return (epochInSeconds * 100000000 + random).ToString(); } private string? GetBaseUrl(string region, AptabaseOptions? options) { if (region == "SH") { if (string.IsNullOrEmpty(options?.Host)) { _logger?.LogWarning("Host parameter must be defined when using Self-Hosted App Key. Tracking will be disabled."); return null; } return options.Host; } return _hosts[region]; } }