mirror of
https://github.com/tiddly-gittly/TidGi-Desktop.git
synced 2026-05-10 22:31:05 -07:00
feat(analytics): stable device UUID for user identity, default host analytics.tidgi.fun
- Add deviceId field to IAnalyticsSecretSettings; generated once via crypto.randomUUID() and persisted to analyticsSecrets on first use - Inject deviceId as user_id in every track payload so Rybbit groups all events from the same installation under one identified_user_id, independent of IP or User-Agent changes (proxy, network switch, etc.) - Set default analyticsHost to https://analytics.tidgi.fun so installations without explicit env-var override point at the right server
This commit is contained in:
parent
c82be64705
commit
d9ae611e01
2 changed files with 29 additions and 1 deletions
|
|
@ -1,5 +1,6 @@
|
|||
import { app } from 'electron';
|
||||
import { inject, injectable } from 'inversify';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
|
||||
import { container } from '@services/container';
|
||||
import type { IDatabaseService, ISettingFile } from '@services/database/interface';
|
||||
|
|
@ -11,6 +12,12 @@ import type { AnalyticsEventName, BuiltInAnalyticsEventName, IAnalyticsEventProp
|
|||
interface IAnalyticsSecretSettings {
|
||||
deviceFirstLaunchDate?: string;
|
||||
deviceLastLaunchDate?: string;
|
||||
/**
|
||||
* Stable random UUID generated once on first launch and persisted forever.
|
||||
* Used as Rybbit `user_id` so events from the same installation are always
|
||||
* grouped under the same user regardless of IP or User-Agent changes.
|
||||
*/
|
||||
deviceId?: string;
|
||||
}
|
||||
|
||||
interface ITrackPayload {
|
||||
|
|
@ -20,6 +27,8 @@ interface ITrackPayload {
|
|||
properties?: Record<string, string | number | boolean>;
|
||||
hostname: string;
|
||||
pathname: string;
|
||||
/** Stable per-installation UUID — maps to Rybbit identified_user_id */
|
||||
user_id?: string;
|
||||
}
|
||||
|
||||
const ANALYTICS_SETTINGS_KEY = 'analyticsSecrets';
|
||||
|
|
@ -266,6 +275,8 @@ export class AnalyticsService implements IAnalyticsService {
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const deviceId = this.getOrCreateDeviceId();
|
||||
|
||||
return {
|
||||
site_id: analyticsSiteId.trim(),
|
||||
type: 'custom_event',
|
||||
|
|
@ -273,10 +284,27 @@ export class AnalyticsService implements IAnalyticsService {
|
|||
properties,
|
||||
hostname: this.getAnalyticsHostname(analyticsHost),
|
||||
pathname: ANALYTICS_PATHNAME,
|
||||
user_id: deviceId,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the persisted device UUID, creating and storing it on first call.
|
||||
* Stored alongside other analytics secrets so it survives app updates.
|
||||
*/
|
||||
private getOrCreateDeviceId(): string {
|
||||
const databaseService = container.get<IDatabaseService>(serviceIdentifier.Database);
|
||||
const secrets = this.getAnalyticsSecrets(databaseService);
|
||||
if (secrets.deviceId) {
|
||||
return secrets.deviceId;
|
||||
}
|
||||
const newId = randomUUID();
|
||||
databaseService.setSetting(ANALYTICS_SETTINGS_KEY as keyof ISettingFile, { ...secrets, deviceId: newId } as never);
|
||||
void databaseService.immediatelyStoreSettingsToFile();
|
||||
return newId;
|
||||
}
|
||||
|
||||
private getAnalyticsTrackUrl(analyticsHost: string): string {
|
||||
const normalizedHost = analyticsHost.trim().replace(/\/+$/, '');
|
||||
return normalizedHost.endsWith('/api') ? `${normalizedHost}/track` : `${normalizedHost}/api/track`;
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ function getAnalyticsEnvironmentOverrides(): { analyticsApiKey: string; analytic
|
|||
analyticsSiteId: process.env.TIDGI_ANALYTICS_SITE_ID ?? 'test-site',
|
||||
};
|
||||
}
|
||||
return { analyticsApiKey: '', analyticsEnabled: true, analyticsHost: '', analyticsSiteId: '' };
|
||||
return { analyticsApiKey: '', analyticsEnabled: true, analyticsHost: 'https://analytics.tidgi.fun', analyticsSiteId: '' };
|
||||
}
|
||||
|
||||
const analyticsEnvironment = getAnalyticsEnvironmentOverrides();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue