feat: implement telemetry system for application usage tracking

- Added telemetry utility to capture application events and metrics.
- Integrated PostHog for event tracking with distinct user identification.
- Implemented telemetry initialization, event capturing, and shutdown procedures.

feat: add UV environment setup for Python management

- Created utilities to manage Python installation and configuration.
- Implemented network optimization checks for Python installation mirrors.
- Added functions to set up managed Python environments with error handling.

feat: enhance host API communication with token management

- Introduced host API token retrieval and management for secure requests.
- Updated host API fetch functions to include token in headers.
- Added support for creating event sources with authentication.

test: add comprehensive tests for gateway protocol and startup helpers

- Implemented unit tests for gateway protocol helpers, event dispatching, and state management.
- Added tests for startup recovery strategies and process policies.
- Ensured coverage for connection monitoring and restart governance logic.
This commit is contained in:
DEV_DSW
2026-04-23 17:21:57 +08:00
parent 655e7c51d2
commit 71bcc3b3c5
39 changed files with 5504 additions and 313 deletions

116
electron/utils/telemetry.ts Normal file
View File

@@ -0,0 +1,116 @@
import { randomUUID } from 'node:crypto';
import { app } from 'electron';
import axios from 'axios';
import logManager from '@electron/service/logger';
import configManager from '@electron/service/config-service';
const POSTHOG_API_KEY = 'phc_aGNegeJQP5FzNiF2rEoKqQbkuCpiiETMttplibXpB0n';
const POSTHOG_HOST = 'https://us.i.posthog.com';
const TELEMETRY_REQUEST_TIMEOUT_MS = 2_500;
const TELEMETRY_SHUTDOWN_TIMEOUT_MS = 1_500;
let telemetryEnabled = false;
let distinctId = '';
const pendingCaptures = new Set<Promise<void>>();
function getCommonProperties(): Record<string, unknown> {
return {
$app_version: app.getVersion(),
$os: process.platform,
os_tag: process.platform,
arch: process.arch,
};
}
function queueCapture(event: string, properties: Record<string, unknown>): void {
let capturePromise: Promise<void>;
const request = axios.post(
`${POSTHOG_HOST}/capture/`,
{
api_key: POSTHOG_API_KEY,
event,
properties: {
distinct_id: distinctId,
...properties,
},
},
{
headers: {
'Content-Type': 'application/json',
},
timeout: TELEMETRY_REQUEST_TIMEOUT_MS,
validateStatus: () => true,
},
).then((response) => {
if (response.status >= 400) {
logManager.debug(`Telemetry backend rejected event "${event}" with status ${response.status}`);
}
}).catch((error) => {
logManager.debug(`Failed to capture telemetry event "${event}":`, error);
}).finally(() => {
pendingCaptures.delete(capturePromise);
});
capturePromise = request.then(() => {});
pendingCaptures.add(capturePromise);
}
export async function initTelemetry(): Promise<void> {
telemetryEnabled = Boolean(configManager.get('telemetryEnabled' as never));
if (!telemetryEnabled) {
logManager.info('Telemetry is disabled; observability stays local-only');
return;
}
const storedDistinctId = configManager.get<string | null>('machineId' as never);
distinctId = storedDistinctId && storedDistinctId.trim()
? storedDistinctId
: randomUUID();
if (!storedDistinctId) {
configManager.set('machineId' as never, distinctId);
}
const hasReportedInstall = Boolean(configManager.get('hasReportedInstall' as never));
if (!hasReportedInstall) {
captureTelemetryEvent('app_installed');
configManager.set('hasReportedInstall' as never, true);
}
captureTelemetryEvent('app_opened');
}
export function trackMetric(event: string, properties: Record<string, unknown> = {}): void {
logManager.info(`[metric] ${event}`, properties);
}
export function captureTelemetryEvent(
event: string,
properties: Record<string, unknown> = {},
): void {
if (!telemetryEnabled || !distinctId) {
return;
}
const mergedProperties = {
...getCommonProperties(),
...properties,
};
queueCapture(event, mergedProperties);
}
export async function shutdownTelemetry(): Promise<void> {
telemetryEnabled = false;
if (pendingCaptures.size === 0) {
distinctId = '';
return;
}
const captures = Array.from(pendingCaptures);
await Promise.race([
Promise.allSettled(captures).then(() => undefined),
new Promise<void>((resolve) => {
setTimeout(resolve, TELEMETRY_SHUTDOWN_TIMEOUT_MS);
}),
]);
distinctId = '';
}