forked from eatvio/eatvio_app
259 lines
8.5 KiB
Dart
259 lines
8.5 KiB
Dart
import 'dart:async';
|
||
import 'dart:developer';
|
||
import 'dart:io';
|
||
import 'package:eatvio_app/core/app_config.dart';
|
||
import 'package:eatvio_app/core/app_initializer.dart';
|
||
import 'package:eatvio_app/core/notifications/notification_permission_requester.dart';
|
||
import 'package:eatvio_app/core/push/hms_client_error.dart';
|
||
import 'package:device_info_plus/device_info_plus.dart';
|
||
import 'package:dio/dio.dart';
|
||
import 'package:eatvio_app/core/locator_service.dart';
|
||
import 'package:eatvio_app/core/token_storage.dart';
|
||
import 'package:firebase_messaging/firebase_messaging.dart';
|
||
import 'package:flutter/services.dart';
|
||
import 'package:eatvio_app/core/error_reporting/error_reporter.dart';
|
||
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
|
||
|
||
class FirebaseApi {
|
||
final ErrorReporter _errorReporter;
|
||
final NotificationPermissionRequester _notificationPermissionRequester;
|
||
final _firebaseMessaging = FirebaseMessaging.instance;
|
||
|
||
FirebaseApi({
|
||
required ErrorReporter errorReporter,
|
||
NotificationPermissionRequester? notificationPermissionRequester,
|
||
}) : _errorReporter = errorReporter,
|
||
_notificationPermissionRequester = notificationPermissionRequester ?? NotificationPermissionRequester();
|
||
|
||
Future<String?> getToken() async {
|
||
final credentials = await _resolvePushCredentials();
|
||
return credentials.token;
|
||
}
|
||
|
||
Future<void> _requestNotificationPermission() async {
|
||
if (sl<AppConfig>().buildType != AppBuildType.prod) return;
|
||
try {
|
||
await _notificationPermissionRequester.requestOnce(
|
||
() => _firebaseMessaging.requestPermission(),
|
||
);
|
||
} catch (e) {
|
||
log('Failed to request Firebase messaging permission: $e');
|
||
}
|
||
}
|
||
|
||
Future<void> registerAnonymousPushToken() async {
|
||
try {
|
||
await _requestNotificationPermission();
|
||
final deviceInfo = DeviceInfoPlugin();
|
||
final credentials = await _resolvePushCredentials();
|
||
final token = credentials.token;
|
||
if (token == null || token.isEmpty) return;
|
||
await sl<Dio>().post(
|
||
'/api/anonymous_token',
|
||
data: {
|
||
'platform': credentials.platform,
|
||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||
'token': token,
|
||
},
|
||
).timeout(
|
||
const Duration(seconds: 10),
|
||
onTimeout: () {
|
||
throw TimeoutException('registerAnonymousPushToken timeout');
|
||
},
|
||
);
|
||
} catch (e, st) {
|
||
log(e.toString(), stackTrace: st);
|
||
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
||
}
|
||
}
|
||
|
||
Future<void> registerPushToken() async {
|
||
if (await TokenStorage.haveTokenInStorage()) {
|
||
try {
|
||
await _requestNotificationPermission();
|
||
final deviceInfo = DeviceInfoPlugin();
|
||
final credentials = await _resolvePushCredentials();
|
||
final token = credentials.token;
|
||
if (token == null || token.isEmpty) return;
|
||
await sl<Dio>().post(
|
||
'/api/profile/notification/token/register',
|
||
data: {
|
||
'platform': credentials.platform,
|
||
'hw_id': Platform.isIOS ? (await deviceInfo.iosInfo).identifierForVendor : (await deviceInfo.androidInfo).id,
|
||
'token': token,
|
||
},
|
||
).timeout(
|
||
const Duration(seconds: 10),
|
||
onTimeout: () {
|
||
throw TimeoutException('registerPushToken timeout');
|
||
},
|
||
);
|
||
if (Platform.isIOS) {
|
||
await _registerIosVoipPushToken();
|
||
}
|
||
} catch (e, st) {
|
||
log(e.toString(), stackTrace: st);
|
||
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||
// Не пробрасываем ошибку дальше, так как это не критичная операция
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
Future<void> unregisterPushTokens() async {
|
||
if (!await TokenStorage.haveTokenInStorage()) return;
|
||
try {
|
||
await unregisterPushToken();
|
||
} catch (_) {}
|
||
if (Platform.isIOS) {
|
||
try {
|
||
await _unregisterIosVoipPushToken();
|
||
} catch (_) {}
|
||
}
|
||
}
|
||
|
||
Future<void> unregisterPushToken() async {
|
||
if (!await TokenStorage.haveTokenInStorage()) return;
|
||
try {
|
||
final credentials = await _resolvePushCredentials();
|
||
final token = credentials.token ?? '';
|
||
if (token.isEmpty) return;
|
||
await sl<Dio>().post(
|
||
'/api/profile/notification/token/unregister',
|
||
data: {
|
||
'platform': credentials.platform,
|
||
'token': token,
|
||
},
|
||
).timeout(
|
||
const Duration(seconds: 10),
|
||
onTimeout: () {
|
||
throw TimeoutException('unregisterPushToken timeout');
|
||
},
|
||
);
|
||
} catch (e, st) {
|
||
log(e.toString(), stackTrace: st);
|
||
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||
}
|
||
}
|
||
|
||
Future<void> _registerIosVoipPushToken() async {
|
||
try {
|
||
final token = (await FlutterCallkitIncoming.getDevicePushTokenVoIP())?.trim();
|
||
if (token == null || token.isEmpty) return;
|
||
|
||
final deviceInfo = DeviceInfoPlugin();
|
||
await sl<Dio>().post(
|
||
'/api/profile/notification/token/voip/register',
|
||
data: {
|
||
'platform': 'ios',
|
||
'hw_id': (await deviceInfo.iosInfo).identifierForVendor,
|
||
'token': token,
|
||
},
|
||
).timeout(
|
||
const Duration(seconds: 10),
|
||
onTimeout: () {
|
||
throw TimeoutException('registerPushToken voip timeout');
|
||
},
|
||
);
|
||
} catch (e, st) {
|
||
log(e.toString(), stackTrace: st);
|
||
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||
}
|
||
}
|
||
|
||
Future<void> _unregisterIosVoipPushToken() async {
|
||
try {
|
||
final token = (await FlutterCallkitIncoming.getDevicePushTokenVoIP())?.trim() ?? '';
|
||
await sl<Dio>().post(
|
||
'/api/profile/notification/token/voip/unregister',
|
||
data: {
|
||
'token': token,
|
||
},
|
||
).timeout(
|
||
const Duration(seconds: 10),
|
||
onTimeout: () {
|
||
throw TimeoutException('unregisterPushToken voip timeout');
|
||
},
|
||
);
|
||
} catch (e, st) {
|
||
log(e.toString(), stackTrace: st);
|
||
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||
}
|
||
}
|
||
|
||
Future<({String? token, String platform})> _resolvePushCredentials() async {
|
||
if (Platform.isIOS) {
|
||
return (
|
||
token: await _getFcmToken(),
|
||
platform: pushPlatform(isIos: true, huaweiFamily: false, hmsAvailable: false, hasHmsToken: false),
|
||
);
|
||
}
|
||
|
||
final huaweiFamily = await _isHuaweiDevice();
|
||
if (huaweiFamily && await _isHmsAvailable()) {
|
||
final token = await _getHuaweiPushTokenNative().timeout(
|
||
const Duration(seconds: 5),
|
||
onTimeout: () => null,
|
||
);
|
||
if (token != null && token.isNotEmpty) {
|
||
return (
|
||
token: token,
|
||
platform: pushPlatform(isIos: false, huaweiFamily: true, hmsAvailable: true, hasHmsToken: true),
|
||
);
|
||
}
|
||
}
|
||
|
||
return (
|
||
token: await _getFcmToken(),
|
||
platform: pushPlatform(isIos: false, huaweiFamily: huaweiFamily, hmsAvailable: false, hasHmsToken: false),
|
||
);
|
||
}
|
||
|
||
Future<String?> _getFcmToken() {
|
||
return _firebaseMessaging.getToken().timeout(
|
||
const Duration(seconds: 5),
|
||
onTimeout: () => null,
|
||
);
|
||
}
|
||
|
||
Future<bool> _isHuaweiDevice() async {
|
||
if (!Platform.isAndroid) return false;
|
||
try {
|
||
final info = await DeviceInfoPlugin().androidInfo;
|
||
return isHuaweiFamilyBrand(brand: info.brand, manufacturer: info.manufacturer);
|
||
} catch (_) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Future<bool> _isHmsAvailable() async {
|
||
try {
|
||
const channel = MethodChannel('com.eatvioapp/hms_push');
|
||
final available = await channel.invokeMethod<bool>('isHmsAvailable').timeout(const Duration(seconds: 5));
|
||
return available ?? false;
|
||
} catch (e, st) {
|
||
log('HMS availability check failed: $e', stackTrace: st);
|
||
return false;
|
||
}
|
||
}
|
||
|
||
Future<String?> _getHuaweiPushTokenNative() async {
|
||
try {
|
||
const channel = MethodChannel('com.eatvioapp/hms_push');
|
||
final String? token = await channel.invokeMethod<String>('getHuaweiToken');
|
||
return token;
|
||
} on PlatformException catch (e, st) {
|
||
log(e.toString(), stackTrace: st);
|
||
if (!isHmsClientUnavailable(e)) {
|
||
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||
}
|
||
return null;
|
||
} catch (e, st) {
|
||
log(e.toString(), stackTrace: st);
|
||
await _errorReporter.report(e, st, origin: 'FirebaseApi');
|
||
return null;
|
||
}
|
||
}
|
||
}
|