From c4e6acc8cdd63feb92594f980c2dda6e17603c45 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 3 Dec 2025 14:48:30 +0900 Subject: [PATCH 1/6] Add a telemetry to WelsonJS Launcher Add a telemetry to WelsonJS Launcher, To improve debugging experience. --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 24 +++++- .../Properties/Resources.Designer.cs | 36 ++++++++ .../Properties/Resources.resx | 12 +++ .../Telemetry/ITelemetryProvider.cs | 20 +++++ .../Telemetry/PosthogTelemetryProvider.cs | 85 +++++++++++++++++++ .../Telemetry/TelemetryClient.cs | 51 +++++++++++ .../Telemetry/TelemetryOptions.cs | 15 ++++ .../Telemetry/TelemetryProviderFactory.cs | 34 ++++++++ .../WelsonJS.Launcher.csproj | 5 ++ WelsonJS.Toolkit/WelsonJS.Launcher/app.config | 4 + 10 files changed, 282 insertions(+), 4 deletions(-) create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index bad2a37..4518917 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -11,6 +11,7 @@ using System.IO; using System.Linq; using System.Threading; using System.Windows.Forms; +using WelsonJS.Launcher.Telemetry; namespace WelsonJS.Launcher { @@ -21,6 +22,7 @@ namespace WelsonJS.Launcher public static Mutex _mutex; public static ResourceServer _resourceServer; public static string _dateTimeFormat; + public static TelemetryClient _telemetryClient; static Program() { @@ -32,10 +34,10 @@ namespace WelsonJS.Launcher // load native libraries string appDataSubDirectory = "WelsonJS"; - bool requireSigned = string.Equals( - GetAppConfig("NativeRequireSigned"), - "true", - StringComparison.OrdinalIgnoreCase); + bool requireSigned = string.Equals( + GetAppConfig("NativeRequireSigned"), + "true", + StringComparison.OrdinalIgnoreCase); NativeBootstrap.Init( dllNames: new[] { "ChakraCore.dll" }, @@ -43,6 +45,17 @@ namespace WelsonJS.Launcher logger: _logger, requireSigned: requireSigned ); + + // telemetry + var telemetryProvider = GetAppConfig("TelemetryProvider"); + var telemetryOptions = new TelemetryOptions + { + ApiKey = GetAppConfig("TelemetryApiKey"), + BaseUrl = GetAppConfig("TelemetryBaseUrl"), + DistinctId = Environment.MachineName, + Disabled = string.Equals(GetAppConfig("TelemetryDisabled"), "true", StringComparison.OrdinalIgnoreCase) + }; + _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger); } [STAThread] @@ -70,6 +83,9 @@ namespace WelsonJS.Launcher return; } + // send event to the telemetry server + _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", "0.2.7.57"); + // draw the main form Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs index d329414..1036a54 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs @@ -378,6 +378,42 @@ namespace WelsonJS.Launcher.Properties { } } + /// + /// phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string TelemetryApiKey { + get { + return ResourceManager.GetString("TelemetryApiKey", resourceCulture); + } + } + + /// + /// https://us.i.posthog.com과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string TelemetryBaseUrl { + get { + return ResourceManager.GetString("TelemetryBaseUrl", resourceCulture); + } + } + + /// + /// false과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string TelemetryDisabled { + get { + return ResourceManager.GetString("TelemetryDisabled", resourceCulture); + } + } + + /// + /// posthog과(와) 유사한 지역화된 문자열을 찾습니다. + /// + internal static string TelemetryProvider { + get { + return ResourceManager.GetString("TelemetryProvider", resourceCulture); + } + } + /// /// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다. /// diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx index 7397892..e418c1c 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx @@ -229,4 +229,16 @@ ..\Resources\icon_editor_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a + + phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8 + + + https://us.i.posthog.com + + + posthog + + + false + \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs new file mode 100644 index 0000000..40abe54 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs @@ -0,0 +1,20 @@ +// ITelemetryProvider.cs +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors +// https://github.com/gnh1201/welsonjs +// +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; + +namespace WelsonJS.Launcher.Telemetry +{ + public interface ITelemetryProvider + { + Task TrackEventAsync( + string eventName, + IDictionary properties = null, + CancellationToken cancellationToken = default + ); + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs new file mode 100644 index 0000000..1924ae7 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs @@ -0,0 +1,85 @@ +// 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 bool _disposed; + + public PosthogTelemetryProvider(TelemetryOptions options) + { + _options = options ?? throw new ArgumentNullException(nameof(options)); + + 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) }; + } + + 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 + { + await _httpClient.PostAsync( + url, + new StringContent(json, Encoding.UTF8, "application/json"), + cancellationToken + ); + } + catch + { + // swallow for fire-and-forget telemetry + } + } + + public void Dispose() + { + if (_disposed) return; + _disposed = true; + _httpClient.Dispose(); + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs new file mode 100644 index 0000000..9c6dfdc --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs @@ -0,0 +1,51 @@ +// TelemetryClient.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.Threading; +using System.Threading.Tasks; + +namespace WelsonJS.Launcher.Telemetry +{ + public sealed class TelemetryClient : IDisposable + { + private readonly ITelemetryProvider _provider; + private readonly ICompatibleLogger _logger; + + public TelemetryClient(string providerName, TelemetryOptions options, ICompatibleLogger logger = null) + { + _provider = TelemetryProviderFactory.Create(providerName, options); + _logger = logger; + } + + public Task TrackEventAsync( + string eventName, + IDictionary properties = null, + CancellationToken cancellationToken = default) + { + return _provider.TrackEventAsync(eventName, properties, cancellationToken); + } + + public Task TrackAppStartedAsync(string appName, string appVersion) + { + var props = new Dictionary + { + { "app_name", appName }, + { "app_version", appVersion }, + { "os_platform", Environment.OSVersion.Platform.ToString() }, + { "os_version", Environment.OSVersion.Version.ToString() }, + { "timestamp_utc", DateTime.UtcNow.ToString("o") } + }; + + return TrackEventAsync("app_started", props); + } + + public void Dispose() + { + (_provider as IDisposable)?.Dispose(); + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs new file mode 100644 index 0000000..4b057a9 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs @@ -0,0 +1,15 @@ +// TelemetryOptions.cs +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors +// https://github.com/gnh1201/welsonjs +// +namespace WelsonJS.Launcher.Telemetry +{ + public class TelemetryOptions + { + public string ApiKey { get; set; } + public string BaseUrl { get; set; } + public string DistinctId { get; set; } + public bool Disabled { get; set; } = false; + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs new file mode 100644 index 0000000..b7a4978 --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs @@ -0,0 +1,34 @@ +// TelemetryProviderFactory.cs +// SPDX-License-Identifier: GPL-3.0-or-later +// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors +// https://github.com/gnh1201/welsonjs +// +using System; + +namespace WelsonJS.Launcher.Telemetry +{ + public static class TelemetryProviderFactory + { + public static ITelemetryProvider Create(string provider, TelemetryOptions options) + { + if (options == null) + throw new ArgumentNullException(nameof(options)); + + if (provider == null) + throw new ArgumentNullException(nameof(provider)); + + provider = provider.ToLowerInvariant(); + + switch (provider) + { + case "posthog": + return new PosthogTelemetryProvider(options); + + default: + throw new NotSupportedException( + "Unknown telemetry provider: " + provider + ); + } + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index 5bc8b61..d2deefc 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -128,6 +128,11 @@ GlobalSettingsForm.cs + + + + + diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config index b74b7fe..4f86bd8 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config @@ -26,6 +26,10 @@ + + + + From 78bcae182e7dc5a8bde36e4966b849d2c876c8ad Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 3 Dec 2025 14:57:59 +0900 Subject: [PATCH 2/6] Update telemetryEnabled configuration key Update telemetryEnabled configuration key --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 20 +++++++++++-------- .../Properties/Resources.Designer.cs | 6 +++--- .../Properties/Resources.resx | 4 ++-- WelsonJS.Toolkit/WelsonJS.Launcher/app.config | 2 +- 4 files changed, 18 insertions(+), 14 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 4518917..b023a02 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -47,15 +47,19 @@ namespace WelsonJS.Launcher ); // telemetry - var telemetryProvider = GetAppConfig("TelemetryProvider"); - var telemetryOptions = new TelemetryOptions + bool telemetryEnabled = string.Equals(GetAppConfig("TelemetryEnabled"), "true", StringComparison.OrdinalIgnoreCase); + if (!telemetryEnabled) { - ApiKey = GetAppConfig("TelemetryApiKey"), - BaseUrl = GetAppConfig("TelemetryBaseUrl"), - DistinctId = Environment.MachineName, - Disabled = string.Equals(GetAppConfig("TelemetryDisabled"), "true", StringComparison.OrdinalIgnoreCase) - }; - _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger); + string telemetryProvider = GetAppConfig("TelemetryProvider"); + var telemetryOptions = new TelemetryOptions + { + ApiKey = GetAppConfig("TelemetryApiKey"), + BaseUrl = GetAppConfig("TelemetryBaseUrl"), + DistinctId = Environment.MachineName, + Disabled = !telemetryEnabled + }; + _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger); + } } [STAThread] diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs index 1036a54..6ef92bc 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs @@ -397,11 +397,11 @@ namespace WelsonJS.Launcher.Properties { } /// - /// false과(와) 유사한 지역화된 문자열을 찾습니다. + /// true과(와) 유사한 지역화된 문자열을 찾습니다. /// - internal static string TelemetryDisabled { + internal static string TelemetryEnabled { get { - return ResourceManager.GetString("TelemetryDisabled", resourceCulture); + return ResourceManager.GetString("TelemetryEnabled", resourceCulture); } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx index e418c1c..0f32638 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx @@ -238,7 +238,7 @@ posthog - - false + + true \ No newline at end of file diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config index 4f86bd8..617e7c7 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config @@ -29,7 +29,7 @@ - + From 2db2e7b91a9bf1be3be39f6305df2b6a5ffad25d Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 3 Dec 2025 16:34:41 +0900 Subject: [PATCH 3/6] Create TelemetryIdentity.cs and more fixes Create TelemetryIdentity.cs and more fixes --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 20 +-- .../Telemetry/PosthogTelemetryProvider.cs | 11 +- .../Telemetry/TelemetryIdentity.cs | 145 ++++++++++++++++++ .../WelsonJS.Launcher.csproj | 2 + 4 files changed, 162 insertions(+), 16 deletions(-) create mode 100644 WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index b023a02..57d7a80 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -47,19 +47,15 @@ namespace WelsonJS.Launcher ); // telemetry - bool telemetryEnabled = string.Equals(GetAppConfig("TelemetryEnabled"), "true", StringComparison.OrdinalIgnoreCase); - if (!telemetryEnabled) + string telemetryProvider = GetAppConfig("TelemetryProvider"); + var telemetryOptions = new TelemetryOptions { - string telemetryProvider = GetAppConfig("TelemetryProvider"); - var telemetryOptions = new TelemetryOptions - { - ApiKey = GetAppConfig("TelemetryApiKey"), - BaseUrl = GetAppConfig("TelemetryBaseUrl"), - DistinctId = Environment.MachineName, - Disabled = !telemetryEnabled - }; - _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger); - } + ApiKey = GetAppConfig("TelemetryApiKey"), + BaseUrl = GetAppConfig("TelemetryBaseUrl"), + DistinctId = TelemetryIdentity.GetDistinctId(), + Disabled = !string.Equals(GetAppConfig("TelemetryEnabled"), "true", StringComparison.OrdinalIgnoreCase) + }; + _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger); } [STAThread] diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs index 1924ae7..ea445eb 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs @@ -22,11 +22,14 @@ namespace WelsonJS.Launcher.Telemetry { _options = options ?? throw new ArgumentNullException(nameof(options)); - if (string.IsNullOrWhiteSpace(_options.ApiKey)) - throw new ArgumentException("PostHog API key is missing."); + 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.BaseUrl)) + throw new ArgumentException("PostHog BaseUrl is missing."); + } if (string.IsNullOrWhiteSpace(_options.DistinctId)) _options.DistinctId = $"anon-{Guid.NewGuid():N}"; diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs new file mode 100644 index 0000000..68852bb --- /dev/null +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs @@ -0,0 +1,145 @@ +// TelemetryIdentity.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.Management; +using System.Security.Cryptography; +using System.Text; + +namespace WelsonJS.Launcher.Telemetry +{ + public static class TelemetryIdentity + { + /// + /// Collects multiple hardware/OS identity sources (BIOS UUID, OS SerialNumber, MachineName), + /// joins them with ',' and computes SHA-256 hash, then returns the first 16 hex characters + /// as a compact Distinct ID. + /// + public static string GetDistinctId() + { + var sources = new List(); + + string biosUuid = GetBiosUuid(); + if (!string.IsNullOrEmpty(biosUuid)) + sources.Add(biosUuid); + + string osUuid = GetOsUuid(); + if (!string.IsNullOrEmpty(osUuid)) + sources.Add(osUuid); + + string machineName = GetMachineName(); + if (!string.IsNullOrEmpty(machineName)) + sources.Add(machineName); + + string raw = string.Join(",", sources); + string hash = ComputeSha256(raw); + + if (!string.IsNullOrEmpty(hash) && hash.Length >= 16) + return hash.Substring(0, 16); + + return hash; + } + + private static string GetMachineName() + { + try { return Environment.MachineName ?? ""; } + catch { return ""; } + } + + /// + /// Retrieves BIOS UUID from Win32_ComputerSystemProduct. + /// Filters out invalid values (all zeros or all 'F'). + /// + private static string GetBiosUuid() + { + try + { + using (var searcher = + new ManagementObjectSearcher("SELECT UUID FROM Win32_ComputerSystemProduct")) + { + foreach (ManagementObject obj in searcher.Get()) + { + string uuid = obj["UUID"] as string; + if (string.IsNullOrEmpty(uuid)) + return null; + + uuid = uuid.Trim(); + string compact = uuid.Replace("-", "").ToUpperInvariant(); + + // Exclude invalid dummy UUIDs such as all 0s or all Fs + if (IsAllChar(compact, '0') || IsAllChar(compact, 'F')) + return null; + + return uuid; + } + } + } + catch { } + + return null; + } + + /// + /// Retrieves OS-level UUID equivalent using Win32_OperatingSystem.SerialNumber. + /// This value is unique per Windows installation. + /// + private static string GetOsUuid() + { + try + { + using (var searcher = + new ManagementObjectSearcher("SELECT SerialNumber FROM Win32_OperatingSystem")) + { + foreach (ManagementObject obj in searcher.Get()) + { + string serial = obj["SerialNumber"] as string; + if (!string.IsNullOrEmpty(serial)) + return serial.Trim(); + } + } + } + catch { } + + return null; + } + + private static bool IsAllChar(string s, char c) + { + if (string.IsNullOrEmpty(s)) return false; + for (int i = 0; i < s.Length; i++) + if (s[i] != c) return false; + return true; + } + + /// + /// Computes SHA-256 hex string for the given input text. + /// Returns null if hashing fails. + /// + private static string ComputeSha256(string input) + { + if (input == null) input = ""; + + try + { + using (var sha = SHA256.Create()) + { + var data = Encoding.UTF8.GetBytes(input); + var digest = sha.ComputeHash(data); + + var sb = new StringBuilder(digest.Length * 2); + for (int i = 0; i < digest.Length; i++) + sb.Append(digest[i].ToString("x2")); + + return sb.ToString(); + } + } + catch + { + return null; + } + } + } +} diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj index d2deefc..a656599 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -80,6 +80,7 @@ + @@ -131,6 +132,7 @@ + From 3188317bf6fc91b36bf6eb07a5e1c0720ad072f2 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 3 Dec 2025 16:45:31 +0900 Subject: [PATCH 4/6] Fix the TelemetryClient Fix the TelemetryClient --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 39 ++++++++++++++----- .../Telemetry/PosthogTelemetryProvider.cs | 4 +- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 57d7a80..14f5e22 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -47,15 +47,33 @@ namespace WelsonJS.Launcher ); // telemetry - string telemetryProvider = GetAppConfig("TelemetryProvider"); - var telemetryOptions = new TelemetryOptions + try { - ApiKey = GetAppConfig("TelemetryApiKey"), - BaseUrl = GetAppConfig("TelemetryBaseUrl"), - DistinctId = TelemetryIdentity.GetDistinctId(), - Disabled = !string.Equals(GetAppConfig("TelemetryEnabled"), "true", StringComparison.OrdinalIgnoreCase) - }; - _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger); + var telemetryProvider = GetAppConfig("TelemetryProvider"); + var telemetryOptions = new TelemetryOptions + { + ApiKey = GetAppConfig("TelemetryApiKey"), + BaseUrl = GetAppConfig("TelemetryBaseUrl"), + DistinctId = TelemetryIdentity.GetDistinctId(), + Disabled = !string.Equals( + GetAppConfig("TelemetryEnabled"), + "true", + StringComparison.OrdinalIgnoreCase) + }; + + if (!telemetryOptions.Disabled && + !string.IsNullOrWhiteSpace(telemetryProvider) && + !string.IsNullOrWhiteSpace(telemetryOptions.ApiKey) && + !string.IsNullOrWhiteSpace(telemetryOptions.BaseUrl)) + { + _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger); + } + } + catch (Exception ex) + { + _logger.Error($"Telemetry initialization failed: {ex}"); + _telemetryClient = null; + } } [STAThread] @@ -84,7 +102,10 @@ namespace WelsonJS.Launcher } // send event to the telemetry server - _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", "0.2.7.57"); + if (_telemetryClient != null) + { + _ = _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", "0.2.7.57"); + } // draw the main form Application.EnableVisualStyles(); diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs index ea445eb..63c6313 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs @@ -66,11 +66,11 @@ namespace WelsonJS.Launcher.Telemetry try { - await _httpClient.PostAsync( + using (var response = await _httpClient.PostAsync( url, new StringContent(json, Encoding.UTF8, "application/json"), cancellationToken - ); + )) { /* disposed automatically */ } } catch { From 1d4b7af5b37a2b528aad0e8cc87033b8b8880352 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 3 Dec 2025 16:54:19 +0900 Subject: [PATCH 5/6] Fix the logger for telemetry Fix the logger for telemetry --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 3 ++- .../WelsonJS.Launcher/Properties/AssemblyInfo.cs | 4 ++-- .../Telemetry/PosthogTelemetryProvider.cs | 14 ++++++++++---- .../WelsonJS.Launcher/Telemetry/TelemetryClient.cs | 4 +--- .../Telemetry/TelemetryProviderFactory.cs | 4 ++-- 5 files changed, 17 insertions(+), 12 deletions(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 14f5e22..3455c5f 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -104,7 +104,8 @@ namespace WelsonJS.Launcher // send event to the telemetry server if (_telemetryClient != null) { - _ = _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", "0.2.7.57"); + var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + _ = _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", version); } // draw the main form diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/AssemblyInfo.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/AssemblyInfo.cs index 8b48616..520bc4d 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/AssemblyInfo.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를 // 기본값으로 할 수 있습니다. // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.2.7.55")] -[assembly: AssemblyFileVersion("0.2.7.55")] +[assembly: AssemblyVersion("0.2.7.58")] +[assembly: AssemblyFileVersion("0.2.7.58")] diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs index 63c6313..5240b55 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs @@ -16,9 +16,10 @@ namespace WelsonJS.Launcher.Telemetry { private readonly TelemetryOptions _options; private readonly HttpClient _httpClient; + private readonly ICompatibleLogger _logger; private bool _disposed; - public PosthogTelemetryProvider(TelemetryOptions options) + public PosthogTelemetryProvider(TelemetryOptions options, ICompatibleLogger logger = null) { _options = options ?? throw new ArgumentNullException(nameof(options)); @@ -35,6 +36,7 @@ namespace WelsonJS.Launcher.Telemetry _options.DistinctId = $"anon-{Guid.NewGuid():N}"; _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) }; + _logger = logger; } public async Task TrackEventAsync( @@ -70,11 +72,15 @@ namespace WelsonJS.Launcher.Telemetry url, new StringContent(json, Encoding.UTF8, "application/json"), cancellationToken - )) { /* disposed automatically */ } + )) + { + response.EnsureSuccessStatusCode(); + } } - catch + catch (Exception ex) { - // swallow for fire-and-forget telemetry + // Log and swallow for fire-and-forget telemetry + _logger?.Error($"Failed to send telemetry event '{eventName}': {ex.Message}"); } } diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs index 9c6dfdc..9fb1de7 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs @@ -13,12 +13,10 @@ namespace WelsonJS.Launcher.Telemetry public sealed class TelemetryClient : IDisposable { private readonly ITelemetryProvider _provider; - private readonly ICompatibleLogger _logger; public TelemetryClient(string providerName, TelemetryOptions options, ICompatibleLogger logger = null) { - _provider = TelemetryProviderFactory.Create(providerName, options); - _logger = logger; + _provider = TelemetryProviderFactory.Create(providerName, options, logger); } public Task TrackEventAsync( diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs index b7a4978..3454e15 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs @@ -9,7 +9,7 @@ namespace WelsonJS.Launcher.Telemetry { public static class TelemetryProviderFactory { - public static ITelemetryProvider Create(string provider, TelemetryOptions options) + public static ITelemetryProvider Create(string provider, TelemetryOptions options, ICompatibleLogger logger = null) { if (options == null) throw new ArgumentNullException(nameof(options)); @@ -22,7 +22,7 @@ namespace WelsonJS.Launcher.Telemetry switch (provider) { case "posthog": - return new PosthogTelemetryProvider(options); + return new PosthogTelemetryProvider(options, logger); default: throw new NotSupportedException( From e84a69d929294019f335dbf073d3f4a6369ab602 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Wed, 3 Dec 2025 16:59:41 +0900 Subject: [PATCH 6/6] Add null-check for Assembly Version property. The code properly checks if _telemetryClient is null before tracking the event and uses the assembly version dynamically. However, the Version property of AssemblyName can be null, which would cause a NullReferenceException when calling .ToString(). --- WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs index 3455c5f..2ed4b5d 100644 --- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs @@ -104,7 +104,7 @@ namespace WelsonJS.Launcher // send event to the telemetry server if (_telemetryClient != null) { - var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version.ToString(); + var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown"; _ = _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", version); }