mirror of
https://github.com/krille-chan/fluffychat
synced 2024-10-05 15:12:44 +00:00
feat: Backup session and restore on database error
This commit is contained in:
parent
4d7d5bf181
commit
2a5c9d0a62
5 changed files with 178 additions and 18 deletions
|
@ -2386,11 +2386,28 @@
|
||||||
"decline": "Decline",
|
"decline": "Decline",
|
||||||
"thisDevice": "This device:",
|
"thisDevice": "This device:",
|
||||||
"initAppError": "An error occured while init the app",
|
"initAppError": "An error occured while init the app",
|
||||||
"databaseBuildErrorBody": "Unable to build the SQlite database. The app tries to use the legacy database for now. Please report this error to the developers at {url}",
|
"databaseBuildErrorBody": "Unable to build the SQlite database. The app tries to use the legacy database for now. Please report this error to the developers at {url}. The error message is: {error}",
|
||||||
"@databaseBuildErrorBody": {
|
"@databaseBuildErrorBody": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders": {
|
"placeholders": {
|
||||||
"url": {}
|
"url": {},
|
||||||
|
"error": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"sessionLostBody": "Your session is lost. Please report this error to the developers at {url}. The error message is: {error}",
|
||||||
|
"@sessionLostBody": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {
|
||||||
|
"url": {},
|
||||||
|
"error": {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"restoreSessionBody": "The app now tries to restore your session from the backup. Please report this error to the developers at {url}. The error message is: {error}",
|
||||||
|
"@restoreSessionBody": {
|
||||||
|
"type": "text",
|
||||||
|
"placeholders": {
|
||||||
|
"url": {},
|
||||||
|
"error": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -15,6 +15,7 @@ import 'package:universal_html/html.dart' as html;
|
||||||
import 'package:fluffychat/config/app_config.dart';
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
import 'package:fluffychat/utils/custom_http_client.dart';
|
import 'package:fluffychat/utils/custom_http_client.dart';
|
||||||
import 'package:fluffychat/utils/custom_image_resizer.dart';
|
import 'package:fluffychat/utils/custom_image_resizer.dart';
|
||||||
|
import 'package:fluffychat/utils/init_with_restore.dart';
|
||||||
import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart';
|
import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart';
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
import 'matrix_sdk_extensions/flutter_matrix_sdk_database_builder.dart';
|
import 'matrix_sdk_extensions/flutter_matrix_sdk_database_builder.dart';
|
||||||
|
@ -46,21 +47,17 @@ abstract class ClientManager {
|
||||||
if (initialize) {
|
if (initialize) {
|
||||||
await Future.wait(
|
await Future.wait(
|
||||||
clients.map(
|
clients.map(
|
||||||
(client) => client
|
(client) => client.initWithRestore(
|
||||||
.init(
|
onMigration: () {
|
||||||
waitForFirstSync: false,
|
final l10n = lookupL10n(PlatformDispatcher.instance.locale);
|
||||||
waitUntilLoadCompletedLoaded: false,
|
sendInitNotification(
|
||||||
onMigration: () {
|
l10n.databaseMigrationTitle,
|
||||||
final l10n = lookupL10n(PlatformDispatcher.instance.locale);
|
l10n.databaseMigrationBody,
|
||||||
sendInitNotification(
|
);
|
||||||
l10n.databaseMigrationTitle,
|
},
|
||||||
l10n.databaseMigrationBody,
|
).catchError(
|
||||||
);
|
(e, s) => Logs().e('Unable to initialize client', e, s),
|
||||||
},
|
),
|
||||||
)
|
|
||||||
.catchError(
|
|
||||||
(e, s) => Logs().e('Unable to initialize client', e, s),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
139
lib/utils/init_with_restore.dart
Normal file
139
lib/utils/init_with_restore.dart
Normal file
|
@ -0,0 +1,139 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:ui';
|
||||||
|
|
||||||
|
import 'package:flutter_gen/gen_l10n/l10n.dart';
|
||||||
|
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
|
||||||
|
import 'package:matrix/matrix.dart';
|
||||||
|
|
||||||
|
import 'package:fluffychat/config/app_config.dart';
|
||||||
|
import 'package:fluffychat/utils/client_manager.dart';
|
||||||
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
|
|
||||||
|
class SessionBackup {
|
||||||
|
final String? olmAccount;
|
||||||
|
final String accessToken;
|
||||||
|
final String userId;
|
||||||
|
final String homeserver;
|
||||||
|
final String? deviceId;
|
||||||
|
final String? deviceName;
|
||||||
|
|
||||||
|
const SessionBackup({
|
||||||
|
required this.olmAccount,
|
||||||
|
required this.accessToken,
|
||||||
|
required this.userId,
|
||||||
|
required this.homeserver,
|
||||||
|
required this.deviceId,
|
||||||
|
this.deviceName,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SessionBackup.fromJsonString(String json) =>
|
||||||
|
SessionBackup.fromJson(jsonDecode(json));
|
||||||
|
|
||||||
|
factory SessionBackup.fromJson(Map<String, dynamic> json) => SessionBackup(
|
||||||
|
olmAccount: json['olm_account'],
|
||||||
|
accessToken: json['access_token'],
|
||||||
|
userId: json['user_id'],
|
||||||
|
homeserver: json['homeserver'],
|
||||||
|
deviceId: json['device_id'],
|
||||||
|
deviceName: json['device_name'],
|
||||||
|
);
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() => {
|
||||||
|
'olm_account': olmAccount,
|
||||||
|
'access_token': accessToken,
|
||||||
|
'user_id': userId,
|
||||||
|
'homeserver': homeserver,
|
||||||
|
'device_id': deviceId,
|
||||||
|
if (deviceName != null) 'device_name': deviceName,
|
||||||
|
};
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => jsonEncode(toJson());
|
||||||
|
}
|
||||||
|
|
||||||
|
extension InitWithRestoreExtension on Client {
|
||||||
|
static Future<void> deleteSessionBackup(String clientName) async {
|
||||||
|
final storage = PlatformInfos.isMobile || PlatformInfos.isLinux
|
||||||
|
? const FlutterSecureStorage()
|
||||||
|
: null;
|
||||||
|
await storage?.delete(
|
||||||
|
key: '${AppConfig.applicationName}_session_backup_$clientName',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> initWithRestore({void Function()? onMigration}) async {
|
||||||
|
final storageKey =
|
||||||
|
'${AppConfig.applicationName}_session_backup_$clientName';
|
||||||
|
final storage = PlatformInfos.isMobile || PlatformInfos.isLinux
|
||||||
|
? const FlutterSecureStorage()
|
||||||
|
: null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
await init(
|
||||||
|
onMigration: onMigration,
|
||||||
|
waitForFirstSync: false,
|
||||||
|
waitUntilLoadCompletedLoaded: false,
|
||||||
|
);
|
||||||
|
if (isLogged()) {
|
||||||
|
final accessToken = this.accessToken;
|
||||||
|
final homeserver = this.homeserver?.toString();
|
||||||
|
final deviceId = deviceID;
|
||||||
|
final userId = userID;
|
||||||
|
final hasBackup = accessToken != null &&
|
||||||
|
homeserver != null &&
|
||||||
|
deviceId != null &&
|
||||||
|
userId != null;
|
||||||
|
assert(hasBackup);
|
||||||
|
if (hasBackup) {
|
||||||
|
Logs().v('Store session in backup');
|
||||||
|
storage?.write(
|
||||||
|
key: storageKey,
|
||||||
|
value: SessionBackup(
|
||||||
|
olmAccount: encryption?.pickledOlmAccount,
|
||||||
|
accessToken: accessToken,
|
||||||
|
deviceId: deviceId,
|
||||||
|
homeserver: homeserver,
|
||||||
|
deviceName: deviceName,
|
||||||
|
userId: userId,
|
||||||
|
).toString(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
final l10n = lookupL10n(PlatformDispatcher.instance.locale);
|
||||||
|
final sessionBackupString = await storage?.read(key: storageKey);
|
||||||
|
if (sessionBackupString == null) {
|
||||||
|
ClientManager.sendInitNotification(
|
||||||
|
l10n.initAppError,
|
||||||
|
l10n.sessionLostBody(AppConfig.newIssueUrl.toString(), e.toString()),
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
|
||||||
|
ClientManager.sendInitNotification(
|
||||||
|
l10n.initAppError,
|
||||||
|
l10n.restoreSessionBody(AppConfig.newIssueUrl.toString(), e.toString()),
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final sessionBackup = SessionBackup.fromJsonString(sessionBackupString);
|
||||||
|
await init(
|
||||||
|
newToken: sessionBackup.accessToken,
|
||||||
|
newOlmAccount: sessionBackup.olmAccount,
|
||||||
|
newDeviceID: sessionBackup.deviceId,
|
||||||
|
newDeviceName: sessionBackup.deviceName,
|
||||||
|
newHomeserver: Uri.tryParse(sessionBackup.homeserver),
|
||||||
|
newUserID: sessionBackup.userId,
|
||||||
|
waitForFirstSync: false,
|
||||||
|
waitUntilLoadCompletedLoaded: false,
|
||||||
|
onMigration: onMigration,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
ClientManager.sendInitNotification(
|
||||||
|
l10n.initAppError,
|
||||||
|
l10n.sessionLostBody(AppConfig.newIssueUrl.toString(), e.toString()),
|
||||||
|
);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -37,7 +37,10 @@ Future<DatabaseApi> flutterMatrixSdkDatabaseBuilder(Client client) async {
|
||||||
final l10n = lookupL10n(PlatformDispatcher.instance.locale);
|
final l10n = lookupL10n(PlatformDispatcher.instance.locale);
|
||||||
ClientManager.sendInitNotification(
|
ClientManager.sendInitNotification(
|
||||||
l10n.initAppError,
|
l10n.initAppError,
|
||||||
l10n.databaseBuildErrorBody(AppConfig.newIssueUrl.toString()),
|
l10n.databaseBuildErrorBody(
|
||||||
|
AppConfig.newIssueUrl.toString(),
|
||||||
|
e.toString(),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
return FlutterHiveCollectionsDatabase.databaseBuilder(client);
|
return FlutterHiveCollectionsDatabase.databaseBuilder(client);
|
||||||
|
|
|
@ -19,6 +19,7 @@ import 'package:universal_html/html.dart' as html;
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
import 'package:url_launcher/url_launcher_string.dart';
|
||||||
|
|
||||||
import 'package:fluffychat/utils/client_manager.dart';
|
import 'package:fluffychat/utils/client_manager.dart';
|
||||||
|
import 'package:fluffychat/utils/init_with_restore.dart';
|
||||||
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
import 'package:fluffychat/utils/localized_exception_extension.dart';
|
||||||
import 'package:fluffychat/utils/platform_infos.dart';
|
import 'package:fluffychat/utils/platform_infos.dart';
|
||||||
import 'package:fluffychat/utils/uia_request_manager.dart';
|
import 'package:fluffychat/utils/uia_request_manager.dart';
|
||||||
|
@ -314,6 +315,9 @@ class MatrixState extends State<Matrix> with WidgetsBindingObserver {
|
||||||
});
|
});
|
||||||
onLoginStateChanged[name] ??= c.onLoginStateChanged.stream.listen((state) {
|
onLoginStateChanged[name] ??= c.onLoginStateChanged.stream.listen((state) {
|
||||||
final loggedInWithMultipleClients = widget.clients.length > 1;
|
final loggedInWithMultipleClients = widget.clients.length > 1;
|
||||||
|
if (state == LoginState.loggedOut) {
|
||||||
|
InitWithRestoreExtension.deleteSessionBackup(name);
|
||||||
|
}
|
||||||
if (loggedInWithMultipleClients && state != LoginState.loggedIn) {
|
if (loggedInWithMultipleClients && state != LoginState.loggedIn) {
|
||||||
_cancelSubs(c.clientName);
|
_cancelSubs(c.clientName);
|
||||||
widget.clients.remove(c);
|
widget.clients.remove(c);
|
||||||
|
|
Loading…
Reference in a new issue