mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-07-27 16:35:26 -04:00
more refactor work
This commit is contained in:
parent
0a922e97b6
commit
6e8ba551ad
10 changed files with 295 additions and 213 deletions
16
lib/account_manager/cubit/account_record_cubit.dart
Normal file
16
lib/account_manager/cubit/account_record_cubit.dart
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
141
lib/contact_invitation/valid_contact_invitation.dart
Normal file
141
lib/contact_invitation/valid_contact_invitation.dart
Normal 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;
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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({
|
||||||
|
|
|
@ -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;
|
|
||||||
}
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
Loading…
Add table
Add a link
Reference in a new issue