mirror of
https://git.selfprivacy.org/kherel/selfprivacy.org.app.git
synced 2024-09-18 17:37:50 +00:00
feat: db versioning and better logging
This commit is contained in:
parent
78b026ed42
commit
85bc997776
4
.vscode/launch.json
vendored
4
.vscode/launch.json
vendored
|
@ -31,7 +31,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "debug (production)",
|
"name": "debug (production flavor)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"args": [
|
"args": [
|
||||||
|
@ -41,7 +41,7 @@
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "debug (nightly)",
|
"name": "debug (nightly flavor)",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"type": "dart",
|
"type": "dart",
|
||||||
"args": [
|
"args": [
|
||||||
|
|
18
lib/config/config.dart
Normal file
18
lib/config/config.dart
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
/// internal app configuration
|
||||||
|
const config = InternalConfig(
|
||||||
|
shouldDebugPrint: kDebugMode,
|
||||||
|
);
|
||||||
|
|
||||||
|
class InternalConfig {
|
||||||
|
const InternalConfig({
|
||||||
|
required this.shouldDebugPrint,
|
||||||
|
});
|
||||||
|
|
||||||
|
final bool shouldDebugPrint;
|
||||||
|
|
||||||
|
// example of other possible fields
|
||||||
|
// final String appVersion;
|
||||||
|
//
|
||||||
|
}
|
|
@ -1,7 +1,3 @@
|
||||||
import 'dart:convert';
|
|
||||||
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
import 'package:selfprivacy/logic/models/hive/backups_credential.dart';
|
||||||
|
@ -12,144 +8,210 @@ import 'package:selfprivacy/logic/models/hive/server_domain.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
|
import 'package:selfprivacy/logic/models/hive/server_provider_credential.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/user.dart';
|
import 'package:selfprivacy/logic/models/hive/user.dart';
|
||||||
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
|
import 'package:selfprivacy/logic/models/hive/wizards_data/server_installation_wizard_data.dart';
|
||||||
|
import 'package:selfprivacy/utils/app_logger.dart';
|
||||||
import 'package:selfprivacy/utils/platform_adapter.dart';
|
import 'package:selfprivacy/utils/platform_adapter.dart';
|
||||||
|
import 'package:selfprivacy/utils/secure_storage.dart';
|
||||||
|
|
||||||
class HiveConfig {
|
class HiveConfig {
|
||||||
|
static final log = const AppLogger(name: 'hive_config').log;
|
||||||
|
|
||||||
|
/// bump on schema changes
|
||||||
|
static const version = 2;
|
||||||
|
|
||||||
static Future<void> init() async {
|
static Future<void> init() async {
|
||||||
final String? storagePath = PlatformAdapter.storagePath;
|
final String? storagePath = PlatformAdapter.storagePath;
|
||||||
print('HiveConfig: Custom storage path: $storagePath');
|
log('set custom storage path to: "$storagePath"');
|
||||||
|
|
||||||
await Hive.initFlutter(storagePath);
|
await Hive.initFlutter(storagePath);
|
||||||
Hive.registerAdapter(UserAdapter());
|
|
||||||
Hive.registerAdapter(ServerHostingDetailsAdapter());
|
|
||||||
Hive.registerAdapter(ServerDomainAdapter());
|
|
||||||
Hive.registerAdapter(BackupsCredentialAdapter());
|
|
||||||
Hive.registerAdapter(ServerProviderVolumeAdapter());
|
|
||||||
Hive.registerAdapter(BackblazeBucketAdapter());
|
|
||||||
Hive.registerAdapter(ServerProviderCredentialAdapter());
|
|
||||||
Hive.registerAdapter(DnsProviderCredentialAdapter());
|
|
||||||
Hive.registerAdapter(ServerAdapter());
|
|
||||||
Hive.registerAdapter(DnsProviderTypeAdapter());
|
|
||||||
Hive.registerAdapter(ServerProviderTypeAdapter());
|
|
||||||
Hive.registerAdapter(UserTypeAdapter());
|
|
||||||
Hive.registerAdapter(BackupsProviderTypeAdapter());
|
|
||||||
Hive.registerAdapter(ServerInstallationWizardDataAdapter());
|
|
||||||
|
|
||||||
await Hive.openBox(BNames.appSettingsBox);
|
registerAdapters();
|
||||||
|
await decryptBoxes();
|
||||||
|
await performMigrations();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void registerAdapters() {
|
||||||
try {
|
try {
|
||||||
final HiveAesCipher cipher = HiveAesCipher(
|
Hive.registerAdapter(UserAdapter());
|
||||||
await getEncryptedKey(BNames.serverInstallationEncryptionKey),
|
Hive.registerAdapter(ServerHostingDetailsAdapter());
|
||||||
|
Hive.registerAdapter(ServerDomainAdapter());
|
||||||
|
Hive.registerAdapter(BackupsCredentialAdapter());
|
||||||
|
Hive.registerAdapter(ServerProviderVolumeAdapter());
|
||||||
|
Hive.registerAdapter(BackblazeBucketAdapter());
|
||||||
|
Hive.registerAdapter(ServerProviderCredentialAdapter());
|
||||||
|
Hive.registerAdapter(DnsProviderCredentialAdapter());
|
||||||
|
Hive.registerAdapter(ServerAdapter());
|
||||||
|
Hive.registerAdapter(DnsProviderTypeAdapter());
|
||||||
|
Hive.registerAdapter(ServerProviderTypeAdapter());
|
||||||
|
Hive.registerAdapter(UserTypeAdapter());
|
||||||
|
Hive.registerAdapter(BackupsProviderTypeAdapter());
|
||||||
|
Hive.registerAdapter(ServerInstallationWizardDataAdapter());
|
||||||
|
log('successfully registered every adapter');
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log(
|
||||||
|
'error registering adapters',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
);
|
);
|
||||||
|
|
||||||
await Hive.openBox(BNames.serverInstallationBox,
|
|
||||||
encryptionCipher: cipher);
|
|
||||||
await Hive.openBox(BNames.resourcesBox, encryptionCipher: cipher);
|
|
||||||
await Hive.openBox(BNames.wizardDataBox, encryptionCipher: cipher);
|
|
||||||
|
|
||||||
final Box resourcesBox = Hive.box(BNames.resourcesBox);
|
|
||||||
if (resourcesBox.isEmpty) {
|
|
||||||
final Box serverInstallationBox =
|
|
||||||
Hive.box(BNames.serverInstallationBox);
|
|
||||||
|
|
||||||
final String? serverProviderKey =
|
|
||||||
serverInstallationBox.get(BNames.hetznerKey);
|
|
||||||
final String? serverLocation =
|
|
||||||
serverInstallationBox.get(BNames.serverLocation);
|
|
||||||
final String? dnsProviderKey =
|
|
||||||
serverInstallationBox.get(BNames.cloudFlareKey);
|
|
||||||
final BackupsCredential? backblazeCredential =
|
|
||||||
serverInstallationBox.get(BNames.backblazeCredential);
|
|
||||||
final ServerDomain? serverDomain =
|
|
||||||
serverInstallationBox.get(BNames.serverDomain);
|
|
||||||
final ServerHostingDetails? serverDetails =
|
|
||||||
serverInstallationBox.get(BNames.serverDetails);
|
|
||||||
final BackblazeBucket? backblazeBucket =
|
|
||||||
serverInstallationBox.get(BNames.backblazeBucket);
|
|
||||||
final String? serverType =
|
|
||||||
serverInstallationBox.get(BNames.serverTypeIdentifier);
|
|
||||||
final ServerProviderType? serverProvider =
|
|
||||||
serverInstallationBox.get(BNames.serverProvider);
|
|
||||||
final DnsProviderType? dnsProvider =
|
|
||||||
serverInstallationBox.get(BNames.dnsProvider);
|
|
||||||
|
|
||||||
if (serverProviderKey != null &&
|
|
||||||
(serverProvider != null ||
|
|
||||||
(serverDetails != null &&
|
|
||||||
serverDetails.provider != ServerProviderType.unknown))) {
|
|
||||||
final ServerProviderCredential serverProviderCredential =
|
|
||||||
ServerProviderCredential(
|
|
||||||
tokenId: null,
|
|
||||||
token: serverProviderKey,
|
|
||||||
provider: serverProvider ?? serverDetails!.provider,
|
|
||||||
associatedServerIds:
|
|
||||||
serverDetails != null ? [serverDetails.id] : [],
|
|
||||||
);
|
|
||||||
|
|
||||||
await resourcesBox
|
|
||||||
.put(BNames.serverProviderTokens, [serverProviderCredential]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dnsProviderKey != null &&
|
|
||||||
(dnsProvider != null ||
|
|
||||||
(serverDomain != null &&
|
|
||||||
serverDomain.provider != DnsProviderType.unknown))) {
|
|
||||||
final DnsProviderCredential dnsProviderCredential =
|
|
||||||
DnsProviderCredential(
|
|
||||||
tokenId: null,
|
|
||||||
token: dnsProviderKey,
|
|
||||||
provider: dnsProvider ?? serverDomain!.provider,
|
|
||||||
associatedDomainNames:
|
|
||||||
serverDomain != null ? [serverDomain.domainName] : [],
|
|
||||||
);
|
|
||||||
|
|
||||||
await resourcesBox
|
|
||||||
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backblazeCredential != null) {
|
|
||||||
await resourcesBox
|
|
||||||
.put(BNames.backupsProviderTokens, [backblazeCredential]);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (backblazeBucket != null) {
|
|
||||||
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (serverDetails != null && serverDomain != null) {
|
|
||||||
await resourcesBox.put(BNames.servers, [
|
|
||||||
Server(
|
|
||||||
domain: serverDomain,
|
|
||||||
hostingDetails: serverDetails.copyWith(
|
|
||||||
serverLocation: serverLocation,
|
|
||||||
serverType: serverType,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} on PlatformException catch (e) {
|
|
||||||
print('HiveConfig: Error while opening boxes: $e');
|
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Uint8List> getEncryptedKey(final String encKey) async {
|
static Future<HiveAesCipher> getCipher() async {
|
||||||
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
List<int>? key = await SecureStorage.getKey();
|
||||||
try {
|
if (key == null) {
|
||||||
final bool hasEncryptionKey =
|
key = Hive.generateSecureKey();
|
||||||
await secureStorage.containsKey(key: encKey);
|
await SecureStorage.setKey(key);
|
||||||
if (!hasEncryptionKey) {
|
}
|
||||||
final List<int> key = Hive.generateSecureKey();
|
return HiveAesCipher(key);
|
||||||
await secureStorage.write(key: encKey, value: base64UrlEncode(key));
|
}
|
||||||
}
|
|
||||||
|
|
||||||
final String? string = await secureStorage.read(key: encKey);
|
static Future<void> decryptBoxes() async {
|
||||||
return base64Url.decode(string!);
|
try {
|
||||||
} on PlatformException catch (e) {
|
// load encrypted boxes into memory
|
||||||
print('HiveConfig: Error while getting encryption key: $e');
|
final HiveAesCipher cipher = await getCipher();
|
||||||
|
|
||||||
|
await Hive.openBox(
|
||||||
|
BNames.serverInstallationBox,
|
||||||
|
encryptionCipher: cipher,
|
||||||
|
);
|
||||||
|
await Hive.openBox(
|
||||||
|
BNames.resourcesBox,
|
||||||
|
encryptionCipher: cipher,
|
||||||
|
);
|
||||||
|
await Hive.openBox(
|
||||||
|
BNames.wizardDataBox,
|
||||||
|
encryptionCipher: cipher,
|
||||||
|
);
|
||||||
|
log('successfully decrypted boxes');
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log(
|
||||||
|
'error initializing encrypted boxes',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// migrations
|
||||||
|
|
||||||
|
static Future<void> performMigrations() async {
|
||||||
|
try {
|
||||||
|
// perform migration check
|
||||||
|
final localSettingsBox = await Hive.openBox(BNames.appSettingsBox);
|
||||||
|
|
||||||
|
// if it is an initial app launch, we do not need to perform any migrations
|
||||||
|
final savedVersion = localSettingsBox.isEmpty
|
||||||
|
? version
|
||||||
|
// if box was initialized, but database version was not introduced in
|
||||||
|
// it yet, it means that we have initial value
|
||||||
|
: await localSettingsBox.get(BNames.databaseVersion, defaultValue: 1);
|
||||||
|
|
||||||
|
/// launch migrations based on version
|
||||||
|
if (savedVersion < version) {
|
||||||
|
if (savedVersion < 2) {
|
||||||
|
await migrateFrom1To2();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// add new migrations here, like:
|
||||||
|
/// if (version < 3) {...}, etc.
|
||||||
|
|
||||||
|
/// update saved version after successfull migraions
|
||||||
|
await localSettingsBox.put(BNames.databaseVersion, version);
|
||||||
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log(
|
||||||
|
'error running db migrations',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// introduce and populate resourcesBox
|
||||||
|
static Future<void> migrateFrom1To2() async {
|
||||||
|
final Box resourcesBox = Hive.box(BNames.resourcesBox);
|
||||||
|
if (resourcesBox.isEmpty) {
|
||||||
|
final Box serverInstallationBox = Hive.box(BNames.serverInstallationBox);
|
||||||
|
|
||||||
|
final String? serverProviderKey =
|
||||||
|
serverInstallationBox.get(BNames.hetznerKey);
|
||||||
|
final String? serverLocation =
|
||||||
|
serverInstallationBox.get(BNames.serverLocation);
|
||||||
|
final String? dnsProviderKey =
|
||||||
|
serverInstallationBox.get(BNames.cloudFlareKey);
|
||||||
|
final BackupsCredential? backblazeCredential =
|
||||||
|
serverInstallationBox.get(BNames.backblazeCredential);
|
||||||
|
final ServerDomain? serverDomain =
|
||||||
|
serverInstallationBox.get(BNames.serverDomain);
|
||||||
|
final ServerHostingDetails? serverDetails =
|
||||||
|
serverInstallationBox.get(BNames.serverDetails);
|
||||||
|
final BackblazeBucket? backblazeBucket =
|
||||||
|
serverInstallationBox.get(BNames.backblazeBucket);
|
||||||
|
final String? serverType =
|
||||||
|
serverInstallationBox.get(BNames.serverTypeIdentifier);
|
||||||
|
final ServerProviderType? serverProvider =
|
||||||
|
serverInstallationBox.get(BNames.serverProvider);
|
||||||
|
final DnsProviderType? dnsProvider =
|
||||||
|
serverInstallationBox.get(BNames.dnsProvider);
|
||||||
|
|
||||||
|
if (serverProviderKey != null &&
|
||||||
|
(serverProvider != null ||
|
||||||
|
(serverDetails != null &&
|
||||||
|
serverDetails.provider != ServerProviderType.unknown))) {
|
||||||
|
final ServerProviderCredential serverProviderCredential =
|
||||||
|
ServerProviderCredential(
|
||||||
|
tokenId: null,
|
||||||
|
token: serverProviderKey,
|
||||||
|
provider: serverProvider ?? serverDetails!.provider,
|
||||||
|
associatedServerIds: serverDetails != null ? [serverDetails.id] : [],
|
||||||
|
);
|
||||||
|
|
||||||
|
await resourcesBox
|
||||||
|
.put(BNames.serverProviderTokens, [serverProviderCredential]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dnsProviderKey != null &&
|
||||||
|
(dnsProvider != null ||
|
||||||
|
(serverDomain != null &&
|
||||||
|
serverDomain.provider != DnsProviderType.unknown))) {
|
||||||
|
final DnsProviderCredential dnsProviderCredential =
|
||||||
|
DnsProviderCredential(
|
||||||
|
tokenId: null,
|
||||||
|
token: dnsProviderKey,
|
||||||
|
provider: dnsProvider ?? serverDomain!.provider,
|
||||||
|
associatedDomainNames:
|
||||||
|
serverDomain != null ? [serverDomain.domainName] : [],
|
||||||
|
);
|
||||||
|
|
||||||
|
await resourcesBox
|
||||||
|
.put(BNames.dnsProviderTokens, [dnsProviderCredential]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backblazeCredential != null) {
|
||||||
|
await resourcesBox
|
||||||
|
.put(BNames.backupsProviderTokens, [backblazeCredential]);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (backblazeBucket != null) {
|
||||||
|
await resourcesBox.put(BNames.backblazeBucket, backblazeBucket);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverDetails != null && serverDomain != null) {
|
||||||
|
await resourcesBox.put(BNames.servers, [
|
||||||
|
Server(
|
||||||
|
domain: serverDomain,
|
||||||
|
hostingDetails: serverDetails.copyWith(
|
||||||
|
serverLocation: serverLocation,
|
||||||
|
serverType: serverType,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log('successfully migration of db from 1 to 2 version');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Mappings for the different boxes and their keys
|
/// Mappings for the different boxes and their keys
|
||||||
|
@ -157,6 +219,9 @@ class BNames {
|
||||||
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
|
/// App settings box. Contains app settings like [darkThemeModeOn], [shouldShowOnboarding]
|
||||||
static String appSettingsBox = 'appSettings';
|
static String appSettingsBox = 'appSettings';
|
||||||
|
|
||||||
|
/// An integer with last saved version of the database
|
||||||
|
static String databaseVersion = 'databaseVersion';
|
||||||
|
|
||||||
/// A boolean field of [appSettingsBox] box.
|
/// A boolean field of [appSettingsBox] box.
|
||||||
static String darkThemeModeOn = 'isDarkModeOn';
|
static String darkThemeModeOn = 'isDarkModeOn';
|
||||||
|
|
||||||
|
@ -169,9 +234,6 @@ class BNames {
|
||||||
/// A string field
|
/// A string field
|
||||||
static String appLocale = 'appLocale';
|
static String appLocale = 'appLocale';
|
||||||
|
|
||||||
/// Encryption key to decrypt [serverInstallationBox] box.
|
|
||||||
static String serverInstallationEncryptionKey = 'key';
|
|
||||||
|
|
||||||
/// Server installation box. Contains server details and provider tokens.
|
/// Server installation box. Contains server details and provider tokens.
|
||||||
static String serverInstallationBox = 'appConfig';
|
static String serverInstallationBox = 'appConfig';
|
||||||
|
|
||||||
|
|
|
@ -1,19 +1,25 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:developer';
|
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio/io.dart';
|
import 'package:dio/io.dart';
|
||||||
import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
// import 'package:pretty_dio_logger/pretty_dio_logger.dart';
|
||||||
import 'package:selfprivacy/config/get_it_config.dart';
|
import 'package:selfprivacy/config/get_it_config.dart';
|
||||||
import 'package:selfprivacy/logic/models/console_log.dart';
|
import 'package:selfprivacy/logic/models/console_log.dart';
|
||||||
|
import 'package:selfprivacy/utils/app_logger.dart';
|
||||||
|
|
||||||
abstract class RestApiMap {
|
abstract class RestApiMap {
|
||||||
|
static final log = const AppLogger(name: 'rest_api_map').log;
|
||||||
|
|
||||||
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
Future<Dio> getClient({final BaseOptions? customOptions}) async {
|
||||||
final Dio dio = Dio(customOptions ?? (await options));
|
final Dio dio = Dio(customOptions ?? (await options));
|
||||||
if (hasLogger) {
|
if (hasLogger) {
|
||||||
dio.interceptors.add(PrettyDioLogger());
|
// dio.interceptors.add(
|
||||||
|
// PrettyDioLogger(
|
||||||
|
// logPrint: (final object) => log('$object'),
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
dio.interceptors.add(ConsoleInterceptor());
|
dio.interceptors.add(ConsoleInterceptor());
|
||||||
dio.httpClientAdapter = IOHttpClientAdapter(
|
dio.httpClientAdapter = IOHttpClientAdapter(
|
||||||
|
@ -29,11 +35,7 @@ abstract class RestApiMap {
|
||||||
dio.interceptors.add(
|
dio.interceptors.add(
|
||||||
InterceptorsWrapper(
|
InterceptorsWrapper(
|
||||||
onError: (final DioException e, final ErrorInterceptorHandler handler) {
|
onError: (final DioException e, final ErrorInterceptorHandler handler) {
|
||||||
print(e.requestOptions.path);
|
log('got dio error', error: e);
|
||||||
print(e.requestOptions.data);
|
|
||||||
|
|
||||||
print(e.message);
|
|
||||||
print(e.response);
|
|
||||||
|
|
||||||
return handler.next(e);
|
return handler.next(e);
|
||||||
},
|
},
|
||||||
|
@ -103,7 +105,6 @@ class ConsoleInterceptor extends InterceptorsWrapper {
|
||||||
final ErrorInterceptorHandler handler,
|
final ErrorInterceptorHandler handler,
|
||||||
) async {
|
) async {
|
||||||
final Response? response = err.response;
|
final Response? response = err.response;
|
||||||
log(err.toString());
|
|
||||||
|
|
||||||
addConsoleLog(
|
addConsoleLog(
|
||||||
ManualConsoleLog.warning(
|
ManualConsoleLog.warning(
|
||||||
|
|
26
lib/utils/app_logger.dart
Normal file
26
lib/utils/app_logger.dart
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
import 'dart:developer' as developer;
|
||||||
|
|
||||||
|
import 'package:selfprivacy/config/config.dart';
|
||||||
|
|
||||||
|
class AppLogger {
|
||||||
|
const AppLogger({required this.name});
|
||||||
|
final String name;
|
||||||
|
|
||||||
|
// TODO: research other possible options, which could support both
|
||||||
|
// throttling and output formatting
|
||||||
|
void log(
|
||||||
|
final String message, {
|
||||||
|
final Object? error,
|
||||||
|
final StackTrace? stackTrace,
|
||||||
|
}) {
|
||||||
|
if (config.shouldDebugPrint) {
|
||||||
|
developer.log(
|
||||||
|
message,
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
time: DateTime.now(),
|
||||||
|
name: name,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
lib/utils/secure_storage.dart
Normal file
48
lib/utils/secure_storage.dart
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:selfprivacy/utils/app_logger.dart';
|
||||||
|
|
||||||
|
class SecureStorage {
|
||||||
|
static final log = const AppLogger(name: 'secure_storage').log;
|
||||||
|
|
||||||
|
static const FlutterSecureStorage secureStorage = FlutterSecureStorage();
|
||||||
|
static String keyName = 'key';
|
||||||
|
|
||||||
|
static Future<Uint8List?> getKey() async {
|
||||||
|
try {
|
||||||
|
final bool hasEncryptionKey =
|
||||||
|
await secureStorage.containsKey(key: keyName);
|
||||||
|
if (!hasEncryptionKey) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
final String? string = await secureStorage.read(key: keyName);
|
||||||
|
log('successfully got encryption key: $string');
|
||||||
|
|
||||||
|
return base64Url.decode(string!);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log(
|
||||||
|
'error reading encryption key',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<void> setKey(final List<int> key) async {
|
||||||
|
try {
|
||||||
|
final value = base64UrlEncode(key);
|
||||||
|
await secureStorage.write(key: keyName, value: value);
|
||||||
|
log('successfully saved encryption key: $value');
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
log(
|
||||||
|
'error saving new encryption key',
|
||||||
|
error: error,
|
||||||
|
stackTrace: stackTrace,
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue