checkpoint

This commit is contained in:
Christien Rioux 2024-06-18 21:20:06 -04:00
parent 3edf2ebb46
commit c40f835ec5
25 changed files with 378 additions and 312 deletions

View file

@ -4,7 +4,6 @@ import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
@ -37,14 +36,12 @@ class ContactInvitationListCubit
StateMapFollowable<ContactInvitiationListState, TypedKey,
proto.ContactInvitationRecord> {
ContactInvitationListCubit({
required Locator locator,
required TypedKey accountRecordKey,
required AccountInfo accountInfo,
required OwnedDHTRecordPointer contactInvitationListRecordPointer,
}) : _locator = locator,
_accountRecordKey = accountRecordKey,
}) : _accountInfo = accountInfo,
super(
open: () =>
_open(accountRecordKey, contactInvitationListRecordPointer),
open: () => _open(accountInfo.accountRecordKey,
contactInvitationListRecordPointer),
decodeElement: proto.ContactInvitationRecord.fromBuffer);
static Future<DHTShortArray> _open(TypedKey accountRecordKey,
@ -58,7 +55,8 @@ class ContactInvitationListCubit
}
Future<Uint8List> createInvitation(
{required EncryptionKeyType encryptionKeyType,
{required proto.Profile profile,
required EncryptionKeyType encryptionKeyType,
required String encryptionKey,
required String message,
required Timestamp? expiration}) async {
@ -68,12 +66,8 @@ class ContactInvitationListCubit
final crcs = await pool.veilid.bestCryptoSystem();
final contactRequestWriter = await crcs.generateKeyPair();
final activeAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final profile = _locator<AccountRecordCubit>().state.asData!.value.profile;
final idcs = await activeAccountInfo.identityCryptoSystem;
final identityWriter = activeAccountInfo.identityWriter;
final idcs = await _accountInfo.identityCryptoSystem;
final identityWriter = _accountInfo.identityWriter;
// Encrypt the writer secret with the encryption key
final encryptedSecret = await encryptionKeyType.encryptSecretToBytes(
@ -91,7 +85,7 @@ class ContactInvitationListCubit
await (await pool.createRecord(
debugName: 'ContactInvitationListCubit::createInvitation::'
'LocalConversation',
parent: _accountRecordKey,
parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl(
oCnt: 0,
members: [DHTSchemaMember(mKey: identityWriter.key, mCnt: 1)])))
@ -101,8 +95,7 @@ class ContactInvitationListCubit
final crpriv = proto.ContactRequestPrivate()
..writerKey = contactRequestWriter.key.toProto()
..profile = profile
..superIdentityRecordKey =
activeAccountInfo.userLogin.superIdentityRecordKey.toProto()
..superIdentityRecordKey = _accountInfo.superIdentityRecordKey.toProto()
..chatRecordKey = localConversation.key.toProto()
..expiration = expiration?.toInt64() ?? Int64.ZERO;
final crprivbytes = crpriv.writeToBuffer();
@ -120,7 +113,7 @@ class ContactInvitationListCubit
await (await pool.createRecord(
debugName: 'ContactInvitationListCubit::createInvitation::'
'ContactRequestInbox',
parent: _accountRecordKey,
parent: _accountInfo.accountRecordKey,
schema: DHTSchema.smpl(oCnt: 1, members: [
DHTSchemaMember(mCnt: 1, mKey: contactRequestWriter.key)
]),
@ -197,7 +190,7 @@ class ContactInvitationListCubit
await (await pool.openRecordOwned(contactRequestInbox,
debugName: 'ContactInvitationListCubit::deleteInvitation::'
'ContactRequestInbox',
parent: _accountRecordKey))
parent: _accountInfo.accountRecordKey))
.scope((contactRequestInbox) async {
// Wipe out old invitation so it shows up as invalid
await contactRequestInbox.tryWriteBytes(Uint8List(0));
@ -248,7 +241,7 @@ class ContactInvitationListCubit
debugName: 'ContactInvitationListCubit::validateInvitation::'
'ContactRequestInbox',
parent: pool.getParentRecordKey(contactRequestInboxKey) ??
_accountRecordKey))
_accountInfo.accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
//
final contactRequest = await contactRequestInbox
@ -293,7 +286,7 @@ class ContactInvitationListCubit
secret: writerSecret);
out = ValidContactInvitation(
locator: _locator,
accountInfo: _accountInfo,
contactRequestInboxKey: contactRequestInboxKey,
contactRequestPrivate: contactRequestPrivate,
contactSuperIdentity: contactSuperIdentity,
@ -317,6 +310,5 @@ class ContactInvitationListCubit
}
//
final Locator _locator;
final TypedKey _accountRecordKey;
final AccountInfo _accountInfo;
}

View file

@ -1,4 +1,3 @@
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
@ -8,23 +7,21 @@ import '../../proto/proto.dart' as proto;
class ContactRequestInboxCubit
extends DefaultDHTRecordCubit<proto.SignedContactResponse?> {
ContactRequestInboxCubit(
{required Locator locator, required this.contactInvitationRecord})
{required AccountInfo accountInfo, required this.contactInvitationRecord})
: super(
open: () => _open(
locator: locator,
accountInfo: accountInfo,
contactInvitationRecord: contactInvitationRecord),
decodeState: (buf) => buf.isEmpty
? null
: proto.SignedContactResponse.fromBuffer(buf));
static Future<DHTRecord> _open(
{required Locator locator,
{required AccountInfo accountInfo,
required proto.ContactInvitationRecord contactInvitationRecord}) async {
final pool = DHTRecordPool.instance;
final unlockedAccountInfo =
locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final accountRecordKey = accountInfo.accountRecordKey;
final writerSecret = contactInvitationRecord.writerSecret.toVeilid();
final recordKey =

View file

@ -4,9 +4,9 @@ import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart';
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
@ -24,18 +24,22 @@ class InvitationStatus extends Equatable {
class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
proto.SignedContactResponse?> {
WaitingInvitationCubit(ContactRequestInboxCubit super.input,
{required Locator locator,
required proto.ContactInvitationRecord contactInvitationRecord})
: super(
WaitingInvitationCubit(
ContactRequestInboxCubit super.input, {
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord,
}) : super(
transform: (signedContactResponse) => _transform(
signedContactResponse,
locator: locator,
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
contactInvitationRecord: contactInvitationRecord));
static Future<AsyncValue<InvitationStatus>> _transform(
proto.SignedContactResponse? signedContactResponse,
{required Locator locator,
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord}) async {
if (signedContactResponse == null) {
return const AsyncValue.loading();
@ -69,7 +73,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
contactResponse.remoteConversationRecordKey.toVeilid();
final conversation = ConversationCubit(
locator: locator,
accountInfo: accountInfo,
remoteIdentityPublicKey:
contactSuperIdentity.currentInstance.typedPublicKey,
remoteConversationRecordKey: remoteConversationRecordKey);
@ -96,6 +100,7 @@ class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
final localConversationRecordKey =
contactInvitationRecord.localConversationRecordKey.toVeilid();
return conversation.initLocalConversation(
profile: accountRecordCubit.state.asData!.value.profile,
existingConversationRecordKey: localConversationRecordKey,
callback: (localConversation) async => AsyncValue.data(InvitationStatus(
acceptedContact: AcceptedContact(

View file

@ -1,8 +1,8 @@
import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../proto/proto.dart' as proto;
import 'cubits.dart';
@ -17,11 +17,14 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
with
StateMapFollower<DHTShortArrayBusyState<proto.ContactInvitationRecord>,
TypedKey, proto.ContactInvitationRecord> {
WaitingInvitationsBlocMapCubit({
required Locator locator,
}) : _locator = locator {
WaitingInvitationsBlocMapCubit(
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required ContactInvitationListCubit contactInvitationListCubit})
: _accountInfo = accountInfo,
_accountRecordCubit = accountRecordCubit {
// Follow the contact invitation list cubit
follow(locator<ContactInvitationListCubit>());
follow(contactInvitationListCubit);
}
Future<void> _addWaitingInvitation(
@ -31,9 +34,10 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
contactInvitationRecord.contactRequestInbox.recordKey.toVeilid(),
WaitingInvitationCubit(
ContactRequestInboxCubit(
locator: _locator,
accountInfo: _accountInfo,
contactInvitationRecord: contactInvitationRecord),
locator: _locator,
accountInfo: _accountInfo,
accountRecordCubit: _accountRecordCubit,
contactInvitationRecord: contactInvitationRecord)));
/// StateFollower /////////////////////////
@ -46,5 +50,6 @@ class WaitingInvitationsBlocMapCubit extends BlocMapCubit<TypedKey,
_addWaitingInvitation(contactInvitationRecord: value);
////
final Locator _locator;
final AccountInfo _accountInfo;
final AccountRecordCubit _accountRecordCubit;
}

View file

@ -1,5 +1,4 @@
import 'package:meta/meta.dart';
import 'package:provider/provider.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
@ -14,12 +13,12 @@ import 'models.dart';
class ValidContactInvitation {
@internal
ValidContactInvitation(
{required Locator locator,
{required AccountInfo accountInfo,
required TypedKey contactRequestInboxKey,
required proto.ContactRequestPrivate contactRequestPrivate,
required SuperIdentity contactSuperIdentity,
required KeyPair writer})
: _locator = locator,
: _accountInfo = accountInfo,
_contactRequestInboxKey = contactRequestInboxKey,
_contactRequestPrivate = contactRequestPrivate,
_contactSuperIdentity = contactSuperIdentity,
@ -27,65 +26,57 @@ class ValidContactInvitation {
proto.Profile get remoteProfile => _contactRequestPrivate.profile;
Future<AcceptedContact?> accept() async {
Future<AcceptedContact?> accept(proto.Profile profile) async {
final pool = DHTRecordPool.instance;
try {
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final identityPublicKey = unlockedAccountInfo.identityPublicKey;
// Ensure we don't delete this if we're trying to chat to self
// The initiating side will delete the records in deleteInvitation()
final isSelf =
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey;
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
_accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::accept::'
'ContactRequestInbox',
parent: pool.getParentRecordKey(_contactRequestInboxKey) ??
accountRecordKey))
_accountInfo.accountRecordKey))
// ignore: prefer_expression_function_bodies
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
// Create local conversation key for this
// contact and send via contact response
final conversation = ConversationCubit(
locator: _locator,
accountInfo: _accountInfo,
remoteIdentityPublicKey:
_contactSuperIdentity.currentInstance.typedPublicKey);
return conversation.initLocalConversation(
profile: profile,
callback: (localConversation) async {
final contactResponse = proto.ContactResponse()
..accept = true
..remoteConversationRecordKey = localConversation.key.toProto()
..superIdentityRecordKey =
unlockedAccountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final contactResponse = proto.ContactResponse()
..accept = true
..remoteConversationRecordKey = localConversation.key.toProto()
..superIdentityRecordKey =
_accountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final cs =
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
final cs = await _accountInfo.identityCryptoSystem;
final identitySignature = await cs.signWithKeyPair(
_accountInfo.identityWriter, contactResponseBytes);
final identitySignature = await cs.sign(
unlockedAccountInfo.identityWriter.key,
unlockedAccountInfo.identityWriter.secret,
contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
..identitySignature = identitySignature.toProto();
// Write the acceptance to the inbox
await contactRequestInbox
.eventualWriteProtobuf(signedContactResponse, subkey: 1);
// Write the acceptance to the inbox
await contactRequestInbox.eventualWriteProtobuf(signedContactResponse,
subkey: 1);
return AcceptedContact(
remoteProfile: _contactRequestPrivate.profile,
remoteIdentity: _contactSuperIdentity,
remoteConversationRecordKey:
_contactRequestPrivate.chatRecordKey.toVeilid(),
localConversationRecordKey: localConversation.key,
);
});
return AcceptedContact(
remoteProfile: _contactRequestPrivate.profile,
remoteIdentity: _contactSuperIdentity,
remoteConversationRecordKey:
_contactRequestPrivate.chatRecordKey.toVeilid(),
localConversationRecordKey: localConversation.key,
);
});
});
} on Exception catch (e) {
log.debug('exception: $e', e);
@ -96,33 +87,24 @@ class ValidContactInvitation {
Future<bool> reject() async {
final pool = DHTRecordPool.instance;
final unlockedAccountInfo =
_locator<AccountInfoCubit>().state.unlockedAccountInfo!;
final accountRecordKey = unlockedAccountInfo.accountRecordKey;
final identityPublicKey = unlockedAccountInfo.identityPublicKey;
// Ensure we don't delete this if we're trying to chat to self
final isSelf =
_contactSuperIdentity.currentInstance.publicKey == identityPublicKey;
final isSelf = _contactSuperIdentity.currentInstance.publicKey ==
_accountInfo.identityPublicKey;
return (await pool.openRecordWrite(_contactRequestInboxKey, _writer,
debugName: 'ValidContactInvitation::reject::'
'ContactRequestInbox',
parent: accountRecordKey))
parent: _accountInfo.accountRecordKey))
.maybeDeleteScope(!isSelf, (contactRequestInbox) async {
final cs =
await pool.veilid.getCryptoSystem(_contactRequestInboxKey.kind);
final contactResponse = proto.ContactResponse()
..accept = false
..superIdentityRecordKey =
unlockedAccountInfo.superIdentityRecordKey.toProto();
_accountInfo.superIdentityRecordKey.toProto();
final contactResponseBytes = contactResponse.writeToBuffer();
final identitySignature = await cs.sign(
unlockedAccountInfo.identityWriter.key,
unlockedAccountInfo.identityWriter.secret,
contactResponseBytes);
final cs = await _accountInfo.identityCryptoSystem;
final identitySignature = await cs.signWithKeyPair(
_accountInfo.identityWriter, contactResponseBytes);
final signedContactResponse = proto.SignedContactResponse()
..contactResponse = contactResponseBytes
@ -136,7 +118,7 @@ class ValidContactInvitation {
}
//
final Locator _locator;
final AccountInfo _accountInfo;
final TypedKey _contactRequestInboxKey;
final SuperIdentity _contactSuperIdentity;
final KeyPair _writer;

View file

@ -140,8 +140,18 @@ class CreateInvitationDialogState extends State<CreateInvitationDialog> {
// Start generation
final contactInvitationListCubit =
widget.modalContext.read<ContactInvitationListCubit>();
final profile = widget.modalContext
.read<AccountRecordCubit>()
.state
.asData
?.value
.profile;
if (profile == null) {
return;
}
final generator = contactInvitationListCubit.createInvitation(
profile: profile,
encryptionKeyType: _encryptionKeyType,
encryptionKey: _encryptionKey,
message: _messageTextController.text,

View file

@ -76,17 +76,19 @@ class InvitationDialogState extends State<InvitationDialog> {
final navigator = Navigator.of(context);
final accountInfo = widget._locator<AccountInfoCubit>().state;
final contactList = widget._locator<ContactListCubit>();
final profile =
widget._locator<AccountRecordCubit>().state.asData!.value.profile;
setState(() {
_isAccepting = true;
});
final validInvitation = _validInvitation;
if (validInvitation != null) {
final acceptedContact = await validInvitation.accept();
final acceptedContact = await validInvitation.accept(profile);
if (acceptedContact != null) {
// initiator when accept is received will create
// contact in the case of a 'note to self'
final isSelf = accountInfo.unlockedAccountInfo!.identityPublicKey ==
final isSelf = accountInfo.identityPublicKey ==
acceptedContact.remoteIdentity.currentInstance.publicKey;
if (!isSelf) {
await contactList.createContact(