2023-07-20 15:48:55 -04:00
|
|
|
import 'dart:async';
|
2023-07-19 00:09:57 -04:00
|
|
|
|
|
|
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
2023-07-20 15:48:55 -04:00
|
|
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
2023-07-19 00:09:57 -04:00
|
|
|
|
|
|
|
import '../entities/entities.dart';
|
2023-09-26 18:46:02 -04:00
|
|
|
import '../proto/proto.dart' as proto;
|
2023-07-26 14:20:29 -04:00
|
|
|
import '../tools/tools.dart';
|
2023-09-26 18:46:02 -04:00
|
|
|
import '../veilid_init.dart';
|
2023-07-26 14:20:29 -04:00
|
|
|
import '../veilid_support/veilid_support.dart';
|
2023-07-22 23:29:10 -04:00
|
|
|
import 'logins.dart';
|
2023-07-19 00:09:57 -04:00
|
|
|
|
2023-07-20 15:48:55 -04:00
|
|
|
part 'local_accounts.g.dart';
|
2023-07-19 00:09:57 -04:00
|
|
|
|
2023-09-26 18:46:02 -04:00
|
|
|
const String veilidChatAccountKey = 'com.veilid.veilidchat';
|
|
|
|
|
2023-07-19 00:09:57 -04:00
|
|
|
// Local account manager
|
2023-07-20 15:48:55 -04:00
|
|
|
@riverpod
|
2023-07-21 21:25:27 -04:00
|
|
|
class LocalAccounts extends _$LocalAccounts
|
|
|
|
with AsyncTableDBBacked<IList<LocalAccount>> {
|
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
/// AsyncTableDBBacked
|
|
|
|
@override
|
2023-07-26 14:20:29 -04:00
|
|
|
String tableName() => 'local_account_manager';
|
2023-07-21 21:25:27 -04:00
|
|
|
@override
|
2023-07-26 14:20:29 -04:00
|
|
|
String tableKeyName() => 'local_accounts';
|
2023-07-21 21:25:27 -04:00
|
|
|
@override
|
2023-07-26 10:06:54 -04:00
|
|
|
IList<LocalAccount> valueFromJson(Object? obj) => obj != null
|
2023-07-21 21:25:27 -04:00
|
|
|
? IList<LocalAccount>.fromJson(
|
|
|
|
obj, genericFromJson(LocalAccount.fromJson))
|
|
|
|
: IList<LocalAccount>();
|
2023-07-26 10:06:54 -04:00
|
|
|
@override
|
|
|
|
Object? valueToJson(IList<LocalAccount> val) =>
|
|
|
|
val.toJson((la) => la.toJson());
|
2023-07-19 00:09:57 -04:00
|
|
|
|
2023-07-20 15:48:55 -04:00
|
|
|
/// Get all local account information
|
|
|
|
@override
|
2023-08-05 01:00:46 -04:00
|
|
|
FutureOr<IList<LocalAccount>> build() async {
|
|
|
|
try {
|
2023-09-26 18:46:02 -04:00
|
|
|
await eventualVeilid.future;
|
2023-08-05 01:00:46 -04:00
|
|
|
return await load();
|
|
|
|
} on Exception catch (e) {
|
|
|
|
log.error('Failed to load LocalAccounts table: $e');
|
|
|
|
return const IListConst([]);
|
|
|
|
}
|
|
|
|
}
|
2023-07-19 00:09:57 -04:00
|
|
|
|
2023-07-21 21:25:27 -04:00
|
|
|
//////////////////////////////////////////////////////////////
|
|
|
|
/// Mutators and Selectors
|
2023-07-19 00:09:57 -04:00
|
|
|
|
2023-07-22 23:29:10 -04:00
|
|
|
/// Reorder accounts
|
|
|
|
Future<void> reorderAccount(int oldIndex, int newIndex) async {
|
|
|
|
final localAccounts = state.requireValue;
|
2023-07-26 14:20:29 -04:00
|
|
|
final removedItem = Output<LocalAccount>();
|
2023-07-22 23:29:10 -04:00
|
|
|
final updated = localAccounts
|
|
|
|
.removeAt(oldIndex, removedItem)
|
|
|
|
.insert(newIndex, removedItem.value!);
|
|
|
|
await store(updated);
|
|
|
|
state = AsyncValue.data(updated);
|
2023-07-19 00:09:57 -04:00
|
|
|
}
|
|
|
|
|
2023-08-01 00:39:50 -04:00
|
|
|
/// Creates a new Account associated with master identity
|
|
|
|
/// Adds a logged-out LocalAccount to track its existence on this device
|
|
|
|
Future<LocalAccount> newLocalAccount(
|
|
|
|
{required IdentityMaster identityMaster,
|
|
|
|
required SecretKey identitySecret,
|
|
|
|
required String name,
|
|
|
|
required String title,
|
|
|
|
EncryptionKeyType encryptionKeyType = EncryptionKeyType.none,
|
|
|
|
String encryptionKey = ''}) async {
|
|
|
|
final localAccounts = state.requireValue;
|
|
|
|
|
2023-09-26 18:46:02 -04:00
|
|
|
// Add account with profile to DHT
|
|
|
|
await identityMaster.addAccountToIdentity(
|
|
|
|
identitySecret: identitySecret,
|
|
|
|
accountKey: veilidChatAccountKey,
|
|
|
|
createAccountCallback: (parent) async {
|
|
|
|
// Make empty contact list
|
|
|
|
final contactList = await (await DHTShortArray.create(parent: parent))
|
|
|
|
.scope((r) async => r.record.ownedDHTRecordPointer);
|
|
|
|
|
|
|
|
// Make empty contact invitation record list
|
|
|
|
final contactInvitationRecords =
|
|
|
|
await (await DHTShortArray.create(parent: parent))
|
|
|
|
.scope((r) async => r.record.ownedDHTRecordPointer);
|
|
|
|
|
|
|
|
// Make empty chat record list
|
|
|
|
final chatRecords = await (await DHTShortArray.create(parent: parent))
|
|
|
|
.scope((r) async => r.record.ownedDHTRecordPointer);
|
|
|
|
|
|
|
|
// Make account object
|
|
|
|
final account = proto.Account()
|
|
|
|
..profile = (proto.Profile()
|
|
|
|
..name = name
|
|
|
|
..title = title)
|
|
|
|
..contactList = contactList.toProto()
|
|
|
|
..contactInvitationRecords = contactInvitationRecords.toProto()
|
|
|
|
..chatList = chatRecords.toProto();
|
|
|
|
return account;
|
|
|
|
});
|
2023-08-01 00:39:50 -04:00
|
|
|
|
|
|
|
// Encrypt identitySecret with key
|
2023-08-02 21:09:28 -04:00
|
|
|
final identitySecretBytes = await encryptSecretToBytes(
|
|
|
|
secret: identitySecret,
|
2023-08-01 00:39:50 -04:00
|
|
|
cryptoKind: identityMaster.identityRecordKey.kind,
|
|
|
|
encryptionKey: encryptionKey,
|
|
|
|
encryptionKeyType: encryptionKeyType);
|
2023-07-19 00:09:57 -04:00
|
|
|
|
|
|
|
// Create local account object
|
2023-08-01 00:39:50 -04:00
|
|
|
// Does not contain the account key or its secret
|
|
|
|
// as that is not to be persisted, and only pulled from the identity key
|
|
|
|
// and optionally decrypted with the unlock password
|
2023-07-19 00:09:57 -04:00
|
|
|
final localAccount = LocalAccount(
|
|
|
|
identityMaster: identityMaster,
|
2023-08-01 00:39:50 -04:00
|
|
|
identitySecretBytes: identitySecretBytes,
|
2023-07-19 00:09:57 -04:00
|
|
|
encryptionKeyType: encryptionKeyType,
|
|
|
|
biometricsEnabled: false,
|
|
|
|
hiddenAccount: false,
|
2023-08-01 00:39:50 -04:00
|
|
|
name: name,
|
2023-07-19 00:09:57 -04:00
|
|
|
);
|
|
|
|
|
|
|
|
// Add local account object to internal store
|
2023-07-20 15:48:55 -04:00
|
|
|
final newLocalAccounts = localAccounts.add(localAccount);
|
2023-07-21 21:25:27 -04:00
|
|
|
await store(newLocalAccounts);
|
2023-07-20 15:48:55 -04:00
|
|
|
state = AsyncValue.data(newLocalAccounts);
|
2023-07-19 00:09:57 -04:00
|
|
|
|
|
|
|
// Return local account object
|
|
|
|
return localAccount;
|
|
|
|
}
|
2023-07-20 15:48:55 -04:00
|
|
|
|
2023-07-22 23:29:10 -04:00
|
|
|
/// Remove an account and wipe the messages for this account from this device
|
2023-08-01 00:39:50 -04:00
|
|
|
Future<bool> deleteLocalAccount(TypedKey accountMasterRecordKey) async {
|
2023-07-22 23:29:10 -04:00
|
|
|
final logins = ref.read(loginsProvider.notifier);
|
|
|
|
await logins.logout(accountMasterRecordKey);
|
|
|
|
|
|
|
|
final localAccounts = state.requireValue;
|
|
|
|
final updated = localAccounts.removeWhere(
|
|
|
|
(la) => la.identityMaster.masterRecordKey == accountMasterRecordKey);
|
|
|
|
await store(updated);
|
|
|
|
state = AsyncValue.data(updated);
|
|
|
|
|
2023-07-26 17:42:11 -04:00
|
|
|
// TO DO: wipe messages
|
2023-07-22 23:29:10 -04:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-07-20 15:48:55 -04:00
|
|
|
/// Import an account from another VeilidChat instance
|
|
|
|
|
|
|
|
/// Recover an account with the master identity secret
|
2023-08-01 00:39:50 -04:00
|
|
|
|
|
|
|
/// Delete an account from all devices
|
2023-07-19 00:09:57 -04:00
|
|
|
}
|
2023-07-28 20:36:05 -04:00
|
|
|
|
|
|
|
@riverpod
|
|
|
|
Future<LocalAccount?> fetchLocalAccount(FetchLocalAccountRef ref,
|
|
|
|
{required TypedKey accountMasterRecordKey}) async {
|
|
|
|
final localAccounts = await ref.watch(localAccountsProvider.future);
|
|
|
|
try {
|
|
|
|
return localAccounts.firstWhere(
|
|
|
|
(e) => e.identityMaster.masterRecordKey == accountMasterRecordKey);
|
|
|
|
} on Exception catch (e) {
|
|
|
|
if (e is StateError) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
}
|