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:
116
electron/utils/telemetry.ts
Normal file
116
electron/utils/telemetry.ts
Normal 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 = '';
|
||||
}
|
||||
Reference in New Issue
Block a user