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 b60a106..14cbbaa 100644 Binary files a/WelsonJS.Augmented/WelsonJS.Launcher/Resources/Catswords.Phantomizer.dll.gz and b/WelsonJS.Augmented/WelsonJS.Launcher/Resources/Catswords.Phantomizer.dll.gz differ 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 @@ - - - - +