fix(analytics): use Android ID and persist UUID fallbacks (#1624)

This commit is contained in:
Levi authored and GitHub committed 2026-10-28 22:33:13 +08:00
1 parent be0fb05edd
commit e0951de270
9 files changed
+61 -29

No files matched your search

@@ -196,7 +196,9 @@ class ExtraConfig {
List<String> createHistory;
bool runAsMenubarApp;
bool analyticsEnabled;
String analyticsClientId;
/// Persisted UUID fallback; IDs returned by platform APIs are not stored here.
String deviceId;
ExtraConfig({
this.themeMode = '',
@@ -214,7 +216,7 @@ class ExtraConfig {
this.createHistory = const [],
this.runAsMenubarApp = false,
this.analyticsEnabled = true,
this.analyticsClientId = '',
this.deviceId = '',
}) : windowState = windowState ?? WindowStateConfig();
factory ExtraConfig.fromJson(Map<String, dynamic>? json) =>
@@ -193,7 +193,7 @@ ExtraConfig _$ExtraConfigFromJson(Map<String, dynamic> json) => ExtraConfig(
createHistory: (json['createHistory'] as List<dynamic>?)?.map((e) => e as String).toList() ?? const [],
runAsMenubarApp: json['runAsMenubarApp'] as bool? ?? false,
analyticsEnabled: json['analyticsEnabled'] as bool? ?? true,
analyticsClientId: json['analyticsClientId'] as String? ?? '',
deviceId: json['deviceId'] as String? ?? '',
);
Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) => <String, dynamic>{
@@ -212,7 +212,7 @@ Map<String, dynamic> _$ExtraConfigToJson(ExtraConfig instance) => <String, dynam
'createHistory': instance.createHistory,
'runAsMenubarApp': instance.runAsMenubarApp,
'analyticsEnabled': instance.analyticsEnabled,
'analyticsClientId': instance.analyticsClientId,
'deviceId': instance.deviceId,
};
WindowStateConfig _$WindowStateConfigFromJson(Map<String, dynamic> json) => WindowStateConfig(
@@ -255,9 +255,9 @@ class AppRuntimeController extends AsyncNotifier<AppRuntimeState> {
Future<void> _initAnalytics(DownloaderConfig config) async {
if (!AnalyticsConfig.isConfigured || !config.extra.analyticsEnabled) return;
try {
await Analytics.instance.init(fallbackClientId: config.extra.analyticsClientId);
if (config.extra.analyticsClientId.isEmpty && Analytics.instance.clientId.isNotEmpty) {
config.extra.analyticsClientId = Analytics.instance.clientId;
final generatedClientId = await Analytics.instance.init(fallbackClientId: config.extra.deviceId);
if (generatedClientId != null) {
config.extra.deviceId = generatedClientId;
await ref.read(gopeedServiceProvider).putConfig(config);
}
unawaited(Analytics.instance.logAppOpen());
@@ -56,7 +56,7 @@ class SettingsController extends AsyncNotifier<SettingsState> {
..bookmarks = latest.extra.bookmarks
..createHistory = latest.extra.createHistory
..runAsMenubarApp = latest.extra.runAsMenubarApp
..analyticsClientId = latest.extra.analyticsClientId;
..deviceId = latest.extra.deviceId;
// The backend preserves protocol-owned results and validates the whole PUT.
await service.putConfig(config);
final saved = await service.getConfig();
+21 -16
View File
@@ -1,8 +1,10 @@
import 'dart:io';
import 'package:android_id/android_id.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:uuid/uuid.dart';
import 'log_util.dart';
import 'package_info.dart';
@@ -31,12 +33,23 @@ class Analytics {
String get clientId => _initialized ? _clientId : '';
Future<void> init({String fallbackClientId = ''}) async {
if (_initialized || !AnalyticsConfig.isConfigured) return;
_clientId = await _getDeviceId(fallbackClientId);
/// Returns a newly generated fallback ID for the caller to persist, if any.
Future<String?> init({String fallbackClientId = ''}) async {
if (_initialized || !AnalyticsConfig.isConfigured) return null;
final deviceId = await _getDeviceId();
String? generatedClientId;
if (deviceId != null && deviceId.isNotEmpty) {
_clientId = deviceId;
} else if (fallbackClientId.isNotEmpty) {
_clientId = fallbackClientId;
} else {
_clientId = const Uuid().v4();
generatedClientId = _clientId;
}
_sessionId = DateTime.now().millisecondsSinceEpoch ~/ 1000;
_dio = Dio(BaseOptions(connectTimeout: const Duration(seconds: 10), receiveTimeout: const Duration(seconds: 10)));
_initialized = true;
return generatedClientId;
}
Future<void> logAppOpen() => logEvent('app_open');
@@ -70,14 +83,14 @@ class Analytics {
}
}
Future<String> _getDeviceId(String fallbackClientId) async {
Future<String?> _getDeviceId() async {
if (kIsWeb) return null;
String? deviceId;
try {
final info = DeviceInfoPlugin();
if (kIsWeb) {
deviceId = null;
} else if (Platform.isAndroid) {
deviceId = (await info.androidInfo).id;
if (Platform.isAndroid) {
deviceId = await const AndroidId().getId();
} else if (Platform.isIOS) {
deviceId = (await info.iosInfo).identifierForVendor;
} else if (Platform.isMacOS) {
@@ -91,14 +104,6 @@ class Analytics {
logger.w('GA4Analytics: failed to read device id: $error');
}
if (deviceId == null || deviceId.isEmpty) {
deviceId = fallbackClientId;
if (deviceId.isEmpty) {
final random = DateTime.now().microsecondsSinceEpoch % 2147483647;
final timestamp = DateTime.now().millisecondsSinceEpoch ~/ 1000;
deviceId = '$random.$timestamp';
}
}
return deviceId;
}