import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:dio/dio.dart'; import 'package:package_info_plus/package_info_plus.dart'; import '../../../../push/push_registration_store.dart'; import '../../../../push/push_registration_type.dart'; import '../../errors/marianumconnect_error.dart'; import '../../marianumconnect_api.dart'; import '../../marianumconnect_endpoint.dart'; import 'telemetry_device_id.dart'; /// Sends a telemetry heartbeat to MarianumConnect (`POST me/telemetry`) — /// upserts the stable install id, platform, app version and device info. Sent /// once on app start and again once push registration completes that session /// (so a fresh registration isn't under-reported until the next launch). /// Bearer-authenticated via the shared dio interceptor. Replaces the legacy /// mhsl.eu `server/userIndex/update` call. class TelemetryHeartbeat { final Dio _dio; TelemetryHeartbeat({Dio? dio}) : _dio = dio ?? MarianumConnectApi.dio(); /// Fire-and-forget: schedules a heartbeat and swallows any error, so a failed /// send never disrupts app start. Used from the app shell's initState and /// re-emitted once push registration completes (see `_MainState._syncPush`). static void report({required bool notificationsEnabled}) { unawaited( TelemetryHeartbeat() .send(notificationsEnabled: notificationsEnabled) .catchError((Object _) {}), ); } Future send({required bool notificationsEnabled}) async { try { final info = DeviceInfoPlugin(); final package = await PackageInfo.fromPlatform(); final deviceIdentifier = await TelemetryDeviceId.resolve(); final pushDeviceIdentifier = await const PushRegistrationStore() .deviceIdentifier(PushRegistrationType.general); var platform = 'unknown'; String? deviceModel; String? osVersion; var raw = {}; if (Platform.isAndroid) { platform = 'android'; final androidInfo = await info.androidInfo; deviceModel = androidInfo.model; osVersion = androidInfo.version.release; raw = androidInfo.data; } else if (Platform.isIOS) { platform = 'ios'; final appleInfo = await info.iosInfo; deviceModel = appleInfo.utsname.machine; osVersion = appleInfo.systemVersion; raw = appleInfo.data; } await _dio.post( MarianumConnectEndpoint.resolve('me/telemetry'), data: { 'deviceIdentifier': deviceIdentifier, // `pushDeviceIdentifier` reflects a *completed* registration and is // absent until it lands; `pushEnabled` carries the user's intent // (the notification toggle) so the backend can tell "user wants push" // apart from "registration not finished yet". 'pushDeviceIdentifier': ?pushDeviceIdentifier, 'pushEnabled': notificationsEnabled, 'platform': platform, 'appVersion': package.version, 'appBuild': int.tryParse(package.buildNumber), 'deviceModel': deviceModel, 'osVersion': osVersion, 'deviceInfo': jsonEncode(raw), }, ); } on DioException catch (e) { throw mapMarianumConnectError(e); } } }