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 @@
-
-
-
-
+