1+ using System . Diagnostics . CodeAnalysis ;
2+ using System . Net . Http . Json ;
3+ using Microsoft . Extensions . Logging ;
4+ using SubathonManager . Core ;
5+ using SubathonManager . Core . Interfaces ;
6+
7+ namespace SubathonManager . Services ;
8+
9+ [ ExcludeFromCodeCoverage ]
10+ public class TelemetryService ( ILogger < TelemetryService > ? logger , IConfig config ) : IDisposable , IAppService
11+ {
12+ private readonly HttpClient _http = new ( ) ;
13+ private static readonly TimeSpan PingInterval = TimeSpan . FromHours ( 1 ) ;
14+ private DateTime LastPinged { get ; set ; } = DateTime . MinValue ;
15+ private readonly string _telemetryUrl = "https://telemetry.subathonmanager.app" ;
16+ private CancellationTokenSource ? _cts ;
17+ private Task ? _loopTask ;
18+ private static string ? TelemetryKey =>
19+ "BUILD_TELEMETRY_KEY" . StartsWith ( "BUILD" ) ? Environment . GetEnvironmentVariable ( "SM_TELEMETRY_SECRET" ) : "BUILD_TELEMETRY_KEY" ;
20+
21+ private async Task TryPingAsync ( )
22+ {
23+ if ( ! config . GetBool ( "Telemetry" , "Enabled" , false ) ) return ;
24+
25+ if ( DateTime . UtcNow - LastPinged < PingInterval ) return ;
26+
27+ try
28+ {
29+ LastPinged = DateTime . UtcNow ;
30+ await Task . Delay ( 5000 ) ; // offset a bit
31+ var connections = Utils . GetAllConnections ( ) . Where ( x => x . Service != "Unknown" )
32+ . Select ( c => new
33+ {
34+ source = c . Source . ToString ( ) ,
35+ service = c . Service ,
36+ connected = c . Status
37+ } ) ;
38+
39+ var payload = new
40+ {
41+ install_id = config . GetInstallId ( ) ,
42+ version = AppServices . AppVersion ,
43+ connections
44+ } ;
45+
46+ var request = new HttpRequestMessage ( HttpMethod . Post , _telemetryUrl ) ;
47+ request . Headers . Add ( "X-Telemetry-Key" , TelemetryKey ) ;
48+ request . Content = JsonContent . Create ( payload ) ;
49+
50+ var response = await _http . SendAsync ( request ) ;
51+ //LastPinged = DateTime.UtcNow;
52+ logger ? . LogDebug ( "Telemetry sent for install id {InstallId}. Response: {ResponseCode}" , config . GetInstallId ( ) , response . StatusCode ) ;
53+ }
54+ catch { /**/ }
55+ }
56+
57+
58+ private async Task RunLoopAsync ( CancellationToken ct )
59+ {
60+ while ( ! ct . IsCancellationRequested )
61+ {
62+ await TryPingAsync ( ) ;
63+ try
64+ {
65+ await Task . Delay ( PingInterval , ct ) ;
66+ }
67+ catch ( OperationCanceledException )
68+ {
69+ break ;
70+ }
71+ }
72+ }
73+
74+ public void Dispose ( )
75+ {
76+ _cts ? . Cancel ( ) ;
77+ _cts ? . Dispose ( ) ;
78+ }
79+
80+ public Task StartAsync ( CancellationToken ct = default )
81+ {
82+ LastPinged = DateTime . MinValue ;
83+ _cts = CancellationTokenSource . CreateLinkedTokenSource ( ct ) ;
84+ _loopTask = RunLoopAsync ( _cts . Token ) ;
85+ return Task . CompletedTask ;
86+ }
87+
88+ public Task StopAsync ( CancellationToken ct = default )
89+ {
90+ _cts ? . Cancel ( ) ;
91+ return _loopTask ?? Task . CompletedTask ;
92+ }
93+ }
0 commit comments