veilidchat/lib/contact_invitation/cubits/waiting_invitation_cubit.dart

113 lines
4.3 KiB
Dart
Raw Normal View History

2024-02-20 17:57:05 -05:00
import 'dart:typed_data';
import 'package:async_tools/async_tools.dart';
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
2024-02-20 17:57:05 -05:00
import 'package:equatable/equatable.dart';
import 'package:meta/meta.dart';
import 'package:veilid_support/veilid_support.dart';
2024-06-18 21:20:06 -04:00
import '../../account_manager/account_manager.dart';
import '../../conversation/conversation.dart';
2024-02-20 17:57:05 -05:00
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
import '../models/accepted_contact.dart';
import 'contact_request_inbox_cubit.dart';
@immutable
class InvitationStatus extends Equatable {
const InvitationStatus({required this.acceptedContact});
final AcceptedContact? acceptedContact;
@override
List<Object?> get props => [acceptedContact];
}
class WaitingInvitationCubit extends AsyncTransformerCubit<InvitationStatus,
proto.SignedContactResponse?> {
2024-06-18 21:20:06 -04:00
WaitingInvitationCubit(
ContactRequestInboxCubit super.input, {
required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
required proto.ContactInvitationRecord contactInvitationRecord,
}) : super(
2024-02-20 17:57:05 -05:00
transform: (signedContactResponse) => _transform(
signedContactResponse,
2024-06-18 21:20:06 -04:00
accountInfo: accountInfo,
accountRecordCubit: accountRecordCubit,
2024-02-20 17:57:05 -05:00
contactInvitationRecord: contactInvitationRecord));
static Future<AsyncValue<InvitationStatus>> _transform(
proto.SignedContactResponse? signedContactResponse,
2024-06-18 21:20:06 -04:00
{required AccountInfo accountInfo,
required AccountRecordCubit accountRecordCubit,
2024-02-20 17:57:05 -05:00
required proto.ContactInvitationRecord contactInvitationRecord}) async {
if (signedContactResponse == null) {
return const AsyncValue.loading();
}
2024-06-15 23:29:15 -04:00
2024-02-20 17:57:05 -05:00
final contactResponseBytes =
Uint8List.fromList(signedContactResponse.contactResponse);
final contactResponse =
proto.ContactResponse.fromBuffer(contactResponseBytes);
final contactIdentityMasterRecordKey =
2024-06-07 14:42:04 -04:00
contactResponse.superIdentityRecordKey.toVeilid();
2024-02-20 17:57:05 -05:00
// Fetch the remote contact's account master
2024-06-07 14:42:04 -04:00
final contactSuperIdentity = await SuperIdentity.open(
superRecordKey: contactIdentityMasterRecordKey);
2024-02-20 17:57:05 -05:00
// Verify
2024-06-07 14:42:04 -04:00
final idcs = await contactSuperIdentity.currentInstance.cryptoSystem;
2024-02-20 17:57:05 -05:00
final signature = signedContactResponse.identitySignature.toVeilid();
2024-06-07 14:42:04 -04:00
await idcs.verify(contactSuperIdentity.currentInstance.publicKey,
2024-02-20 17:57:05 -05:00
contactResponseBytes, signature);
// Check for rejection
if (!contactResponse.accept) {
// Rejection
return const AsyncValue.data(InvitationStatus(acceptedContact: null));
}
// Pull profile from remote conversation key
final remoteConversationRecordKey =
contactResponse.remoteConversationRecordKey.toVeilid();
final conversation = ConversationCubit(
2024-06-18 21:20:06 -04:00
accountInfo: accountInfo,
2024-06-07 14:42:04 -04:00
remoteIdentityPublicKey:
contactSuperIdentity.currentInstance.typedPublicKey,
2024-02-20 17:57:05 -05:00
remoteConversationRecordKey: remoteConversationRecordKey);
// wait for remote conversation for up to 20 seconds
proto.Conversation? remoteConversation;
var retryCount = 20;
do {
await conversation.refresh();
2024-04-05 22:03:04 -04:00
remoteConversation = conversation.state.asData?.value.remoteConversation;
2024-02-20 17:57:05 -05:00
if (remoteConversation != null) {
break;
}
log.info('Remote conversation could not be read. Waiting...');
await Future<void>.delayed(const Duration(seconds: 1));
retryCount--;
} while (retryCount > 0);
if (remoteConversation == null) {
return AsyncValue.error('Invitation accept timed out.');
}
// Complete the local conversation now that we have the remote profile
final remoteProfile = remoteConversation.profile;
final localConversationRecordKey =
contactInvitationRecord.localConversationRecordKey.toVeilid();
return conversation.initLocalConversation(
2024-06-18 21:20:06 -04:00
profile: accountRecordCubit.state.asData!.value.profile,
2024-02-20 17:57:05 -05:00
existingConversationRecordKey: localConversationRecordKey,
2024-06-15 23:29:15 -04:00
callback: (localConversation) async => AsyncValue.data(InvitationStatus(
acceptedContact: AcceptedContact(
remoteProfile: remoteProfile,
remoteIdentity: contactSuperIdentity,
remoteConversationRecordKey: remoteConversationRecordKey,
localConversationRecordKey: localConversationRecordKey))));
2024-02-20 17:57:05 -05:00
}
}