more refactor work

This commit is contained in:
Christien Rioux 2024-01-18 19:44:15 -05:00
parent 0a922e97b6
commit 6e8ba551ad
10 changed files with 295 additions and 213 deletions

View file

@ -0,0 +1,16 @@
import 'dart:async';
import 'package:veilid_support/veilid_support.dart';
import '../../proto/proto.dart' as proto;
class AccountRecordCubit extends DefaultDHTRecordCubit<proto.Account> {
AccountRecordCubit({
required super.record,
}) : super(decodeState: proto.Account.fromBuffer);
@override
Future<void> close() async {
await super.close();
}
}

View file

@ -1,3 +1,4 @@
export 'account_record_cubit.dart';
export 'active_user_login_cubit/active_user_login_cubit.dart'; export 'active_user_login_cubit/active_user_login_cubit.dart';
export 'local_accounts_cubit/local_accounts_cubit.dart'; export 'local_accounts_cubit/local_accounts_cubit.dart';
export 'user_logins_cubit/user_logins_cubit.dart'; export 'user_logins_cubit/user_logins_cubit.dart';

View file

@ -1,5 +1,6 @@
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:veilid_support/veilid_support.dart';
import 'active_account_info.dart';
enum AccountInfoStatus { enum AccountInfoStatus {
noAccount, noAccount,
@ -13,10 +14,10 @@ class AccountInfo {
const AccountInfo({ const AccountInfo({
required this.status, required this.status,
required this.active, required this.active,
this.accountRecord, required this.activeAccountInfo,
}); });
final AccountInfoStatus status; final AccountInfoStatus status;
final bool active; final bool active;
final DHTRecord? accountRecord; final ActiveAccountInfo? activeAccountInfo;
} }

View file

@ -82,66 +82,40 @@ class AccountRepository {
return userLogins[idx]; return userLogins[idx];
} }
AccountInfo getAccountInfo({required TypedKey accountMasterRecordKey}) { AccountInfo? getAccountInfo({TypedKey? accountMasterRecordKey}) {
// Get active user if we have one
if (accountMasterRecordKey == null) {
final activeUserLogin = getActiveUserLogin();
if (activeUserLogin == null) {
// No user logged in
return null;
}
accountMasterRecordKey = activeUserLogin;
}
// Get which local account we want to fetch the profile for // Get which local account we want to fetch the profile for
final localAccount = final localAccount =
fetchLocalAccount(accountMasterRecordKey: accountMasterRecordKey); fetchLocalAccount(accountMasterRecordKey: accountMasterRecordKey);
if (localAccount == null) { if (localAccount == null) {
// Local account does not exist // Local account does not exist
return const AccountInfo( return const AccountInfo(
status: AccountInfoStatus.noAccount, active: false); status: AccountInfoStatus.noAccount,
active: false,
activeAccountInfo: null);
} }
// See if we've logged into this account or if it is locked // See if we've logged into this account or if it is locked
final activeUserLogin = getActiveUserLogin(); final activeUserLogin = getActiveUserLogin();
final active = activeUserLogin == accountMasterRecordKey; final active = activeUserLogin == accountMasterRecordKey;
final login = final userLogin =
fetchUserLogin(accountMasterRecordKey: accountMasterRecordKey); fetchUserLogin(accountMasterRecordKey: accountMasterRecordKey);
if (login == null) {
// Account was locked
return AccountInfo(
status: AccountInfoStatus.accountLocked, active: active);
}
// Pull the account DHT key, decode it and return it
final pool = DHTRecordPool.instance;
final accountRecord =
pool.getOpenedRecord(login.accountRecordInfo.accountRecord.recordKey);
if (accountRecord == null) {
// Account could not be read or decrypted from DHT
return AccountInfo(
status: AccountInfoStatus.accountInvalid, active: active);
}
// Got account, decrypted and decoded
return AccountInfo(
status: AccountInfoStatus.accountReady,
active: active,
accountRecord: accountRecord);
}
Future<ActiveAccountInfo?> fetchActiveAccountInfo() async {
// See if we've logged into this account or if it is locked
final activeUserLogin = getActiveUserLogin();
if (activeUserLogin == null) {
// No user logged in
return null;
}
// Get the user login
final userLogin = fetchUserLogin(accountMasterRecordKey: activeUserLogin);
if (userLogin == null) { if (userLogin == null) {
// Account was locked // Account was locked
return null; return AccountInfo(
} status: AccountInfoStatus.accountLocked,
active: active,
// Get which local account we want to fetch the profile for activeAccountInfo: null);
final localAccount =
fetchLocalAccount(accountMasterRecordKey: activeUserLogin);
if (localAccount == null) {
// Local account does not exist
return null;
} }
// Pull the account DHT key, decode it and return it // Pull the account DHT key, decode it and return it
@ -149,14 +123,21 @@ class AccountRepository {
final accountRecord = pool final accountRecord = pool
.getOpenedRecord(userLogin.accountRecordInfo.accountRecord.recordKey); .getOpenedRecord(userLogin.accountRecordInfo.accountRecord.recordKey);
if (accountRecord == null) { if (accountRecord == null) {
return null; // Account could not be read or decrypted from DHT
return AccountInfo(
status: AccountInfoStatus.accountInvalid,
active: active,
activeAccountInfo: null);
} }
// Got account, decrypted and decoded // Got account, decrypted and decoded
return ActiveAccountInfo( return AccountInfo(
status: AccountInfoStatus.accountReady,
active: active,
activeAccountInfo: ActiveAccountInfo(
localAccount: localAccount, localAccount: localAccount,
userLogin: userLogin, userLogin: userLogin,
accountRecord: accountRecord, accountRecord: accountRecord),
); );
} }
@ -411,12 +392,11 @@ class AccountRepository {
// For all user logins if they arent open yet // For all user logins if they arent open yet
final activeLogins = await _activeLogins.get(); final activeLogins = await _activeLogins.get();
for (final userLogin in activeLogins.userLogins) { for (final userLogin in activeLogins.userLogins) {
//// Account record key /////////////////////////////
final accountRecordKey = final accountRecordKey =
userLogin.accountRecordInfo.accountRecord.recordKey; userLogin.accountRecordInfo.accountRecord.recordKey;
final existingAccountRecord = pool.getOpenedRecord(accountRecordKey); final existingAccountRecord = pool.getOpenedRecord(accountRecordKey);
if (existingAccountRecord != null) { if (existingAccountRecord == null) {
continue;
}
final localAccount = fetchLocalAccount( final localAccount = fetchLocalAccount(
accountMasterRecordKey: userLogin.accountMasterRecordKey); accountMasterRecordKey: userLogin.accountMasterRecordKey);
@ -426,9 +406,7 @@ class AccountRepository {
parent: localAccount!.identityMaster.identityRecordKey); parent: localAccount!.identityMaster.identityRecordKey);
// Watch the record's only (default) key // Watch the record's only (default) key
await record.watch(); await record.watch();
}
// .scope(
// (accountRec) => accountRec.getProtobuf(proto.Account.fromBuffer));
} }
} }
@ -437,6 +415,7 @@ class AccountRepository {
final activeLogins = await _activeLogins.get(); final activeLogins = await _activeLogins.get();
for (final userLogin in activeLogins.userLogins) { for (final userLogin in activeLogins.userLogins) {
//// Account record key /////////////////////////////
final accountRecordKey = final accountRecordKey =
userLogin.accountRecordInfo.accountRecord.recordKey; userLogin.accountRecordInfo.accountRecord.recordKey;
final accountRecord = pool.getOpenedRecord(accountRecordKey); final accountRecord = pool.getOpenedRecord(accountRecordKey);

View file

@ -0,0 +1,141 @@
//////////////////////////////////////////////////
///
class ValidContactInvitation {
ValidContactInvitation._(
{required ContactInvitationListManager contactInvitationManager,
required proto.SignedContactInvitation signedContactInvitation,
required proto.ContactInvitation contactInvitation,
required TypedKey contactRequestInboxKey,
required proto.ContactRequest contactRequest,
required proto.ContactRequestPrivate contactRequestPrivate,
required IdentityMaster contactIdentityMaster,
required KeyPair writer})
: _contactInvitationManager = contactInvitationManager,
_signedContactInvitation = signedContactInvitation,
_contactInvitation = contactInvitation,
_contactRequestInboxKey = contactRequestInboxKey,
_contactRequest = contactRequest,
_contactRequestPrivate = contactRequestPrivate,
_contactIdentityMaster = contactIdentityMaster,
_writer = writer;
Future<AcceptedContact?> accept() async {
final pool = await DHTRecordPool.instance();
final activeAccountInfo = _contactInvitationManager._activeAccountInfo;
try {
// Ensure we don't delete this if we're trying to chat to self
final isSelf = _contactIdentityMaster.identityPublicKey ==
activeAccountInfo.localAccount.identityMaster.identityPublicKey;
final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
return (await pool.openWrite(_contactRequestInboxKey, _writer,
parent: accountRecordKey))
// ignore: prefer_expression_function_bodies
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// Create local conversation key for this
// contact and send via contact response
return createConversation(
activeAccountInfo: activeAccountInfo,
remoteIdentityPublicKey:
_contactIdentityMaster.identityPublicTypedKey(),
callback: (localConversation) async {
final contactResponse = proto.ContactResponse()
..accept = true
..remoteConversationRecordKey = localConversation.key.toProto()
..identityMasterRecordKey = activeAccountInfo
.localAccount.identityMaster.masterRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final cs = await pool.veilid
.getCryptoSystem(_contactRequestInboxKey.kind);
final identitySignature = await cs.sign(
activeAccountInfo
.localAccount.identityMaster.identityPublicKey,
activeAccountInfo.userLogin.identitySecret.value,
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the acceptance to the inbox
if (await contactRequestInbox.tryWriteProtobuf(
proto.SignedContactResponse.fromBuffer,
signedContactResponse,
subkey: 1) !=
null) {
throw Exception('failed to accept contact invitation');
}
return AcceptedContact(
profile: _contactRequestPrivate.profile,
remoteIdentity: _contactIdentityMaster,
remoteConversationRecordKey: proto.TypedKeyProto.fromProto(
_contactRequestPrivate.chatRecordKey),
localConversationRecordKey: localConversation.key,
);
});
});
} on Exception catch (e) {
log.debug('exception: $e', e);
return null;
}
}
Future<bool> reject() async {
final pool = await DHTRecordPool.instance();
final activeAccountInfo = _contactInvitationManager._activeAccountInfo;
// Ensure we don't delete this if we're trying to chat to self
final isSelf = _contactIdentityMaster.identityPublicKey ==
activeAccountInfo.localAccount.identityMaster.identityPublicKey;
final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
return (await pool.openWrite(_contactRequestInboxKey, _writer,
parent: accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
final cs =
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
final contactResponse = proto.ContactResponse()
..accept = false
..identityMasterRecordKey = activeAccountInfo
.localAccount.identityMaster.masterRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign(
activeAccountInfo.localAccount.identityMaster.identityPublicKey,
activeAccountInfo.userLogin.identitySecret.value,
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the rejection to the inbox
if (await contactRequestInbox.tryWriteProtobuf(
proto.SignedContactResponse.fromBuffer, signedContactResponse,
subkey: 1) !=
null) {
log.error('failed to reject contact invitation');
return false;
}
return true;
});
}
//
ContactInvitationListManager _contactInvitationManager;
proto.SignedContactInvitation _signedContactInvitation;
proto.ContactInvitation _contactInvitation;
TypedKey _contactRequestInboxKey;
proto.ContactRequest _contactRequest;
proto.ContactRequestPrivate _contactRequestPrivate;
IdentityMaster _contactIdentityMaster;
KeyPair _writer;
}

View file

@ -67,8 +67,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
final scale = theme.extension<ScaleScheme>()!; final scale = theme.extension<ScaleScheme>()!;
return BlocProvider( return BlocProvider(
create: (context) => DefaultDHTRecordCubit( create: (context) => AccountRecordCubit(record: accountRecord),
record: accountRecord, decodeState: proto.Account.fromBuffer),
child: Column(children: <Widget>[ child: Column(children: <Widget>[
Row(children: [ Row(children: [
IconButton( IconButton(
@ -87,7 +86,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
context.go('/home/settings'); context.go('/home/settings');
}).paddingLTRB(0, 0, 8, 0), }).paddingLTRB(0, 0, 8, 0),
context context
.watch<DefaultDHTRecordCubit<proto.Account>>() .watch<AccountRecordCubit>()
.state .state
.builder((context, account) => ProfileWidget( .builder((context, account) => ProfileWidget(
name: account.profile.name, name: account.profile.name,
@ -96,7 +95,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
.expanded(), .expanded(),
]).paddingAll(8), ]).paddingAll(8),
context context
.watch<DefaultDHTRecordCubit<proto.Account>>() .watch<AccountRecordCubit>()
.state .state
.builder((context, account) => MainPager( .builder((context, account) => MainPager(
localAccounts: localAccounts, localAccounts: localAccounts,

View file

@ -11,6 +11,7 @@ import '../../../proto/proto.dart' as proto;
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
import '../../contact_invitation/contact_invitation.dart'; import '../../contact_invitation/contact_invitation.dart';
import '../../contacts/contacts.dart'; import '../../contacts/contacts.dart';
import '../../theme/theme.dart';
class AccountPage extends StatefulWidget { class AccountPage extends StatefulWidget {
const AccountPage({ const AccountPage({

View file

@ -439,145 +439,3 @@ class ContactInvitationListManager extends _$ContactInvitationListManager {
final DHTShortArray _dhtRecord; final DHTShortArray _dhtRecord;
IList<proto.ContactInvitationRecord> _records; IList<proto.ContactInvitationRecord> _records;
} }
//////////////////////////////////////////////////
///
class ValidContactInvitation {
ValidContactInvitation._(
{required ContactInvitationListManager contactInvitationManager,
required proto.SignedContactInvitation signedContactInvitation,
required proto.ContactInvitation contactInvitation,
required TypedKey contactRequestInboxKey,
required proto.ContactRequest contactRequest,
required proto.ContactRequestPrivate contactRequestPrivate,
required IdentityMaster contactIdentityMaster,
required KeyPair writer})
: _contactInvitationManager = contactInvitationManager,
_signedContactInvitation = signedContactInvitation,
_contactInvitation = contactInvitation,
_contactRequestInboxKey = contactRequestInboxKey,
_contactRequest = contactRequest,
_contactRequestPrivate = contactRequestPrivate,
_contactIdentityMaster = contactIdentityMaster,
_writer = writer;
Future<AcceptedContact?> accept() async {
final pool = await DHTRecordPool.instance();
final activeAccountInfo = _contactInvitationManager._activeAccountInfo;
try {
// Ensure we don't delete this if we're trying to chat to self
final isSelf = _contactIdentityMaster.identityPublicKey ==
activeAccountInfo.localAccount.identityMaster.identityPublicKey;
final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
return (await pool.openWrite(_contactRequestInboxKey, _writer,
parent: accountRecordKey))
// ignore: prefer_expression_function_bodies
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// Create local conversation key for this
// contact and send via contact response
return createConversation(
activeAccountInfo: activeAccountInfo,
remoteIdentityPublicKey:
_contactIdentityMaster.identityPublicTypedKey(),
callback: (localConversation) async {
final contactResponse = proto.ContactResponse()
..accept = true
..remoteConversationRecordKey = localConversation.key.toProto()
..identityMasterRecordKey = activeAccountInfo
.localAccount.identityMaster.masterRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final cs = await pool.veilid
.getCryptoSystem(_contactRequestInboxKey.kind);
final identitySignature = await cs.sign(
activeAccountInfo
.localAccount.identityMaster.identityPublicKey,
activeAccountInfo.userLogin.identitySecret.value,
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the acceptance to the inbox
if (await contactRequestInbox.tryWriteProtobuf(
proto.SignedContactResponse.fromBuffer,
signedContactResponse,
subkey: 1) !=
null) {
throw Exception('failed to accept contact invitation');
}
return AcceptedContact(
profile: _contactRequestPrivate.profile,
remoteIdentity: _contactIdentityMaster,
remoteConversationRecordKey: proto.TypedKeyProto.fromProto(
_contactRequestPrivate.chatRecordKey),
localConversationRecordKey: localConversation.key,
);
});
});
} on Exception catch (e) {
log.debug('exception: $e', e);
return null;
}
}
Future<bool> reject() async {
final pool = await DHTRecordPool.instance();
final activeAccountInfo = _contactInvitationManager._activeAccountInfo;
// Ensure we don't delete this if we're trying to chat to self
final isSelf = _contactIdentityMaster.identityPublicKey ==
activeAccountInfo.localAccount.identityMaster.identityPublicKey;
final accountRecordKey =
activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey;
return (await pool.openWrite(_contactRequestInboxKey, _writer,
parent: accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
final cs =
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
final contactResponse = proto.ContactResponse()
..accept = false
..identityMasterRecordKey = activeAccountInfo
.localAccount.identityMaster.masterRecordKey
.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign(
activeAccountInfo.localAccount.identityMaster.identityPublicKey,
activeAccountInfo.userLogin.identitySecret.value,
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the rejection to the inbox
if (await contactRequestInbox.tryWriteProtobuf(
proto.SignedContactResponse.fromBuffer, signedContactResponse,
subkey: 1) !=
null) {
log.error('failed to reject contact invitation');
return false;
}
return true;
});
}
//
ContactInvitationListManager _contactInvitationManager;
proto.SignedContactInvitation _signedContactInvitation;
proto.ContactInvitation _contactInvitation;
TypedKey _contactRequestInboxKey;
proto.ContactRequest _contactRequest;
proto.ContactRequestPrivate _contactRequestPrivate;
IdentityMaster _contactIdentityMaster;
KeyPair _writer;
}

View file

@ -29,6 +29,8 @@ class _DHTShortArrayCache {
} }
} }
xxxx add listening to head and linked records
class DHTShortArray { class DHTShortArray {
DHTShortArray._({required DHTRecord headRecord}) DHTShortArray._({required DHTRecord headRecord})
: _headRecord = headRecord, : _headRecord = headRecord,

View file

@ -0,0 +1,84 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:bloc/bloc.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import '../../veilid_support.dart';
class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
DHTShortArrayCubit({
required DHTShortArray shortArray,
required T Function(List<int> data) decodeElement,
}) : super(const AsyncValue.loading()) {
Future.delayed(Duration.zero, () async {
// Make initial state update
try {
final initialState = await initialStateFunction(record);
if (initialState != null) {
emit(AsyncValue.data(initialState));
}
} on Exception catch (e) {
emit(AsyncValue.error(e));
}
shortArray. xxx add listen to head and linked records in dht_short_array
_subscription = await record.listen((update) async {
try {
final newState =
await stateFunction(record, update.subkeys, update.valueData);
if (newState != null) {
emit(AsyncValue.data(newState));
}
} on Exception catch (e) {
emit(AsyncValue.error(e));
}
});
});
}
@override
Future<void> close() async {
await _subscription?.cancel();
_subscription = null;
await super.close();
}
StreamSubscription<VeilidUpdateValueChange>? _subscription;
}
// Cubit that watches the default subkey value of a dhtrecord
class DefaultDHTRecordCubit<T> extends DHTRecordCubit<T> {
DefaultDHTRecordCubit({
required super.record,
required T Function(List<int> data) decodeState,
}) : super(
initialStateFunction: (record) async {
final initialData = await record.get();
if (initialData == null) {
return null;
}
return decodeState(initialData);
},
stateFunction: (record, subkeys, valueData) async {
final defaultSubkey = record.subkeyOrDefault(-1);
if (subkeys.containsSubkey(defaultSubkey)) {
final Uint8List data;
final firstSubkey = subkeys.firstOrNull!.low;
if (firstSubkey != defaultSubkey) {
final maybeData = await record.get(forceRefresh: true);
if (maybeData == null) {
return null;
}
data = maybeData;
} else {
data = valueData.data;
}
final newState = decodeState(data);
return newState;
}
return null;
},
);
}