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 'local_accounts_cubit/local_accounts_cubit.dart';
export 'user_logins_cubit/user_logins_cubit.dart';

View File

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

View File

@ -82,66 +82,40 @@ class AccountRepository {
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
final localAccount =
fetchLocalAccount(accountMasterRecordKey: accountMasterRecordKey);
if (localAccount == null) {
// Local account does not exist
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
final activeUserLogin = getActiveUserLogin();
final active = activeUserLogin == accountMasterRecordKey;
final login =
final userLogin =
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) {
// Account was locked
return null;
}
// Get which local account we want to fetch the profile for
final localAccount =
fetchLocalAccount(accountMasterRecordKey: activeUserLogin);
if (localAccount == null) {
// Local account does not exist
return null;
return AccountInfo(
status: AccountInfoStatus.accountLocked,
active: active,
activeAccountInfo: null);
}
// Pull the account DHT key, decode it and return it
@ -149,14 +123,21 @@ class AccountRepository {
final accountRecord = pool
.getOpenedRecord(userLogin.accountRecordInfo.accountRecord.recordKey);
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
return ActiveAccountInfo(
localAccount: localAccount,
userLogin: userLogin,
accountRecord: accountRecord,
return AccountInfo(
status: AccountInfoStatus.accountReady,
active: active,
activeAccountInfo: ActiveAccountInfo(
localAccount: localAccount,
userLogin: userLogin,
accountRecord: accountRecord),
);
}
@ -411,24 +392,21 @@ class AccountRepository {
// For all user logins if they arent open yet
final activeLogins = await _activeLogins.get();
for (final userLogin in activeLogins.userLogins) {
//// Account record key /////////////////////////////
final accountRecordKey =
userLogin.accountRecordInfo.accountRecord.recordKey;
final existingAccountRecord = pool.getOpenedRecord(accountRecordKey);
if (existingAccountRecord != null) {
continue;
if (existingAccountRecord == null) {
final localAccount = fetchLocalAccount(
accountMasterRecordKey: userLogin.accountMasterRecordKey);
// Record not yet open, do it
final record = await pool.openOwned(
userLogin.accountRecordInfo.accountRecord,
parent: localAccount!.identityMaster.identityRecordKey);
// Watch the record's only (default) key
await record.watch();
}
final localAccount = fetchLocalAccount(
accountMasterRecordKey: userLogin.accountMasterRecordKey);
// Record not yet open, do it
final record = await pool.openOwned(
userLogin.accountRecordInfo.accountRecord,
parent: localAccount!.identityMaster.identityRecordKey);
// Watch the record's only (default) key
await record.watch();
// .scope(
// (accountRec) => accountRec.getProtobuf(proto.Account.fromBuffer));
}
}
@ -437,6 +415,7 @@ class AccountRepository {
final activeLogins = await _activeLogins.get();
for (final userLogin in activeLogins.userLogins) {
//// Account record key /////////////////////////////
final accountRecordKey =
userLogin.accountRecordInfo.accountRecord.recordKey;
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>()!;
return BlocProvider(
create: (context) => DefaultDHTRecordCubit(
record: accountRecord, decodeState: proto.Account.fromBuffer),
create: (context) => AccountRecordCubit(record: accountRecord),
child: Column(children: <Widget>[
Row(children: [
IconButton(
@ -87,7 +86,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
context.go('/home/settings');
}).paddingLTRB(0, 0, 8, 0),
context
.watch<DefaultDHTRecordCubit<proto.Account>>()
.watch<AccountRecordCubit>()
.state
.builder((context, account) => ProfileWidget(
name: account.profile.name,
@ -96,7 +95,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
.expanded(),
]).paddingAll(8),
context
.watch<DefaultDHTRecordCubit<proto.Account>>()
.watch<AccountRecordCubit>()
.state
.builder((context, account) => MainPager(
localAccounts: localAccounts,

View File

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

View File

@ -439,145 +439,3 @@ class ContactInvitationListManager extends _$ContactInvitationListManager {
final DHTShortArray _dhtRecord;
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 {
DHTShortArray._({required DHTRecord 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;
},
);
}