From e50a966b89cb830aa5923b82d62dd3b857e73802 Mon Sep 17 00:00:00 2001 From: "Namhyeon, Go" Date: Mon, 22 Dec 2025 00:37:42 +0900 Subject: [PATCH] Remove telemetry, add curl fallback and integrity hash Removed all telemetry-related code and configuration from WelsonJS.Launcher, including source files, resource strings, and app.config keys. Enhanced AssemblyLoader to support fallback to curl.exe for downloads on legacy Windows, with integrity hash verification. Updated documentation and resource files to reflect the new curl fallback mechanism and added the required integrity hash for curl.exe. --- .../Catswords.Phantomizer/AssemblyLoader.cs | 180 +++++++++++++++--- .../Catswords.Phantomizer/README.md | 19 ++ .../WelsonJS.Launcher/Program.cs | 45 +---- .../Properties/Resources.Designer.cs | 47 +---- .../Properties/Resources.resx | 15 +- .../Resources/Catswords.Phantomizer.dll.gz | Bin 18294 -> 12041 bytes .../Telemetry/ITelemetryProvider.cs | 20 -- .../Telemetry/PosthogTelemetryProvider.cs | 94 --------- .../Telemetry/TelemetryClient.cs | 49 ----- .../Telemetry/TelemetryIdentity.cs | 145 -------------- .../Telemetry/TelemetryOptions.cs | 15 -- .../Telemetry/TelemetryProviderFactory.cs | 34 ---- .../WelsonJS.Launcher/TraceLogger.cs | 5 +- .../WelsonJS.Launcher.csproj | 6 - .../WelsonJS.Launcher/app.config | 5 +- 15 files changed, 197 insertions(+), 482 deletions(-) delete mode 100644 WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs delete mode 100644 WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs delete mode 100644 WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryClient.cs delete mode 100644 WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs delete mode 100644 WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs delete mode 100644 WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs diff --git a/WelsonJS.Augmented/Catswords.Phantomizer/AssemblyLoader.cs b/WelsonJS.Augmented/Catswords.Phantomizer/AssemblyLoader.cs index 5a6bf09..68ea3bd 100644 --- a/WelsonJS.Augmented/Catswords.Phantomizer/AssemblyLoader.cs +++ b/WelsonJS.Augmented/Catswords.Phantomizer/AssemblyLoader.cs @@ -180,6 +180,12 @@ namespace Catswords.Phantomizer if (_registered) return; + // Fix TLS connectivity issues + EnsureSecurityProtocols(SecurityProtocolType.Tls12); + EnsureSecurityProtocolByName("Tls13"); // Add if available + // EnsureSecurityProtocols(SecurityProtocolType.Tls11, SecurityProtocolType.Tls); // Optional legacy compatibility (uncomment if needed) + + // Load integrity manifest try { if (!_integrityLoaded) @@ -198,10 +204,6 @@ namespace Catswords.Phantomizer throw; } - EnsureSecurityProtocols(SecurityProtocolType.Tls12); - EnsureSecurityProtocolByName("Tls13"); // Add if available - // EnsureSecurityProtocols(SecurityProtocolType.Tls11, SecurityProtocolType.Tls); // Optional legacy compatibility (uncomment if needed) - AppDomain.CurrentDomain.AssemblyResolve += OnAssemblyResolve; _registered = true; @@ -209,7 +211,6 @@ namespace Catswords.Phantomizer } } - /// /// Loads native modules associated with an assembly (explicit). /// @@ -333,6 +334,19 @@ namespace Catswords.Phantomizer LoadNativeModules(an.Name, an.Version, fileNames); } + public static void AddIntegrityHash(string hash) + { + if (string.IsNullOrWhiteSpace(hash)) + throw new ArgumentNullException(nameof(hash)); + + lock (IntegritySyncRoot) + { + if (_integrityHashes == null) + _integrityHashes = new HashSet(StringComparer.OrdinalIgnoreCase); + + _integrityHashes.Add(hash.Trim().ToLower()); + } + } // ======================================================================== // ASSEMBLY RESOLVE HANDLER (MANAGED) @@ -399,8 +413,6 @@ namespace Catswords.Phantomizer private static void DownloadFile(string url, string dest) { - HttpResponseMessage res = null; - try { string gzUrl = url + ".gz"; @@ -416,13 +428,11 @@ namespace Catswords.Phantomizer if (!downloaded) { Trace.TraceInformation("Downloading file from: {0}", url); - res = Http.GetAsync(url).GetAwaiter().GetResult(); - res.EnsureSuccessStatusCode(); - using (Stream s = res.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) + using (var stream = GetStreamFromUrl(url)) using (var fs = new FileStream(dest, FileMode.Create, FileAccess.Write)) { - s.CopyTo(fs); + stream.CopyTo(fs); } Trace.TraceInformation("Downloaded file to: {0}", dest); @@ -443,10 +453,6 @@ namespace Catswords.Phantomizer Trace.TraceError("Unexpected error downloading {0}: {1}", url, ex.Message); throw; } - finally - { - res?.Dispose(); - } } @@ -527,8 +533,6 @@ namespace Catswords.Phantomizer if (_integrityLoaded) return; - _integrityHashes = new HashSet(StringComparer.OrdinalIgnoreCase); - if (string.IsNullOrWhiteSpace(IntegrityUrl)) { _integrityLoaded = true; @@ -543,18 +547,24 @@ namespace Catswords.Phantomizer if (!verified) throw new InvalidOperationException("IntegrityUrl verification failed."); - using (var res = Http.GetAsync(IntegrityUrl).GetAwaiter().GetResult()) + using (var stream = GetStreamFromUrl(IntegrityUrl)) { - res.EnsureSuccessStatusCode(); - using (var stream = res.Content.ReadAsStreamAsync().GetAwaiter().GetResult()) - { - doc = XDocument.Load(stream); - } + doc = XDocument.Load(stream); } } catch (Exception ex) { - Trace.TraceError("AssemblyIntegrity: failed to load manifest: {0}", ex.Message); + Trace.TraceError("AssemblyIntegrity: failed to load manifest.\n{0}", ex.ToString()); + + Exception inner = ex.InnerException; + int depth = 0; + while (inner != null && depth < 8) + { + Trace.TraceError("AssemblyIntegrity: inner[{0}]\n{1}", depth, inner.ToString()); + inner = inner.InnerException; + depth++; + } + throw new InvalidOperationException("Failed to load AssemblyIntegrity manifest.", ex); } @@ -577,7 +587,7 @@ namespace Catswords.Phantomizer if (string.IsNullOrWhiteSpace(val)) continue; - _integrityHashes.Add(val); + AddIntegrityHash(val); } _integrityLoaded = true; @@ -760,7 +770,7 @@ namespace Catswords.Phantomizer } // Adds protocol by enum name when available (e.g., "Tls13"), otherwise no-op. - public static void EnsureSecurityProtocolByName(string protocolName) + private static void EnsureSecurityProtocolByName(string protocolName) { if (string.IsNullOrEmpty(protocolName)) return; @@ -818,5 +828,123 @@ namespace Catswords.Phantomizer ); } } + + public static Stream CurlGetAsStream(string url) + { + Trace.TraceInformation("Trying curl.exe to get URL: {0}", url); + + // Resolve curl.exe only from the application base directory + string baseDir = AppDomain.CurrentDomain.BaseDirectory; + string curlExePath = Path.Combine(baseDir, "curl.exe"); + + // Check existence of curl.exe + if (!File.Exists(curlExePath)) + throw new FileNotFoundException("curl.exe was not found in the application directory.", curlExePath); + + // Check integrity of curl.exe + byte[] bytes = File.ReadAllBytes(curlExePath); + string sha256 = ComputeHashHex(bytes, SHA256.Create()); + if (_integrityHashes == null || !_integrityHashes.Contains(sha256)) + throw new InvalidOperationException("curl.exe integrity check failed."); + + // Prepare process start info + var psi = new ProcessStartInfo + { + FileName = curlExePath, + Arguments = + "-f -sS -L --retry 3 --retry-delay 1 " + + "--connect-timeout 10 --max-time 30 " + + "\"" + url + "\"", + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true + }; + + var process = new Process { StartInfo = psi }; + if (!process.Start()) + throw new InvalidOperationException("Failed to start curl.exe process."); + + // Drain stderr asynchronously. + // If any error line is received, log it immediately. + process.ErrorDataReceived += (s, e) => + { + if (!string.IsNullOrEmpty(e.Data)) + { + Trace.TraceError("curl stderr: {0}", e.Data); + } + }; + process.BeginErrorReadLine(); + + var memory = new MemoryStream(); + + // Read stdout fully; this completes when stdout is closed (EOF) + process.StandardOutput.BaseStream.CopyTo(memory); + + // Enforce a hard timeout so we never wait forever + if (!process.WaitForExit(60000)) + { + try { process.Kill(); } catch { } + throw new TimeoutException("curl.exe did not exit within the hard timeout."); + } + + if (process.ExitCode != 0) + throw new InvalidOperationException("curl.exe failed with exit code " + process.ExitCode + "."); + + memory.Position = 0; + return memory; // Caller must dispose the stream + } + + private static T ExecuteWithFallback(Func primaryAction, Func shouldFallback, Func fallbackAction) + { + try + { + return primaryAction(); + } + catch (Exception ex) + { + if (shouldFallback != null && shouldFallback(ex)) + return fallbackAction(); + + throw; + } + } + + private static Stream GetStreamFromUrl(string url) + { + Trace.TraceInformation("Getting stream from URL: {0}", url); + + return ExecuteWithFallback( + primaryAction: () => + { + var res = Http.GetAsync(url).GetAwaiter().GetResult(); + res.EnsureSuccessStatusCode(); + return res.Content.ReadAsStreamAsync().GetAwaiter().GetResult(); + }, + shouldFallback: IsTlsHandshakeFailure, + fallbackAction: () => + { + return CurlGetAsStream(url); + } + ); + } + + private static bool IsTlsHandshakeFailure(Exception ex) + { + bool isTlsException = ex is HttpRequestException httpEx && + httpEx.InnerException is WebException webEx && + webEx.Status == WebExceptionStatus.SecureChannelFailure; + + if (isTlsException) + { + Trace.TraceWarning("TLS handshake failure: {0}", ex.Message); + } + else + { + Trace.TraceInformation("HttpRequestException is not a TLS handshake failure: {0}", ex.Message); + } + + return isTlsException; + } } } diff --git a/WelsonJS.Augmented/Catswords.Phantomizer/README.md b/WelsonJS.Augmented/Catswords.Phantomizer/README.md index 6b5d02d..72bbfcb 100644 --- a/WelsonJS.Augmented/Catswords.Phantomizer/README.md +++ b/WelsonJS.Augmented/Catswords.Phantomizer/README.md @@ -178,6 +178,25 @@ Once uploaded and pinned, the file cannot be silently modified without changing --- +### ๐Ÿ”„ curl.exe Fallback on Legacy Windows + +If TLS connectivity issues occur on older versions of Windows (earlier than Windows 10), it is possible to fall back to using `curl.exe`. In this case, the `curl.exe` binary must pass an integrity check. + +```csharp +// curl.exe integrity hash can be added here if needed +// e.g., 23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d (curl-8.17.0-win-x86-full.2025-11-09, Muldersoft) +loaderType.GetMethod("AddIntegrityHash")?.Invoke(null, new object[] { GetAppConfig("IntegrityHashCurl") }); + +/* +// if use non-reflective call +// curl.exe integrity hash can be added here if needed +// e.g., 23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d (curl-8.17.0-win-x86-full.2025-11-09, Muldersoft) +AssemblyLoader.AddIntegrityHash(GetAppConfig("IntegrityHashCurl")); +*/ +``` + +--- + ## ๐Ÿ“ฅ Download the pre-compiled file * [Download Catswords.Phantomizer.dll.gz (catswords.blob.core.windows.net)](https://catswords.blob.core.windows.net/welsonjs/packages/managed/Catswords.Phantomizer/1.0.0.1/Catswords.Phantomizer.dll.gz) diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/Program.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Program.cs index bbea457..1ab087b 100644 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Program.cs +++ b/WelsonJS.Augmented/WelsonJS.Launcher/Program.cs @@ -13,7 +13,6 @@ using System.Linq; using System.Reflection; using System.Threading; using System.Windows.Forms; -using WelsonJS.Launcher.Telemetry; namespace WelsonJS.Launcher { @@ -24,7 +23,6 @@ namespace WelsonJS.Launcher public static Mutex _mutex; public static ResourceServer _resourceServer; public static string _dateTimeFormat; - public static TelemetryClient _telemetryClient; static Program() { @@ -36,35 +34,6 @@ namespace WelsonJS.Launcher // load external assemblies InitializeAssemblyLoader(); - - // telemetry - try - { - 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] @@ -92,13 +61,6 @@ namespace WelsonJS.Launcher return; } - // send event to the telemetry server - if (_telemetryClient != null) - { - var version = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version?.ToString() ?? "unknown"; - _ = _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", version); - } - // draw the main form Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); @@ -133,6 +95,9 @@ namespace WelsonJS.Launcher loaderType.GetProperty("LoaderNamespace")?.SetValue(null, typeof(Program).Namespace); loaderType.GetProperty("AppName")?.SetValue(null, "WelsonJS"); loaderType.GetProperty("IntegrityUrl")?.SetValue(null, GetAppConfig("AssemblyIntegrityUrl")); + // curl.exe integrity hash can be added here if needed + // e.g., 23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d (curl-8.17.0-win-x86-full.2025-11-09, Muldersoft) + loaderType.GetMethod("AddIntegrityHash")?.Invoke(null, new object[] { GetAppConfig("IntegrityHashCurl") }); loaderType.GetMethod("Register")?.Invoke(null, null); var loadNativeModulesMethod = loaderType.GetMethod( @@ -161,6 +126,9 @@ namespace WelsonJS.Launcher AssemblyLoader.IntegrityUrl = GetAppConfig("AssemblyIntegrityUrl"); // (Optional) Set the integrity URL AssemblyLoader.LoaderNamespace = typeof(Program).Namespace; AssemblyLoader.AppName = "WelsonJS"; + // curl.exe integrity hash can be added here if needed + // e.g., 23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d (curl-8.17.0-win-x86-full.2025-11-09, Muldersoft) + AssemblyLoader.AddIntegrityHash(GetAppConfig("IntegrityHashCurl")); AssemblyLoader.Register(); AssemblyLoader.LoadNativeModules( @@ -171,7 +139,6 @@ namespace WelsonJS.Launcher */ } - public static void RecordFirstDeployTime(string directory, string instanceId) { // get current time diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.Designer.cs index ddfe573..4edcfc2 100644 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.Designer.cs +++ b/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.Designer.cs @@ -19,7 +19,7 @@ namespace WelsonJS.Launcher.Properties { // ํด๋ž˜์Šค์—์„œ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. // ๋ฉค๋ฒ„๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์ œ๊ฑฐํ•˜๋ ค๋ฉด .ResX ํŒŒ์ผ์„ ํŽธ์ง‘ํ•œ ๋‹ค์Œ /str ์˜ต์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ResGen์„ // ๋‹ค์‹œ ์‹คํ–‰ํ•˜๊ฑฐ๋‚˜ VS ํ”„๋กœ์ ํŠธ๋ฅผ ๋‹ค์‹œ ๋นŒ๋“œํ•˜์‹ญ์‹œ์˜ค. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "18.0.0.0")] [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] internal class Resources { @@ -315,6 +315,15 @@ namespace WelsonJS.Launcher.Properties { } } + /// + /// 23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d๊ณผ(์™€) ์œ ์‚ฌํ•œ ์ง€์—ญํ™”๋œ ๋ฌธ์ž์—ด์„ ์ฐพ์Šต๋‹ˆ๋‹ค. + /// + internal static string IntegrityHashCurl { + get { + return ResourceManager.GetString("IntegrityHashCurl", resourceCulture); + } + } + /// /// ๊ณผ(์™€) ์œ ์‚ฌํ•œ ์ง€์—ญํ™”๋œ ๋ฌธ์ž์—ด์„ ์ฐพ์Šต๋‹ˆ๋‹ค. /// @@ -397,42 +406,6 @@ 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); - } - } - - /// - /// true๊ณผ(์™€) ์œ ์‚ฌํ•œ ์ง€์—ญํ™”๋œ ๋ฌธ์ž์—ด์„ ์ฐพ์Šต๋‹ˆ๋‹ค. - /// - internal static string TelemetryEnabled { - get { - return ResourceManager.GetString("TelemetryEnabled", resourceCulture); - } - } - - /// - /// posthog๊ณผ(์™€) ์œ ์‚ฌํ•œ ์ง€์—ญํ™”๋œ ๋ฌธ์ž์—ด์„ ์ฐพ์Šต๋‹ˆ๋‹ค. - /// - internal static string TelemetryProvider { - get { - return ResourceManager.GetString("TelemetryProvider", resourceCulture); - } - } - /// /// 141.101.82.1๊ณผ(์™€) ์œ ์‚ฌํ•œ ์ง€์—ญํ™”๋œ ๋ฌธ์ž์—ด์„ ์ฐพ์Šต๋‹ˆ๋‹ค. /// diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.resx index 7d96335..bb52cbb 100644 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.resx +++ b/WelsonJS.Augmented/WelsonJS.Launcher/Properties/Resources.resx @@ -226,18 +226,6 @@ ..\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 - - - true - https://catswords.blob.core.windows.net/welsonjs/packages @@ -247,4 +235,7 @@ https://spare-yellow-cicada.myfilebase.com/ipfs/QmYL29Z7BRvE6dnL4HPKeNvJyNJrh2LnVhzo3k9bp3wBZf + + 23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d + \ No newline at end of file diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/Resources/Catswords.Phantomizer.dll.gz b/WelsonJS.Augmented/WelsonJS.Launcher/Resources/Catswords.Phantomizer.dll.gz index b60a106e8eeb717c70ff9805034e286a23bf4018..14cbbaa610cadded9e3425cb783692eef0eb2592 100644 GIT binary patch literal 12041 zcmV+kFZR$MiwFox5lCqS07GGPb9ZlYWOFW1Xkl)2Z*6IMWpXZLY-|ASU3qvD*Aaia zl2*4Y$zYpcKqNQ_Y>W>eKuD;u4LCLCupOWwHL|?66=bd0l>n!OnBGUyHf_^0z3APq zchmb!@3c+tD{YfDz3=-zNPjbLSBK@4(0*V4X(e{{&AfT@=FOWo@7R^u^N42%mk`2@ z>wyP^c$bi$hlvY+=5R$;yeA^=@ZY`iUCQ9yD@Ud?Rw8d2Q)X%=kxu1uMjfL4k3;L2}s(dHvuHLQu;(W zt&-mKDc{A1m`)$G>*(Xb%KyrDx|KhlJtM?kyD`t!7HH&4g$UvH=F>uqh>Q3@KVJpv z1HQWgZ!hSl3b-f7m{+z{tr2mw-L%ZKAl+C;DRjJ=?_v1YZt7VBj3L(9mUTl7-;Vie zsyE5|ZZ6^xKi?|EN5Q3tfA4e28I*LdU9QgY{gN}Pjj9rL*{DmRZX4x$eTPe|5?pso zs0~&ez29<~>{at&$$Fs1BJzlU7Hk#XwM#poFX>JEX95rrXb5U?CApa0=8PAVkgv^i z@29}jcuOlJw=^WkL@!P*6G9UvYRV^a>)CAcp- zuf-w6T1w4pri{ip(%k$LDYuN9a$nHf=GBtR3HjDqrCIjo=xrj=1i`BCi?4l01c|z| z!zZ2sgSf~hQb{3|)SzU@SEZu5=)4m4gEXdqw1P;kMacy2WChS4TcW$cY8&BMWoBa~ zZb3GEz1C3gjcIYOZT%IOBrhdFzNNQpk&XAAYiUe+pvt+HCiB%0a;{~GdamUX@44h< z#OaUuldF)pTp$qyA6YdJ3$!(u?^Y^|=s%Zikycq*9~9XK`bAU)!-cw0^^v)jUkZMW=2sxAsU!=!#unc^ z0L*qqv)d)|v4~Afc7kTRv3aX&0sYvRT}W+x_4x-LxGx+FwfR8lW)Bs>y}(NzD@4zr z;Lp2ot;O|STq|)&|3%z_MUUdj0tq{@373g0fW)IK=t~hNzDY5k;{n9FfY zb;VX=0}=sk@p%uE5 zo&r4F?nTL~ord4zE)QROke3IkYHVjqIAi^Ow=?`T<6*e9MKq%acRBD|l}3B)w|J`g zB$q20A&*1sRmHSGvfKv&ihqX3srQT@Z3{Mpym8fOtp^mTMqP3Tc~~#N98oz-G~ z0XQZJ`r5o+#~{)k%f;14%CSy^UwBCebJkOKQVvUBJUB04hDrf5v_Qc0BBI3HcEk+F!d~+q&?5P88ue>r z{0hq`8jCo=ivn3&Q7peu@bYRLyu42El2w(X7c!P{bux%ixIrZ{h%MEsY*CJ3*J}$! zkFs(cf|tUW)dE^Fi6#F7%)K$n1JmcF^%F(vy2<>#I+p_{i9MD zH#}k`a3AUim$|`fTP~n-vz8irfRM|!`88K^5h}x2hqo>f<~DDIciF9I3<;mJVwe`@ z(<)e0n?+5oWJhx~HCaQXG}QORG-EGi(umh+#M@yLs0XOWM$L~dP^WM?hGQ2E3-kBj za#{Q2l4EzhPnaFPa;R4+()+5H9rb=;K5jmbznX_)w6cM0IV&PhBok8F5UZcVXYKRNNWN+a$MsQC&8^sVD zcxZjYS8Gc|-Fq)r1lUK(zrDt_B2P?miL2_$}Mr2SfX8 zL)(GZyKpza;Le!$x{}us4bE|d9RM0V8>YbG%qd)tsdU6Y&$a<)RU&OmtCla$=LuHZ zUud%05GK|xT7_OGHE*d$m+uag3u{z3Y!_bVEPF5-+-rM2D#yYtxFWb-D5~ey$KqOz z>vi+!v-bY)-Ia8k_rth;f-5NOkInNVT))S)O3v#_CA~?J^d_5rjUvmB;@;w{0c`&D zpl!p|gS0c}JzB_^fg$^X01eaYXaiqV97@kGN_tgEFs%f^QR@GOEuM? zgS4pTsuI$qCS9FwiPA-fIl0(3|%f{_4Gky?fw5;JJ4#*pl9=okIv~ zdg8b@f;>AnuE}+IOmhZv&>RUmb1t)R@F3UqCC6yQ%y(RI?^#Hr;rzT{EGv7ao7m40sieIc%`b=6#JdG7w3 zv`egFbkX*H^ACl+wrs1jE$%GT8=(o7hU0aP-)(*m*(iC8*Mi9`ZnOv2f?UpP%s5-$ zWy1VyUBwiX%$4J7>#VsbTCrG>N|RBY;l7LNxQ<(ri&jr$P87$I=~K~pQ-C&q^J?yh ziM(222e~JpUQc{Pw2&WAd^BZzkBK;{U4`a+-c1S^)Ve%@<221`US9dNin!{fDm30I zo{6L3n2AFmguO|Ns`fjJ8c##Lr?ttS5K3^)KH=~*g5+rez(QuN+>2e0`s;YU$6{MG z3cSL8TBhj{R*|EYH;k#fb%j6XzO~-ti+jwQ(X8YN8igl$rRr`xMD_Wfmg)M2&sZrJ zcR>w<#ON+B?d<)KOAPLH*8eIWXhrS)&?&hIaW@28J9xVlY6$z{uIjzkED-MJ;C(^N z=stTGde=Jgo5Zq>j(m@JDg`0>SGm!AW~_9A(um`g8>J(*!6%o(jB-!8)cgRrG_KG5 zIyLxFUAZc7HGoezKA?!yxJb%1m&S8=ob^mT zn#~XtDw^LK@%f)!@Ak&rM}z*xLt(!UHvYL+JFhg@=Q-`dn>@{~`m>74wVPYz#Ng#b zX+hsC%#HPBkJhSk#eHkZuZ#6%Ip&w@$%9oCWe>Mo!p$hPDl}R(Cl^vy&>^=s*}B2P0j(nlJKz4e-!iMYO>-z^|e+k53GA1L#L?TvIV+?&$hsx zMIyh%DOhfNOOvnK3JGji7FE~boo9&zS>kV1OttNA<`Yw~e6v{HWFLFJ`A(5=Z1HYT z7V+M8ba8DI;lo4YEE~zZ4GhUOY@n+BLCAbHa=bEvdy~i5Q_FELEW>7$Z+>H@6gTUA zF*-$vH8?(MJ%;8;ukl!_y2TtuhHCz>!FFroaZGM79?$ni;|cctiT3?Tbgy??PbMko zkEak$);FFi>!kHHD*S1w!kFjGMkv*?uJQ6S8<04!CFzv(W)d(*ImRs{0}`G_ckAgK zHGn>Y^tly)wpo%TarCD`7|#Ua94RgnRPtG}FFco1hnVe+sQcNnZsRs)cGU;5#cVNe zMwU&i55+^t=O7_hiseC#b=(ccbGbsTK8)}fwqq8rbVDh{L;XiFs}+>RGNu^MqyD>Z zc~H38{4THYd|q-kAe#8ycAk3v60J1mX}h#s#(j^t7L*3avDWa!te)OPXJY1O8Y>3q zqljjEjCLM^V8a*4Q|C)5{X(QcZ*}Oj2yW%mb-89R=;)lsV-azg77RP%|Jivak6 zg84>B;%5nq=G+X)ksEj=X1$oWe5I7XlnNkKK7VRCv!!(4G#4bMmk_nVcqwT#7b;Pf z*px7xR}nc0k=k5@&~OQ@BegyZwXBy>T{OL}{i0CkP06PlX){Qt2i+xDc<=zo8}N{+0Po+^diy+t-VG z6Yr_&sAhK8@)U7Qv^gS-=i-=Vf>%csuUjk0U)?Ff+L+iyslLME(-cMu)|l zx60>;m+cxI=)?U(z}-IJJF>=jc`dFSyz1OCKW)DMs&r$pa_wn*eGdgW7OcZ{&emxq zmE$%@Ubp?oPqp)1M7OV?eRs(xcz#OLF6BD!6C;}V7Q>vjAyOw^&4;#kXgQxIKH@zT z)I^lwvl*^uc(+FIrwkv(loxS&3&TUvv4AFqHIfKGO!zT!SGi>D*Hnh!RHC8@shxo!1LYc zGeNnV;bdT29Fjxtk7>f^Ckb!z5`4DjWKZL363iHVJ%+>JS773a0NC~v7~ zVQjFDuw@R$c`IyeageZ;Hg;Exu*+=he1xzTe5r;%3#uVWc`Kvht;oMhY!H`&Gm2UX z+a%UdNKr%J?qjGkd@jRxM4sV}i9aaM1*}t_>+Ten#a;lonPCsZ??s*g7!JP(u%6)) zLoWlo1#th|0p*o|F~CmIqP)hfY2(W4-Pg`_s&4}PxQE~sNMrm(U+LDOpYnxZ*F*06 z1iT;$2>w@s=kcDmxy$KymeOPH_m6W zZJ=c8-U&G7dN1H3x%PFz4eo6Q6cBpjST!%HRDVWnY4N z{W-oD`@if~#6cT-o!aU07GhP-#y;=<*s=e|g{Kvi2){eotKdJSnkG_eOP* z*tb?SY9P+FY&y!eFUF4ToZ0I}(KB37UiDGWP zy@Wjy8~x_3WzIH{-0I|MqD^dI?6H`O(w;VvugGf`kGAu+ieI?e#b<16KVck8miZQj zj|>xhyqn5a#P&(w7a#T<>mC|oz$sch`I>Bp##2@vO zZdV4&^eu6MUuVhdLIij7iNm1JFZ>Z&yO7CSDa=-88IE~DC zUMFJe?eRB*|4F{XShxHw`~mD_e;&OXb)5-}GS3&q8Z{Q(2g=*kuLE8aeI3g`jI+y$ z(0zdKi{CGL)OUq{gxvknpNakI9kJhv!|I$&V^-l4rprumZodV^O*iGuY)yG61uimMCHhMGQ zU+ZpD``G@2Z2wN>*WNqSyVQE$D6A2Uy&v*_6Zwkz1=X*8Q*9RSt6Qe{)n~ZxSMO0* zxPPbis6)QL1MUd8Ts`XYh!1dopux38d5y2t)uWK#{zSda*M;=^)hk>B(5)YEm4|vn z(;nd>`%Jq2q89vT0iW!DA}H?(JPq)xh@Q79LVKI*e&s3gQIR4q1w7@B0=l9E|LXm+ zE2aD_M)3IzKNbG6E5n=_<%-ZfNM8f5crAG;C`;AvfN~u?iSd0*gYXg<`ITZR+^&Q-1n%zR!8CO zJf>{;lfVx5Ti8S1qF4%i8|U>ow_o%L3p3L`q91ng|+G*#bwIX%C*W&FU)k#v%%-yFf7H8s$=u;ZS>v4B0OT@95D%^8& zcU68Z^IVwH<&Wyf8yD5D;i9^Ixcs;RxPrJsxWabn9=z%F0}*rmL%h)SFwyE} z;}Xh1^BlBYmktN=)g6 zF-}Z_GF(U%bjI@gjBF-7+ndT}$5ZKJk~TcBuQ!{~a|K2x0H`;d+Io#_R%gYn_FZ~T zH#2E*Ku=9LJ=kN-=F$QkA`^o~Y61`aCd61KSJ0LdPoD(H=>@BRj!&7;(i$pe zvwO{h)9|BVQjLwzik7f>$_uNOh6)KD%;ZMQqQ!kNI?y|9WI&uaDUiB7!75c z(r28^WnmuFOLm!caHo1*=1eopGn^!F2-Yl_Us{%(vRUJ#J^`n*X#*CIi88^Gw;dQtIUYbXCI>S)?xS(KAJ9)2=@boIZsU*v z8iW+t^kTktOdOcX875UWlC`KStm)J-eP=3@ErL2^3}>ctsRE_1rLjEg;8Od&=0ql! z%1Rl%7;}Pq9rt}H)8c;YMZhS4u8$X|rgU?MX`E!I7%oV1R>$p7PXWi0<3%JLGwsm} zDT+%@j&wwHp~(>mg(vK+l5`SVqSe+|TgOPIP)Qz1nP}8b6Mb{iFpt&plLx^JG!$wx z2Xd2!Im5_}&YRZ~#2NrNZ&!$ds%Bqn?-8@lD z7Z%bXKWk>Drb}7eT?p%@nVPL(m$PTClu;VaL`U7@8BDRITsb;MEq!=e&u05i>1m?v zm@P<$VP}+67taGu9S9;u_Jl6>=H^rO7y!eW8}+@D2rU!(DctR8YGfL|1qYmR2)1JF zw)K%CBkaMHDG>wn7xNd#Et#T5Ktr1PX%LpC2PRAt$fprqsplz3h)Fxa0maB7nlqLs z(2vn`FoOhq+bqZ=Mz1L1vZjqGy6}_o~ z1!tVF+V@STas^`smM}TzBqm#$6&QJV27nAae~b&OmMA&p6iE!_^!zmz4PZI$fpDpk zfw<^=!qklfi@3;OQj}AfgYJ4nCT}O_EeypTea0|nZG_@sy4Rou8|Y%Esp}=o?r>hQ z^<<|E)IU8VQcj8|a$c689iUq?=$j(QnHid{b2P#C88fL&j+KKNm^KSG2#+g>D2 z$a{Gy)?P6s%E_QuFlgkb`}A}kXZVZne#^A5CL@5*cGA3FLA7Shf zx@kBD8PW^wq=YCpYUB^|~?Gud{`na8mTNSQ+V8qK4+SqgY^m1b+ZqnODu zmec$hDBY*fA@#a43|P3vh>0xft$0QITN> z!YbOdeLsZhqyVQaY3uBCXe8U)9JoHg1NH(OL2%GGiTeVPMV1>(Sp|4%cNecQxJwS` z6Kwfmdj-rn(!XW0ypduN63&Y70 zG=7IMs1R92)!@SNVA=q2UgRI3wioOq)P$g~N3Xg=& zC)G@k?TDHn^KRH$c%=$@zLc_^%35GMSUtQf4lzBvkFWO{@p2MnoxG2%|xb&!sXdsOI*uYF4Ti;AU z9K=X~0NUJ;d&11cY{3}`6#C2zhJcW3RXHvzhNw7Xw+Xg8kui-Nrw{cRX>MC-4sp_C zKSu(k%k-k1I$+?f!k!BE8OA=EiKXeZMB(Ts;GG5b#u5YKO=c2Qat}F2vy-sdW#pB0 zIb(Kjv4E9M&2TPf@ltCDKjSSq5YqEDr_>yxPMmboQlhlnkOA%qOsortX_Fi)~U!dP$=ci|}Q7J9Zop>!HPQjdY2yCdq%(#hpv$Xsu z%^h|(mvNl=^kuUpoIJ-lIbtu$={8o{`1jA`fr;RVz#>R=(n;lYJ46a;U5pA7)P$H3 zMc@Ub#)S@;##3`l?s&2>g&Y&K2|I5RIVSTQ0Gt8`mD52>;<*W)Ep3@E`vm7^SXvr+ z1>8w`0+bZeS&~jAhjAWCC85nYsIUz9aNxt`w^s85D4eo;Ru zTjOvbN5oR|N{j8ozzU)dSQaqDb*8EHNX^c-itL|C3k+PKJW|t4JBhT8v+*=w-sz=E zNwM|7W&cr2*5mLM*Oh%jwmmB?EA^=4WdAZiErO@2ceanRHLz}DpG)DIzL@gYJH0sw z&T+IN_24#fO5uKzJ!YEQYvL<&(_Dwne4Q+PXRW`V))rmN5$N9l*VUOG>5`kFdSePi&#xVg%oPVi;-YR;J7=Zvju)Bi@2W zd3=e#;CTK`2RT0{`_Ud#6Zks)x*DCg*)!dS!jqQ-Oaf^JHA`C{k+&wmh5k*2!^kyUys}rw`z*o zPoubJ!4X`|S1Y|z^Zo_K@x@D-d-{b9P$RCk!@ys#2YnG3a{(*Z0eqsev372Ob7v^* zRVw6dM?Ls~!TyB}l#Gy5D%1-?Doe>?mqyB**hxML7bPQlQeMv1F7df$}LP$Ug#?30fY zH8w+&1Ix_vWG@?&+$Y;>hs^RqwAWtJiq;3?*ajHNUn@^}@Nfjg>X79(YLH;@Cs;3D~;2&ujeT znJX7q=*cA&wTl+L{SU1!yB2BxQlFL=oEIxaE^}w7<*d(2&??8sRZ{Xp9v`K7L$=(G z?8rGK$87oJnFmuTg6gxo@uw;|3dqO=~J zrI@f};jP_jq0BvNwG`nD})ytFgxsmdQ^^@pWkXQxy$S)PVEb-a`;cXrNz zr<&&P+Q>b-hf10xDu)gQUwc7cv^#+t#(OU|$3vbvZG$e@QN#0gi8QSn+@3VoouDqK z1%vGNs{Q&padkbLn^7av$bes?^CD2I%Pt&?n7yCuQ**H|4sLd}SGPN>J zQutkXk}mfcbEbI0$kvRTiTS5~G~Rk@Ysy?iK!b$2Ti1a^ortF|d>>@Ejw&l1N_5xI z*4c45#rd5w-#&8A*=nwH+>RStV7?B?$@9;tG6&Vcd6x>sbtzW73v{==OE3Xzy2%-G z@{mcc!LFajV;gazW+hfAA1BW1jA?s6lBjM8N#8zCdbMfG63ewz9Z!la@?fhV9Z$B- z9ttz%-P=MAw{ZmcaK*8<#i?VJI@GR!j_;NSczhmn9Qkq$-#`DbxOO7K^>5W!-^&hc z$^90W5(6E`OW~X5wZ=I4401N$yAIybiF5~2n?)MmE~LPZR0r}WnX(1tbbOVSw*95$ zX6XU-OF3?77|O`}^-(zpM*)LQ#A!{5cK}60sPe5l=!@A`vF^YhE=z z8Xwg_ieInd$Aq!Dd&3&Ca1VNY%_@K5DZHetG)Gmux~)baE<$)@SuI_Zr>;&V@49GkmE!JU{nO=WH^6@w{*3DRW`F-3e%3V(HlxzQ#^0)7Y< zJG&o{(`d#hm?ZycuXDGn@R1Lkj-5U1p}vi{yo!nxn$qH3)Qp;Mi_bmZZa^xYf`O@1 zK*PUCgv_T9&uOw;N+TDGr?}$hbEjU9-Xw0M!mNDE#(YlNYs(|PvpRBI33QIv9Sfn- zpx3+1Bkh-hJ(1>K$+!hn4SBt>Xj81suY%V{wavuN9!Ir4q`=YS^Fs4`+|F~=6hRskr(&U9m zv@<^UmiXM;<8$w3kM@y)6-AYU?Je^574F)zusr9gK4J-Yy{_c~B4O9L_oG4|?suXB zDq9aHp`pJaep+UuU;rTK^{|i4eN0u^cb?3#;Os38UU!9K+8%>KUUHnzJgWYIyVu>j zpMRF%ra%4TRw$(sh!UXquPjztHH}A}s;R2h?9$@5JnTC4yg4VB~QUR*fHT zj?cn(Vxfo+L4X%h4=KF04@B(SSW2SsGPfK-+kZ?Q9erz{t!^UJ8EkxqDnb z{5o2(|AO~&g??VBHXOtg^`VMaz4&3grGU@NKNQ)eh$Zbq{Ugq60&VsiaaWz_YVUxA z$fDAtc>7lzLqKCxB;k}vfVEK(@zUEy3A}uvr_40{y)s33_yrsxuEwtkQFwY*H*$$x zMq8q*qibWLTxjp`Z~_7o2lXu88@pyWftL^D3#W#OdF(8zI2&8=jiyA#LdnGLk&%7t z##4BWJTWngx34qlb=gerSRyOm5=bQb1_#$9=!J6>!OIr>>Oumq?j^W6sXV<&l9E4( zNDLGb{O*sHm^F%KWoDvaBqsGjdYZIlafy_CDIud<3HjDVb~Z7I*P3M6-oBwkvgby; z6_KF7w9u9q?rux;W{u*+WHtrjfv&a$M+cXv&6r#Elz9`D}Ly-~lS^NPt08`G1W z8?NZwv|&Ss-nA(;*^N?{C}Q!#?}~{{_}%IO9q)V7OI`E==w;LNR?yb<>!+Y#al9Q$ zt)I$GcXoAju0P3LcLRD04p#NK^gGc9gtBKJjmVeNfINfiXgQzQ>+QeS`06vbN_o!T zzpwrr815TBdDoTSy8gQCj$3~DmD$~2|HCgyM(@@mqZVBF$g$k?mJLVt=JY-j?_VAH z+3Vl+^T*wOWLKuJyEuNt(esGoYwbP7DSFRGpE$Ch9gY~|HymLZqlc{_lW8$l0tpbV_dkycWyuF~ELt z0I2~S0S*Bl0NjZ*ectc>)p_SHn!uk+_wD8X*8zC-r;oX=<;{(AfIPtO3K^Wgic8VH zIK}52*@~U0{Jh!yJpRS^Fl}1wEljPX0`qi~z8l0i|J|EyYFttHkpZ3|X;=HuJVSa` zpAy)Q(#spka=pE1?+~}UN8DIC#o9sAA+3$iQo8xx%d{foPLdat-WuzoTwA|(vK#4N zbK2BawGGB=*YnOCcZo*;&(}W8lw37$tI%IP*onyO_)ebeQu%h)LOw+L$_Ij^KigBV zcjNDLlzg_2InwOQWi7=;`6P{RUxmzTtKKr9PbbQD*jE?pa0&RGBWP3F#c9n&)m?fw z1iydY3Ymj+0>sjK+3vI4vMGLxN7yw?gR0wh-i14Q3tDyXV}QhhcEqnk@hyAfK|TVq zSjIJ+(@SYPCXCZj36z};*yJD-hi~oQA2S5J=|Hzc`0BisNoQ1r| literal 18294 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 F0008<7yAGJ diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs deleted file mode 100644 index 40abe54..0000000 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs +++ /dev/null @@ -1,20 +0,0 @@ -๏ปฟ// 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.Augmented/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs deleted file mode 100644 index 5240b55..0000000 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs +++ /dev/null @@ -1,94 +0,0 @@ -๏ปฟ// 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(); - } - } -} diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryClient.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryClient.cs deleted file mode 100644 index 9fb1de7..0000000 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryClient.cs +++ /dev/null @@ -1,49 +0,0 @@ -๏ปฟ// 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; - - public TelemetryClient(string providerName, TelemetryOptions options, ICompatibleLogger logger = null) - { - _provider = TelemetryProviderFactory.Create(providerName, options, 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.Augmented/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs deleted file mode 100644 index 68852bb..0000000 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryIdentity.cs +++ /dev/null @@ -1,145 +0,0 @@ -๏ปฟ// 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.Augmented/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs deleted file mode 100644 index 4b057a9..0000000 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs +++ /dev/null @@ -1,15 +0,0 @@ -๏ปฟ// 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.Augmented/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs b/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs deleted file mode 100644 index 3454e15..0000000 --- a/WelsonJS.Augmented/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs +++ /dev/null @@ -1,34 +0,0 @@ -๏ปฟ// 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, ICompatibleLogger logger = null) - { - 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, logger); - - default: - throw new NotSupportedException( - "Unknown telemetry provider: " + provider - ); - } - } - } -} diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/TraceLogger.cs b/WelsonJS.Augmented/WelsonJS.Launcher/TraceLogger.cs index a74f168..f7fb06a 100644 --- a/WelsonJS.Augmented/WelsonJS.Launcher/TraceLogger.cs +++ b/WelsonJS.Augmented/WelsonJS.Launcher/TraceLogger.cs @@ -52,8 +52,11 @@ namespace WelsonJS.Launcher Trace.Listeners.Add(new TextWriterTraceListener(writer) { Name = "FileTraceListener", - TraceOutputOptions = TraceOptions.DateTime + TraceOutputOptions = TraceOptions.DateTime, + Filter = new EventTypeFilter(SourceLevels.Information) }); + + Trace.Listeners.Add(new ConsoleTraceListener()); } } catch (Exception ex) diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Augmented/WelsonJS.Launcher/WelsonJS.Launcher.csproj index b2ae16d..3a27074 100644 --- a/WelsonJS.Augmented/WelsonJS.Launcher/WelsonJS.Launcher.csproj +++ b/WelsonJS.Augmented/WelsonJS.Launcher/WelsonJS.Launcher.csproj @@ -128,12 +128,6 @@ GlobalSettingsForm.cs - - - - - - diff --git a/WelsonJS.Augmented/WelsonJS.Launcher/app.config b/WelsonJS.Augmented/WelsonJS.Launcher/app.config index c334ddd..b858537 100644 --- a/WelsonJS.Augmented/WelsonJS.Launcher/app.config +++ b/WelsonJS.Augmented/WelsonJS.Launcher/app.config @@ -25,12 +25,9 @@ - - - - +