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.
This commit is contained in:
Namhyeon, Go 2025-12-22 00:37:42 +09:00
parent f6b32d3c88
commit e50a966b89
15 changed files with 197 additions and 482 deletions

View File

@ -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
}
}
/// <summary>
/// Loads native modules associated with an assembly (explicit).
/// </summary>
@ -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<string>(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<string>(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<T>(Func<T> primaryAction, Func<Exception, bool> shouldFallback, Func<T> 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;
}
}
}

View File

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

View File

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

View File

@ -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 {
}
}
/// <summary>
/// 23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string IntegrityHashCurl {
get {
return ResourceManager.GetString("IntegrityHashCurl", resourceCulture);
}
}
/// <summary>
/// 과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
@ -397,42 +406,6 @@ namespace WelsonJS.Launcher.Properties {
}
}
/// <summary>
/// phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryApiKey {
get {
return ResourceManager.GetString("TelemetryApiKey", resourceCulture);
}
}
/// <summary>
/// https://us.i.posthog.com과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryBaseUrl {
get {
return ResourceManager.GetString("TelemetryBaseUrl", resourceCulture);
}
}
/// <summary>
/// true과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryEnabled {
get {
return ResourceManager.GetString("TelemetryEnabled", resourceCulture);
}
}
/// <summary>
/// posthog과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>
internal static string TelemetryProvider {
get {
return ResourceManager.GetString("TelemetryProvider", resourceCulture);
}
}
/// <summary>
/// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다.
/// </summary>

View File

@ -226,18 +226,6 @@
<data name="icon_editor_32" type="System.Resources.ResXFileRef, System.Windows.Forms">
<value>..\Resources\icon_editor_32.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a</value>
</data>
<data name="TelemetryApiKey" xml:space="preserve">
<value>phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8</value>
</data>
<data name="TelemetryBaseUrl" xml:space="preserve">
<value>https://us.i.posthog.com</value>
</data>
<data name="TelemetryProvider" xml:space="preserve">
<value>posthog</value>
</data>
<data name="TelemetryEnabled" xml:space="preserve">
<value>true</value>
</data>
<data name="AssemblyBaseUrl" xml:space="preserve">
<value>https://catswords.blob.core.windows.net/welsonjs/packages</value>
</data>
@ -247,4 +235,7 @@
<data name="AssemblyIntegrityUrl" xml:space="preserve">
<value>https://spare-yellow-cicada.myfilebase.com/ipfs/QmYL29Z7BRvE6dnL4HPKeNvJyNJrh2LnVhzo3k9bp3wBZf</value>
</data>
<data name="IntegrityHashCurl" xml:space="preserve">
<value>23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d</value>
</data>
</root>

View File

@ -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<string, object> properties = null,
CancellationToken cancellationToken = default
);
}
}

View File

@ -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<string, object> 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<string, object>
{
["api_key"] = _options.ApiKey,
["distinct_id"] = _options.DistinctId,
["event"] = eventName,
["properties"] = properties ?? new Dictionary<string, object>()
};
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();
}
}
}

View File

@ -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<string, object> properties = null,
CancellationToken cancellationToken = default)
{
return _provider.TrackEventAsync(eventName, properties, cancellationToken);
}
public Task TrackAppStartedAsync(string appName, string appVersion)
{
var props = new Dictionary<string, object>
{
{ "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();
}
}
}

View File

@ -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
{
/// <summary>
/// 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.
/// </summary>
public static string GetDistinctId()
{
var sources = new List<string>();
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 ""; }
}
/// <summary>
/// Retrieves BIOS UUID from Win32_ComputerSystemProduct.
/// Filters out invalid values (all zeros or all 'F').
/// </summary>
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;
}
/// <summary>
/// Retrieves OS-level UUID equivalent using Win32_OperatingSystem.SerialNumber.
/// This value is unique per Windows installation.
/// </summary>
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;
}
/// <summary>
/// Computes SHA-256 hex string for the given input text.
/// Returns null if hashing fails.
/// </summary>
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;
}
}
}
}

View File

@ -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;
}
}

View File

@ -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
);
}
}
}
}

View File

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

View File

@ -128,12 +128,6 @@
<DependentUpon>GlobalSettingsForm.cs</DependentUpon>
</Compile>
<Compile Include="ResourceServer.cs" />
<Compile Include="Telemetry\ITelemetryProvider.cs" />
<Compile Include="Telemetry\PosthogTelemetryProvider.cs" />
<Compile Include="Telemetry\TelemetryClient.cs" />
<Compile Include="Telemetry\TelemetryIdentity.cs" />
<Compile Include="Telemetry\TelemetryOptions.cs" />
<Compile Include="Telemetry\TelemetryProviderFactory.cs" />
<Compile Include="TraceLogger.cs" />
<Compile Include="WebSocketManager.cs" />
<EmbeddedResource Include="EnvForm.resx">

View File

@ -25,12 +25,9 @@
<add key="IpQueryApiKey2" value=""/>
<add key="IpQueryApiPrefix2" value="https://api.abuseipdb.com/api/v2/"/>
<add key="DateTimeFormat" value="yyyy-MM-dd HH:mm:ss"/>
<add key="TelemetryProvider" value="posthog"/>
<add key="TelemetryApiKey" value="phc_pmRHJ0aVEhtULRT4ilexwCjYpGtE9VYRhlA05fwiYt8"/>
<add key="TelemetryBaseUrl" value="https://us.i.posthog.com"/>
<add key="TelemetryEnabled" value="true"/>
<add key="AssemblyBaseUrl" value="https://catswords.blob.core.windows.net/welsonjs/packages"/>
<add key="AssemblyIntegrityUrl" value="https://spare-yellow-cicada.myfilebase.com/ipfs/QmYL29Z7BRvE6dnL4HPKeNvJyNJrh2LnVhzo3k9bp3wBZf"/>
<add key="IntegrityHashCurl" value="23b24c6a2dc39dbfd83522968d99096fc6076130a6de7a489bc0380cce89143d"/>
</appSettings>
<startup>
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>