mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-08-03 03:36:23 -04:00
checkpoint
This commit is contained in:
parent
3edf2ebb46
commit
c40f835ec5
25 changed files with 378 additions and 312 deletions
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 =
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue