diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs
index bad2a37..4518917 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Program.cs
@@ -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,17 @@ namespace WelsonJS.Launcher
logger: _logger,
requireSigned: requireSigned
);
+
+ // telemetry
+ var telemetryProvider = GetAppConfig("TelemetryProvider");
+ var telemetryOptions = new TelemetryOptions
+ {
+ ApiKey = GetAppConfig("TelemetryApiKey"),
+ BaseUrl = GetAppConfig("TelemetryBaseUrl"),
+ DistinctId = Environment.MachineName,
+ Disabled = string.Equals(GetAppConfig("TelemetryDisabled"), "true", StringComparison.OrdinalIgnoreCase)
+ };
+ _telemetryClient = new TelemetryClient(telemetryProvider, telemetryOptions, _logger);
}
[STAThread]
@@ -70,6 +83,9 @@ namespace WelsonJS.Launcher
return;
}
+ // send event to the telemetry server
+ _telemetryClient.TrackAppStartedAsync("WelsonJS.Launcher", "0.2.7.57");
+
// draw the main form
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
index d329414..1036a54 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.Designer.cs
@@ -378,6 +378,42 @@ 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);
+ }
+ }
+
+ ///
+ /// false과(와) 유사한 지역화된 문자열을 찾습니다.
+ ///
+ internal static string TelemetryDisabled {
+ get {
+ return ResourceManager.GetString("TelemetryDisabled", resourceCulture);
+ }
+ }
+
+ ///
+ /// posthog과(와) 유사한 지역화된 문자열을 찾습니다.
+ ///
+ internal static string TelemetryProvider {
+ get {
+ return ResourceManager.GetString("TelemetryProvider", resourceCulture);
+ }
+ }
+
///
/// 141.101.82.1과(와) 유사한 지역화된 문자열을 찾습니다.
///
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
index 7397892..e418c1c 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Properties/Resources.resx
@@ -229,4 +229,16 @@
..\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
+
+
+ false
+
\ No newline at end of file
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs
new file mode 100644
index 0000000..40abe54
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/ITelemetryProvider.cs
@@ -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 properties = null,
+ CancellationToken cancellationToken = default
+ );
+ }
+}
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs
new file mode 100644
index 0000000..1924ae7
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/PosthogTelemetryProvider.cs
@@ -0,0 +1,85 @@
+// 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 bool _disposed;
+
+ public PosthogTelemetryProvider(TelemetryOptions options)
+ {
+ _options = options ?? throw new ArgumentNullException(nameof(options));
+
+ 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) };
+ }
+
+ 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
+ {
+ await _httpClient.PostAsync(
+ url,
+ new StringContent(json, Encoding.UTF8, "application/json"),
+ cancellationToken
+ );
+ }
+ catch
+ {
+ // swallow for fire-and-forget telemetry
+ }
+ }
+
+ public void Dispose()
+ {
+ if (_disposed) return;
+ _disposed = true;
+ _httpClient.Dispose();
+ }
+ }
+}
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs
new file mode 100644
index 0000000..9c6dfdc
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryClient.cs
@@ -0,0 +1,51 @@
+// 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;
+ private readonly ICompatibleLogger _logger;
+
+ public TelemetryClient(string providerName, TelemetryOptions options, ICompatibleLogger logger = null)
+ {
+ _provider = TelemetryProviderFactory.Create(providerName, options);
+ _logger = 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.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs
new file mode 100644
index 0000000..4b057a9
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryOptions.cs
@@ -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;
+ }
+}
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs
new file mode 100644
index 0000000..b7a4978
--- /dev/null
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/Telemetry/TelemetryProviderFactory.cs
@@ -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)
+ {
+ 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);
+
+ default:
+ throw new NotSupportedException(
+ "Unknown telemetry provider: " + provider
+ );
+ }
+ }
+ }
+}
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
index 5bc8b61..d2deefc 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/WelsonJS.Launcher.csproj
@@ -128,6 +128,11 @@
GlobalSettingsForm.cs
+
+
+
+
+
diff --git a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
index b74b7fe..4f86bd8 100644
--- a/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
+++ b/WelsonJS.Toolkit/WelsonJS.Launcher/app.config
@@ -26,6 +26,10 @@
+
+
+
+