mirror of
https://github.com/gnh1201/welsonjs.git
synced 2026-01-18 15:26:47 +00:00
Merge pull request #357 from gnh1201/dev
Add a telemetry to WelsonJS Launcher
This commit is contained in:
commit
5c41a4bf72
|
|
@ -11,6 +11,7 @@ using System.IO;
|
|||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Windows.Forms;
|
||||
using WelsonJS.Launcher.Telemetry;
|
||||
|
||||
namespace WelsonJS.Launcher
|
||||
{
|
||||
|
|
@ -21,6 +22,7 @@ namespace WelsonJS.Launcher
|
|||
public static Mutex _mutex;
|
||||
public static ResourceServer _resourceServer;
|
||||
public static string _dateTimeFormat;
|
||||
public static TelemetryClient _telemetryClient;
|
||||
|
||||
static Program()
|
||||
{
|
||||
|
|
@ -32,10 +34,10 @@ namespace WelsonJS.Launcher
|
|||
|
||||
// load native libraries
|
||||
string appDataSubDirectory = "WelsonJS";
|
||||
bool requireSigned = string.Equals(
|
||||
GetAppConfig("NativeRequireSigned"),
|
||||
"true",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
bool requireSigned = string.Equals(
|
||||
GetAppConfig("NativeRequireSigned"),
|
||||
"true",
|
||||
StringComparison.OrdinalIgnoreCase);
|
||||
|
||||
NativeBootstrap.Init(
|
||||
dllNames: new[] { "ChakraCore.dll" },
|
||||
|
|
@ -43,6 +45,35 @@ namespace WelsonJS.Launcher
|
|||
logger: _logger,
|
||||
requireSigned: requireSigned
|
||||
);
|
||||
|
||||
// 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]
|
||||
|
|
@ -70,6 +101,13 @@ 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);
|
||||
|
|
|
|||
|
|
@ -32,5 +32,5 @@ using System.Runtime.InteropServices;
|
|||
// 모든 값을 지정하거나 아래와 같이 '*'를 사용하여 빌드 번호 및 수정 번호를
|
||||
// 기본값으로 할 수 있습니다.
|
||||
// [assembly: AssemblyVersion("1.0.*")]
|
||||
[assembly: AssemblyVersion("0.2.7.55")]
|
||||
[assembly: AssemblyFileVersion("0.2.7.55")]
|
||||
[assembly: AssemblyVersion("0.2.7.58")]
|
||||
[assembly: AssemblyFileVersion("0.2.7.58")]
|
||||
|
|
|
|||
|
|
@ -378,6 +378,42 @@ 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>
|
||||
|
|
|
|||
|
|
@ -229,4 +229,16 @@
|
|||
<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>
|
||||
</root>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
// ITelemetryProvider.cs
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
|
||||
// https://github.com/gnh1201/welsonjs
|
||||
//
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace WelsonJS.Launcher.Telemetry
|
||||
{
|
||||
public interface ITelemetryProvider
|
||||
{
|
||||
Task TrackEventAsync(
|
||||
string eventName,
|
||||
IDictionary<string, object> properties = null,
|
||||
CancellationToken cancellationToken = default
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,94 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,145 @@
|
|||
// TelemetryIdentity.cs
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
|
||||
// https://github.com/gnh1201/welsonjs
|
||||
//
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Management;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace WelsonJS.Launcher.Telemetry
|
||||
{
|
||||
public static class TelemetryIdentity
|
||||
{
|
||||
/// <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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// TelemetryOptions.cs
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
|
||||
// https://github.com/gnh1201/welsonjs
|
||||
//
|
||||
namespace WelsonJS.Launcher.Telemetry
|
||||
{
|
||||
public class TelemetryOptions
|
||||
{
|
||||
public string ApiKey { get; set; }
|
||||
public string BaseUrl { get; set; }
|
||||
public string DistinctId { get; set; }
|
||||
public bool Disabled { get; set; } = false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// TelemetryProviderFactory.cs
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
// SPDX-FileCopyrightText: 2025 Catswords OSS and WelsonJS Contributors
|
||||
// https://github.com/gnh1201/welsonjs
|
||||
//
|
||||
using System;
|
||||
|
||||
namespace WelsonJS.Launcher.Telemetry
|
||||
{
|
||||
public static class TelemetryProviderFactory
|
||||
{
|
||||
public static ITelemetryProvider Create(string provider, TelemetryOptions options, 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -80,6 +80,7 @@
|
|||
<Reference Include="System.Deployment" />
|
||||
<Reference Include="System.Drawing" />
|
||||
<Reference Include="System.IO.Compression.FileSystem" />
|
||||
<Reference Include="System.Management" />
|
||||
<Reference Include="System.Net.Http" />
|
||||
<Reference Include="System.Web" />
|
||||
<Reference Include="System.Windows.Forms" />
|
||||
|
|
@ -128,6 +129,12 @@
|
|||
<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">
|
||||
|
|
|
|||
|
|
@ -26,6 +26,10 @@
|
|||
<add key="IpQueryApiPrefix2" value="https://api.abuseipdb.com/api/v2/"/>
|
||||
<add key="DateTimeFormat" value="yyyy-MM-dd HH:mm:ss"/>
|
||||
<add key="NativeRequireSigned" value="false"/>
|
||||
<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"/>
|
||||
</appSettings>
|
||||
<startup>
|
||||
<supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.7.2"/>
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user