feat: secure home credentials
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||
import 'package:geolocator/geolocator.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
@@ -10,6 +11,7 @@ const double geofenceThresholdMeters = 500.0;
|
||||
|
||||
/// Ключ в SharedPreferences: геофенс уже сработал, таск можно не запускать
|
||||
const String _firedKey = 'ignis_geofence_fired';
|
||||
const String _apiKeyPrefix = 'ignis_home_api_key_';
|
||||
|
||||
/// Имя задачи в workmanager
|
||||
const String geofenceTaskName = 'ignis_geofence_check';
|
||||
@@ -93,8 +95,12 @@ Future<bool> executeGeofenceCheck() async {
|
||||
// 4. Считаем расстояние
|
||||
final homeLat = (targetHome['latitude'] as num).toDouble();
|
||||
final homeLon = (targetHome['longitude'] as num).toDouble();
|
||||
final distMeters =
|
||||
_haversineMeters(pos.latitude, pos.longitude, homeLat, homeLon);
|
||||
final distMeters = _haversineMeters(
|
||||
pos.latitude,
|
||||
pos.longitude,
|
||||
homeLat,
|
||||
homeLon,
|
||||
);
|
||||
|
||||
if (distMeters <= geofenceThresholdMeters) {
|
||||
return true; // всё ещё рядом с домом
|
||||
@@ -102,7 +108,8 @@ Future<bool> executeGeofenceCheck() async {
|
||||
|
||||
// 5. Ушли за порог -- выключаем все группы
|
||||
final url = _normalizeUrl(targetHome['url'] as String);
|
||||
final apiKey = targetHome['apiKey'] as String;
|
||||
final apiKey = await _getHomeApiKey(targetHome);
|
||||
if (apiKey == null || apiKey.isEmpty) return true;
|
||||
final homeName = (targetHome['name'] ?? 'Дом') as String;
|
||||
|
||||
int groupCount = 0;
|
||||
@@ -122,7 +129,8 @@ Future<bool> executeGeofenceCheck() async {
|
||||
: '${(distMeters / 1000).toStringAsFixed(1)} км';
|
||||
await _showNotification(
|
||||
title: 'Свет выключен',
|
||||
body: '$homeName -- вы ушли на $distText. '
|
||||
body:
|
||||
'$homeName -- вы ушли на $distText. '
|
||||
'Выключено групп: $groupCount.',
|
||||
);
|
||||
|
||||
@@ -133,6 +141,19 @@ Future<bool> executeGeofenceCheck() async {
|
||||
}
|
||||
}
|
||||
|
||||
Future<String?> _getHomeApiKey(Map<String, dynamic> home) async {
|
||||
final id = home['id']?.toString();
|
||||
if (id == null || id.isEmpty) return null;
|
||||
|
||||
const secureStorage = FlutterSecureStorage();
|
||||
final secureKey = await secureStorage.read(key: '$_apiKeyPrefix$id');
|
||||
if (secureKey != null && secureKey.isNotEmpty) return secureKey;
|
||||
|
||||
// Backward compatibility: if the app has not run after migration yet,
|
||||
// old background tasks can still read the legacy key once.
|
||||
return home['apiKey']?.toString();
|
||||
}
|
||||
|
||||
/// Сбросить флаг "сработал" -- вызывать при включении геофенса
|
||||
/// или при возврате в приложение.
|
||||
Future<void> resetGeofenceFired() async {
|
||||
@@ -183,12 +204,14 @@ Future<void> _showNotification({
|
||||
|
||||
/// Выключить все группы на сервере. Возвращает кол-во выключенных.
|
||||
Future<int> _turnOffAllGroups(String baseUrl, String apiKey) async {
|
||||
final dio = Dio(BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
headers: {'X-API-Key': apiKey},
|
||||
connectTimeout: const Duration(seconds: 15),
|
||||
receiveTimeout: const Duration(seconds: 15),
|
||||
));
|
||||
final dio = Dio(
|
||||
BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
headers: {'X-API-Key': apiKey},
|
||||
connectTimeout: const Duration(seconds: 15),
|
||||
receiveTimeout: const Duration(seconds: 15),
|
||||
),
|
||||
);
|
||||
|
||||
try {
|
||||
// Получаем список групп
|
||||
@@ -212,14 +235,19 @@ Future<int> _turnOffAllGroups(String baseUrl, String apiKey) async {
|
||||
|
||||
// Выключаем каждую группу
|
||||
int success = 0;
|
||||
await Future.wait(groupIds.map((id) async {
|
||||
try {
|
||||
await dio.post('/control/group/$id', queryParameters: {'state': false});
|
||||
success++;
|
||||
} catch (_) {
|
||||
// Одна группа упала -- не останавливаем остальные
|
||||
}
|
||||
}));
|
||||
await Future.wait(
|
||||
groupIds.map((id) async {
|
||||
try {
|
||||
await dio.post(
|
||||
'/control/group/$id',
|
||||
queryParameters: {'state': false},
|
||||
);
|
||||
success++;
|
||||
} catch (_) {
|
||||
// Одна группа упала -- не останавливаем остальные
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
return success;
|
||||
} finally {
|
||||
@@ -236,12 +264,12 @@ String _normalizeUrl(String url) {
|
||||
}
|
||||
|
||||
/// Расстояние в метрах (Haversine)
|
||||
double _haversineMeters(
|
||||
double lat1, double lon1, double lat2, double lon2) {
|
||||
double _haversineMeters(double lat1, double lon1, double lat2, double lon2) {
|
||||
const earthRadiusM = 6371000.0;
|
||||
final dLat = _degToRad(lat2 - lat1);
|
||||
final dLon = _degToRad(lon2 - lon1);
|
||||
final a = math.sin(dLat / 2) * math.sin(dLat / 2) +
|
||||
final a =
|
||||
math.sin(dLat / 2) * math.sin(dLat / 2) +
|
||||
math.cos(_degToRad(lat1)) *
|
||||
math.cos(_degToRad(lat2)) *
|
||||
math.sin(dLon / 2) *
|
||||
|
||||
Reference in New Issue
Block a user