Skip to content

Commit 2c550d0

Browse files
committed
add analytics
1 parent e99c0d9 commit 2c550d0

1 file changed

Lines changed: 133 additions & 60 deletions

File tree

gamevault/Helper/AnalyticsHelper.cs

Lines changed: 133 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,9 @@
1111
using System.IO;
1212
using System.Linq;
1313
using System.Management;
14-
using System.Net;
1514
using System.Net.Http;
1615
using System.Net.Http.Headers;
1716
using System.Reflection;
18-
using System.Runtime.Intrinsics.Arm;
1917
using System.Text;
2018
using System.Text.Json;
2119
using System.Text.Json.Serialization;
@@ -38,6 +36,7 @@ internal static class CustomAnalyticsEventKeys
3836
internal static string SERVER_USER_COUNT => "SERVER_USER_COUNT";
3937
internal static string USER_SETTINGS => "USER_SETTINGS";
4038
}
39+
4140
internal class AnalyticsHelper
4241
{
4342
#region Singleton
@@ -59,11 +58,13 @@ public static AnalyticsHelper Instance
5958
}
6059
}
6160
#endregion
61+
6262
private Timer _heartBeatTimer;
63-
private string timeZone;
64-
private string language;
65-
private HttpClient client;
66-
private bool trackingEnabled = false;
63+
private readonly string timeZone;
64+
private readonly string language;
65+
private readonly HttpClient client;
66+
private readonly bool trackingEnabled = false;
67+
6768
internal AnalyticsHelper()
6869
{
6970
trackingEnabled = SettingsViewModel.Instance.SendAnonymousAnalytics;
@@ -75,8 +76,8 @@ internal AnalyticsHelper()
7576

7677
client = new HttpClient();
7778
client.DefaultRequestHeaders.UserAgent.Clear();
78-
string plattform = IsWineRunning() ? "Linux" : "Windows NT";
79-
var userAgent = $"GameVault/{SettingsViewModel.Instance.Version} ({plattform})";
79+
string platform = IsWineRunning() ? "Linux" : "Windows NT";
80+
var userAgent = $"GameVault/{SettingsViewModel.Instance.Version} ({platform})";
8081
client.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
8182
try
8283
{
@@ -86,17 +87,20 @@ internal AnalyticsHelper()
8687
catch { }
8788

8889
}
90+
8991
internal void InitHeartBeat()
9092
{
9193
if (!trackingEnabled)
9294
return;
9395

9496
_heartBeatTimer = new Timer(HeartBeat, null, TimeSpan.Zero, TimeSpan.FromSeconds(30));
9597
}
98+
9699
private void HeartBeat(object state)
97100
{
98101
SendHeartBeat(AnalyticsTargets.HB);
99102
}
103+
100104
internal void RegisterGlobalEvents()
101105
{
102106
if (!trackingEnabled)
@@ -106,6 +110,7 @@ internal void RegisterGlobalEvents()
106110

107111
EventManager.RegisterClassHandler(typeof(IconButton), IconButton.ClickEvent, new RoutedEventHandler(GlobalButton_Click));
108112
}
113+
109114
private async void GlobalButton_Click(object sender, RoutedEventArgs e)
110115
{
111116
try
@@ -119,33 +124,35 @@ private async void GlobalButton_Click(object sender, RoutedEventArgs e)
119124
}
120125
catch { }
121126
}
127+
122128
private string ParseMethodName(ButtonBase buttonBase)
123129
{
124130
var type = buttonBase.GetType();
125-
var eventHandlersStore = typeof(UIElement).GetProperty("EventHandlersStore", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
126-
var eventHandlersStoreValue = eventHandlersStore.GetValue(buttonBase, null);
131+
var eventHandlersStore = typeof(UIElement).GetProperty("EventHandlersStore", BindingFlags.NonPublic | BindingFlags.Instance);
132+
var eventHandlersStoreValue = eventHandlersStore?.GetValue(buttonBase);
127133

128134
if (eventHandlersStoreValue != null)
129135
{
130136
var entriesField = eventHandlersStoreValue.GetType().GetField("_entries", BindingFlags.NonPublic | BindingFlags.Instance);
131-
var entriesValue = entriesField.GetValue(eventHandlersStoreValue);
137+
var entriesValue = entriesField?.GetValue(eventHandlersStoreValue);
132138

133-
var mapStoreField = entriesValue.GetType().GetField("_mapStore", BindingFlags.NonPublic | BindingFlags.Instance);
134-
var mapStoreValue = mapStoreField.GetValue(entriesValue);
139+
var mapStoreField = entriesValue?.GetType().GetField("_mapStore", BindingFlags.NonPublic | BindingFlags.Instance);
140+
var mapStoreValue = mapStoreField?.GetValue(entriesValue);
135141

136-
var entry0Field = mapStoreValue.GetType().GetField("_entry0", BindingFlags.NonPublic | BindingFlags.Instance);
137-
var entry0Value = entry0Field.GetValue(mapStoreValue);
142+
var entry0Field = mapStoreValue?.GetType().GetField("_entry0", BindingFlags.NonPublic | BindingFlags.Instance);
143+
var entry0Value = entry0Field?.GetValue(mapStoreValue);
138144

139-
var Value = entry0Value.GetType().GetField("Value").GetValue(entry0Value);
145+
var valueField = entry0Value?.GetType().GetField("Value");
146+
var value = valueField?.GetValue(entry0Value);
140147

141-
var listStoreField = Value.GetType().GetField("_listStore", BindingFlags.NonPublic | BindingFlags.Instance);
148+
var listStoreField = value?.GetType().GetField("_listStore", BindingFlags.NonPublic | BindingFlags.Instance);
142149
if (listStoreField == null)
143150
{
144151
string methodName = ((EventSetter)buttonBase.Style.Setters[0]).Handler.Method.Name;
145152
string className = ((EventSetter)buttonBase.Style.Setters[0]).Handler.Method.DeclaringType?.Name ?? "UnknownClass";
146153
return $"{className}.{methodName}";
147154
}
148-
var listStoreValue = listStoreField.GetValue(Value);
155+
var listStoreValue = listStoreField.GetValue(value);
149156
var loneEntryField = listStoreValue.GetType().GetField("_loneEntry", BindingFlags.NonPublic | BindingFlags.Instance);
150157
var loneEntryValue = loneEntryField.GetValue(listStoreValue);
151158

@@ -170,16 +177,27 @@ private string ParseMethodName(ButtonBase buttonBase)
170177
}
171178
return string.Empty;
172179
}
180+
173181
private async Task SendHeartBeat(string url)
174182
{
175183
try
176184
{
177185
var jsonContent = new StringContent(JsonSerializer.Serialize(new AnalyticsData()), Encoding.UTF8, "application/json");
178-
await client.PostAsync(url, jsonContent);
186+
await client.PostAsync(url, jsonContent);
179187
}
180-
catch (Exception e) { }
188+
catch (Exception)
189+
{
190+
// swallow
191+
}
192+
193+
}
181194

195+
private string Truncate(string? value, int maxLength)
196+
{
197+
if (string.IsNullOrEmpty(value)) return string.Empty;
198+
return value.Length <= maxLength ? value : value.Substring(0, maxLength);
182199
}
200+
183201
public async Task SendPageView(UserControl page)
184202
{
185203
if (!trackingEnabled)
@@ -191,9 +209,14 @@ public async Task SendPageView(UserControl page)
191209
var jsonContent = new StringContent(JsonSerializer.Serialize(new AnalyticsData() { Timezone = timeZone, CurrentPage = pageString, Language = language }), Encoding.UTF8, "application/json");
192210
await client.PostAsync(AnalyticsTargets.LG, jsonContent);
193211
}
194-
catch (Exception e) { }
212+
catch (Exception)
213+
{
214+
// swallow
215+
}
195216

196217
}
218+
219+
#region ** New Swetrix Error Structure **
197220
public void SendErrorLog(Exception ex)
198221
{
199222
if (!trackingEnabled)
@@ -203,130 +226,180 @@ public void SendErrorLog(Exception ex)
203226
{
204227
try
205228
{
206-
var jsonContent = new StringContent(JsonSerializer.Serialize(new AnalyticsData() { ExceptionType = ex.GetType().ToString(), ExceptionMessage = $"Message:{ex.Message} | InnerException:{ex.InnerException?.Message} | StackTrace:{ex.StackTrace?.Substring(0, Math.Min(2000, ex.StackTrace.Length))} | Is Windows Package: {(App.IsWindowsPackage == true ? "True" : "False")} | Version: {SettingsViewModel.Instance.Version}", Timezone = timeZone, Language = language }), Encoding.UTF8, "application/json");
229+
// Extract first meaningful frame with file info if available
230+
int? line = null;
231+
int? column = null;
232+
string? file = null;
233+
234+
try
235+
{
236+
var st = new StackTrace(ex, true);
237+
var frame = st.GetFrames()?.FirstOrDefault(f => f.GetFileLineNumber() > 0) ?? st.GetFrame(0);
238+
if (frame != null)
239+
{
240+
line = frame.GetFileLineNumber();
241+
column = frame.GetFileColumnNumber();
242+
file = frame.GetFileName();
243+
}
244+
}
245+
catch { /* ignore parsing issues */ }
246+
247+
var data = new AnalyticsData
248+
{
249+
Name = Truncate(ex.GetType().Name, 200),
250+
Message = Truncate(ex.Message, 2000),
251+
LineNo = line,
252+
ColNo = column,
253+
FileName = Truncate(file, 1000),
254+
StackTrace = Truncate(ex.StackTrace, 7500),
255+
Timezone = timeZone,
256+
Language = language,
257+
Metadata = new
258+
{
259+
InnerException = ex.InnerException?.Message,
260+
IsWindowsPackage = App.IsWindowsPackage == true,
261+
Version = SettingsViewModel.Instance.Version
262+
}
263+
};
264+
265+
var jsonContent = new StringContent(JsonSerializer.Serialize(data), Encoding.UTF8, "application/json");
207266
await client.PostAsync(AnalyticsTargets.ER, jsonContent);
208267
}
209-
catch (Exception ex) { }
268+
catch (Exception)
269+
{
270+
// swallow
271+
}
210272
});
211273
}
274+
#endregion
275+
212276
public void SendCustomEvent(string eventName, object meta)
213277
{
214-
if (!trackingEnabled) return;
278+
if (!trackingEnabled)
279+
return;
215280
Task.Run(async () =>
216281
{
217282
try
218283
{
219284
var jsonContent = new StringContent(JsonSerializer.Serialize(new AnalyticsData() { Event = eventName, Metadata = meta, Timezone = timeZone, Language = language }), Encoding.UTF8, "application/json");
220-
HttpResponseMessage res =
221-
await client.PostAsync(AnalyticsTargets.CU, jsonContent);
285+
await client.PostAsync(AnalyticsTargets.CU, jsonContent);
222286
}
223287
catch { }
224288
});
225289
}
290+
226291
private string? ParseUserControl(UserControl page)
227292
{
228293
switch (page)
229294
{
230295
case LibraryUserControl:
231-
{
232-
return "/library";
233-
234-
}
296+
return "/library";
235297
case DownloadsUserControl:
236-
{
237-
return "/downloads";
238-
239-
}
298+
return "/downloads";
240299
case CommunityUserControl:
241-
{
242-
return "/community";
243-
244-
}
300+
return "/community";
245301
case SettingsUserControl:
246-
{
247-
return "/settings";
248-
249-
}
302+
return "/settings";
250303
case AdminConsoleUserControl:
251-
{
252-
return "/admin";
253-
}
304+
return "/admin";
254305
case GameViewUserControl:
255-
{
256-
return "/game";
257-
}
306+
return "/game";
258307
default:
259-
{
260-
return null;
261-
}
308+
return null;
262309
}
263310
}
311+
264312
public object GetSysInfo()
265313
{
266314
try
267315
{
268316
var OS = new ManagementObjectSearcher("select * from Win32_OperatingSystem").Get().Cast<ManagementObject>().First();
269-
string os = $"{OS["Caption"]} - {OS["OSArchitecture"]} - Version.{OS["Version"]}"; os = os.Replace("NT 5.1.2600", "XP"); os = os.Replace("NT 5.2.3790", "Server 2003");
317+
string os = $"{OS["Caption"]} - {OS["OSArchitecture"]} - Version.{OS["Version"]}".Replace("NT 5.1.2600", "XP").Replace("NT 5.2.3790", "Server 2003");
270318
string ram = $"{OS["TotalVisibleMemorySize"]} KB";
271319
var CPU = new ManagementObjectSearcher("select * from Win32_Processor").Get().Cast<ManagementObject>().First();
272320
string cpu = $"{CPU["Name"]} - {CPU["MaxClockSpeed"]} MHz - {CPU["NumberOfCores"]} Core";
273-
return new { app_version = SettingsViewModel.Instance.Version, hardware_os = os, hardware_ram = ram, hardware_cpu = cpu, };
321+
return new { app_version = SettingsViewModel.Instance.Version, hardware_os = os, hardware_ram = ram, hardware_cpu = cpu };
274322
}
275323
catch (Exception ex)
276324
{
277325
return new { app_version = SettingsViewModel.Instance.Version, hardware_os = $"The system information could not be loaded due to an {ex.GetType().Name}" };
278326
}
279327
}
328+
280329
public Dictionary<string, string> PrepareSettingsForAnalytics()
281330
{
282331
try
283332
{
284333
var propertiesToExclude = new[] { "Instance", "UserName", "RootPath", "ServerUrl", "License", "RegistrationUser", "SendAnonymousAnalytics", "IgnoreList", "Themes", "CommunityThemes" };
285334
var trimmedObject = SettingsViewModel.Instance.GetType()
286-
.GetProperties()
287-
.Where(prop => !propertiesToExclude.Contains(prop.Name))
288-
.ToDictionary(prop => prop.Name, prop => prop.GetValue(SettingsViewModel.Instance)?.ToString());
335+
.GetProperties()
336+
.Where(prop => !propertiesToExclude.Contains(prop.Name))
337+
.ToDictionary(prop => prop.Name, prop => prop.GetValue(SettingsViewModel.Instance)?.ToString());
289338

290339
trimmedObject.Add("HasLicence", (SettingsViewModel.Instance.License?.IsActive() == true).ToString());
291340
return trimmedObject;
292341
}
293342
catch { }
294343
return null;
295344
}
345+
296346
private bool IsWineRunning()
297347
{
298348
// Search for WINLOGON process
299349
int p = Process.GetProcessesByName("winlogon").Length;
300350
return p == 0;
301351
}
352+
353+
#region ** DTOs & Helpers **
302354
private class AnalyticsData
303355
{
304356
[JsonPropertyName("pid")]
305357
public string ProjectID => "N2kuL4i8qmOQ";
306358

359+
// General Event Fields
307360
[JsonPropertyName("ev")]
308361
public string? Event { get; set; }
362+
309363
[JsonPropertyName("tz")]
310364
public string? Timezone { get; set; }
365+
311366
[JsonPropertyName("pg")]
312367
public string? CurrentPage { get; set; }
313368

314369
[JsonPropertyName("lc")]
315370
public string? Language { get; set; }
371+
316372
[JsonPropertyName("meta")]
317-
public object? Metadata { get; set; }//Properties of type string only
318-
//Error
373+
public object? Metadata { get; set; }
374+
375+
// Error specific fields
319376
[JsonPropertyName("name")]
320-
public string? ExceptionType { get; set; }
377+
public string? Name { get; set; }
378+
321379
[JsonPropertyName("message")]
322-
public string? ExceptionMessage { get; set; }
380+
public string? Message { get; set; }
381+
382+
[JsonPropertyName("lineno")]
383+
public int? LineNo { get; set; }
384+
385+
[JsonPropertyName("colno")]
386+
public int? ColNo { get; set; }
387+
388+
[JsonPropertyName("stackTrace")]
389+
public string? StackTrace { get; set; }
390+
391+
[JsonPropertyName("filename")]
392+
public string? FileName { get; set; }
323393
}
394+
324395
private static class AnalyticsTargets
325396
{
326397
public static string HB => Encoding.UTF8.GetString(Convert.FromBase64String("aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZy9oYg=="));
327398
public static string LG => Encoding.UTF8.GetString(Convert.FromBase64String("aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZw=="));
328399
public static string CU => Encoding.UTF8.GetString(Convert.FromBase64String("aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZy9jdXN0b20="));
329400
public static string ER => Encoding.UTF8.GetString(Convert.FromBase64String("aHR0cHM6Ly9hbmFseXRpY3MucGxhdGZvcm0ucGhhbGNvLmRlL2xvZy9lcnJvcg=="));
330401
}
402+
#endregion
331403
}
332404
}
405+

0 commit comments

Comments
 (0)