From 7cf44ef192e86a335f8dc1b6ab2704254cc52470 Mon Sep 17 00:00:00 2001 From: Christien Rioux Date: Fri, 26 Jan 2024 21:02:11 -0500 Subject: [PATCH] refactor --- .../contact_invitation.dart | 2 +- lib/contact_invitation/models/models.dart | 2 +- .../contact_invitation_repository.dart | 8 ++ .../views/send_invite_dialog.dart | 2 +- lib/layout/edit_account.dart | 1 - lib/layout/edit_contact.dart | 30 ------- lib/layout/home/home.dart | 87 +++++++++++++++++++ lib/layout/home/home_account_invalid.dart | 32 +++++++ lib/layout/home/home_account_locked.dart | 23 +++++ lib/layout/home/home_account_missing.dart | 33 +++++++ .../home_account_ready}/chat_only.dart | 4 +- .../home_account_ready.dart} | 83 +++++++++++------- .../main_pager/account_page.dart | 10 +-- .../bottom_sheet_action_button.dart | 0 .../main_pager/chats_page.dart | 6 +- .../main_pager/main_pager.dart | 14 +-- lib/layout/home/home_no_active.dart | 25 ++++++ lib/layout/layout.dart | 2 +- .../lib/dht_support/src/dht_record_pool.dart | 10 ++- .../lib/src/async_tag_lock.dart | 45 ++++++++++ .../veilid_support/lib/veilid_support.dart | 1 + 21 files changed, 338 insertions(+), 82 deletions(-) delete mode 100644 lib/layout/edit_account.dart delete mode 100644 lib/layout/edit_contact.dart create mode 100644 lib/layout/home/home.dart create mode 100644 lib/layout/home/home_account_invalid.dart create mode 100644 lib/layout/home/home_account_locked.dart create mode 100644 lib/layout/home/home_account_missing.dart rename lib/layout/{ => home/home_account_ready}/chat_only.dart (92%) rename lib/layout/{home.dart => home/home_account_ready/home_account_ready.dart} (77%) rename lib/layout/{ => home/home_account_ready}/main_pager/account_page.dart (92%) rename lib/layout/{ => home/home_account_ready}/main_pager/bottom_sheet_action_button.dart (100%) rename lib/layout/{ => home/home_account_ready}/main_pager/chats_page.dart (94%) rename lib/layout/{ => home/home_account_ready}/main_pager/main_pager.dart (96%) create mode 100644 lib/layout/home/home_no_active.dart create mode 100644 packages/veilid_support/lib/src/async_tag_lock.dart diff --git a/lib/contact_invitation/contact_invitation.dart b/lib/contact_invitation/contact_invitation.dart index d46a57e..9ca8bca 100644 --- a/lib/contact_invitation/contact_invitation.dart +++ b/lib/contact_invitation/contact_invitation.dart @@ -1,2 +1,2 @@ -export 'views/views.dart'; export 'repository/contact_invitation_repository.dart'; +export 'views/views.dart'; diff --git a/lib/contact_invitation/models/models.dart b/lib/contact_invitation/models/models.dart index 0936f63..331af2d 100644 --- a/lib/contact_invitation/models/models.dart +++ b/lib/contact_invitation/models/models.dart @@ -1,2 +1,2 @@ export 'accepted_contact.dart'; -export 'valid_contact_invitation.dart'; +export '../repository/valid_contact_invitation.dart'; diff --git a/lib/contact_invitation/repository/contact_invitation_repository.dart b/lib/contact_invitation/repository/contact_invitation_repository.dart index 2e8d49c..fe505df 100644 --- a/lib/contact_invitation/repository/contact_invitation_repository.dart +++ b/lib/contact_invitation/repository/contact_invitation_repository.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:fixnum/fixnum.dart'; import 'package:flutter/foundation.dart'; import 'package:veilid_support/veilid_support.dart'; @@ -27,6 +29,7 @@ class InvitationStatus { ////////////////////////////////////////////////// + ////////////////////////////////////////////////// // Mutable state for per-account contact invitations class ContactInvitationRepository { @@ -37,9 +40,14 @@ class ContactInvitationRepository { }) : _activeAccountInfo = activeAccountInfo, _account = account, _dhtRecord = dhtRecord; + + void dispose() { + unawaited(close()); + } static Future open( ActiveAccountInfo activeAccountInfo, proto.Account account) async { + final accountRecordKey = activeAccountInfo.userLogin.accountRecordInfo.accountRecord.recordKey; diff --git a/lib/contact_invitation/views/send_invite_dialog.dart b/lib/contact_invitation/views/send_invite_dialog.dart index 417a18a..3d15d5f 100644 --- a/lib/contact_invitation/views/send_invite_dialog.dart +++ b/lib/contact_invitation/views/send_invite_dialog.dart @@ -136,7 +136,7 @@ class SendInviteDialogState extends State { navigator.pop(); return; } - final generator = createContactInvitation( + final generator = ContactInvitationRespositoryxxx.createContactInvitation( activeAccountInfo: activeAccountInfo, encryptionKeyType: _encryptionKeyType, encryptionKey: _encryptionKey, diff --git a/lib/layout/edit_account.dart b/lib/layout/edit_account.dart deleted file mode 100644 index 8b13789..0000000 --- a/lib/layout/edit_account.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/layout/edit_contact.dart b/lib/layout/edit_contact.dart deleted file mode 100644 index 480ff1f..0000000 --- a/lib/layout/edit_contact.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:flutter/material.dart'; - -class ContactsPage extends StatelessWidget { - const ContactsPage({super.key}); - static const path = '/contacts'; - - @override - Widget build( - BuildContext context, - ) => - const Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Contacts Page'), - // ElevatedButton( - // onPressed: () async { - // ref.watch(authNotifierProvider.notifier).login( - // "myEmail", - // "myPassword", - // ); - // }, - // child: const Text("Login"), - // ), - ], - ), - ), - ); -} diff --git a/lib/layout/home/home.dart b/lib/layout/home/home.dart new file mode 100644 index 0000000..fb98287 --- /dev/null +++ b/lib/layout/home/home.dart @@ -0,0 +1,87 @@ +import 'package:fast_immutable_collections/fast_immutable_collections.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:veilid_support/veilid_support.dart'; + +import '../../account_manager/account_manager.dart'; +import '../../theme/theme.dart'; +import '../../tools/tools.dart'; +import 'home_account_invalid.dart'; +import 'home_account_locked.dart'; +import 'home_account_missing.dart'; +import 'home_account_ready.dart'; +import 'home_account_ready/home_account_ready.dart'; + +class HomePage extends StatefulWidget { + const HomePage({super.key}); + + @override + HomePageState createState() => HomePageState(); +} + +class HomePageState extends State with TickerProviderStateMixin { + final _unfocusNode = FocusNode(); + + @override + void initState() { + super.initState(); + + WidgetsBinding.instance.addPostFrameCallback((_) async { + await changeWindowSetup( + TitleBarStyle.normal, OrientationCapability.normal); + }); + } + + @override + void dispose() { + _unfocusNode.dispose(); + super.dispose(); + } + + Widget buildWithLogin(BuildContext context, IList localAccounts, + Typed? activeUserLogin) { + if (activeUserLogin == null) { + // If no logged in user is active, show the loading panel + return waitingPage(context); + } + + final accountInfo = AccountRepository.instance + .getAccountInfo(accountMasterRecordKey: activeUserLogin)!; + + switch (accountInfo.status) { + case AccountInfoStatus.noAccount: + return const HomeAccountMissing(); + case AccountInfoStatus.accountInvalid: + return const HomeAccountInvalid(); + case AccountInfoStatus.accountLocked: + return const HomeAccountLocked(); + case AccountInfoStatus.accountReady: + return BlocProvider( + create: (context) => AccountRecordCubit( + record: accountInfo.activeAccountInfo!.accountRecord), + child: context.watch().state.builder( + (context, account) => HomeAccountReady( + localAccounts: localAccounts, + activeUserLogin: activeUserLogin, + activeAccountInfo: accountInfo.activeAccountInfo!, + account: account))); + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final scale = theme.extension()!; + final activeUserLogin = context.watch().state; + final localAccounts = context.watch().state; + + return SafeArea( + child: GestureDetector( + onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), + child: DecoratedBox( + decoration: BoxDecoration( + color: scale.primaryScale.activeElementBackground), + child: + buildWithLogin(context, localAccounts, activeUserLogin)))); + } +} diff --git a/lib/layout/home/home_account_invalid.dart b/lib/layout/home/home_account_invalid.dart new file mode 100644 index 0000000..bf11735 --- /dev/null +++ b/lib/layout/home/home_account_invalid.dart @@ -0,0 +1,32 @@ +import 'package:flutter/material.dart'; + +class HomeAccountInvalid extends StatefulWidget { + const HomeAccountInvalid({super.key}); + + @override + HomeAccountInvalidState createState() => HomeAccountInvalidState(); +} + +class HomeAccountInvalidState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) => const Text('Account invalid'); +} +// xxx: delete invalid account + // Future.delayed(0.ms, () async { + // await showErrorModal(context, translate('home.invalid_account_title'), + // translate('home.invalid_account_text')); + // // Delete account + // await AccountRepository.instance.deleteLocalAccount(activeUserLogin); + // // Switch to no active user login + // await AccountRepository.instance.switchToAccount(null); + // }); diff --git a/lib/layout/home/home_account_locked.dart b/lib/layout/home/home_account_locked.dart new file mode 100644 index 0000000..0b8a4f7 --- /dev/null +++ b/lib/layout/home/home_account_locked.dart @@ -0,0 +1,23 @@ +import 'package:flutter/material.dart'; + +class HomeAccountLocked extends StatefulWidget { + const HomeAccountLocked({super.key}); + + @override + HomeAccountLockedState createState() => HomeAccountLockedState(); +} + +class HomeAccountLockedState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) => const Text('Account locked'); +} diff --git a/lib/layout/home/home_account_missing.dart b/lib/layout/home/home_account_missing.dart new file mode 100644 index 0000000..d9c0aad --- /dev/null +++ b/lib/layout/home/home_account_missing.dart @@ -0,0 +1,33 @@ +import 'package:flutter/material.dart'; + +class HomeAccountMissing extends StatefulWidget { + const HomeAccountMissing({super.key}); + + @override + HomeAccountMissingState createState() => HomeAccountMissingState(); +} + +class HomeAccountMissingState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) => const Text('Account missing'); +} + +// xxx click to delete missing account or add to postframecallback + // Future.delayed(0.ms, () async { + // await showErrorModal(context, translate('home.missing_account_title'), + // translate('home.missing_account_text')); + // // Delete account + // await AccountRepository.instance.deleteLocalAccount(activeUserLogin); + // // Switch to no active user login + // await AccountRepository.instance.switchToAccount(null); + // }); \ No newline at end of file diff --git a/lib/layout/chat_only.dart b/lib/layout/home/home_account_ready/chat_only.dart similarity index 92% rename from lib/layout/chat_only.dart rename to lib/layout/home/home_account_ready/chat_only.dart index 6f4e645..48cb02e 100644 --- a/lib/layout/chat_only.dart +++ b/lib/layout/home/home_account_ready/chat_only.dart @@ -1,7 +1,7 @@ import 'package:flutter/material.dart'; -import '../chat/chat.dart'; -import '../tools/tools.dart'; +import '../../../chat/chat.dart'; +import '../../../tools/tools.dart'; class ChatOnlyPage extends StatefulWidget { const ChatOnlyPage({super.key}); diff --git a/lib/layout/home.dart b/lib/layout/home/home_account_ready/home_account_ready.dart similarity index 77% rename from lib/layout/home.dart rename to lib/layout/home/home_account_ready/home_account_ready.dart index 8826958..418d248 100644 --- a/lib/layout/home.dart +++ b/lib/layout/home/home_account_ready/home_account_ready.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/material.dart'; @@ -7,36 +9,61 @@ import 'package:flutter_translate/flutter_translate.dart'; import 'package:go_router/go_router.dart'; import 'package:veilid_support/veilid_support.dart'; -import '../account_manager/account_manager.dart'; -import '../chat/chat.dart'; -import '../theme/theme.dart'; -import '../tools/tools.dart'; -import 'main_pager/main_pager.dart'; +import '../../../account_manager/account_manager.dart'; +import '../../../contact_invitation/contact_invitation.dart'; +import '../../../proto/proto.dart' as proto; +import '../../../theme/theme.dart'; +import '../../../tools/tools.dart'; -class HomePage extends StatefulWidget { - const HomePage({super.key}); +class HomeAccountReady extends StatefulWidget { + const HomeAccountReady( + {required IList localAccounts, + required TypedKey activeUserLogin, + required ActiveAccountInfo activeAccountInfo, + required proto.Account account, + super.key}) + : _localAccounts = localAccounts, + _activeUserLogin = activeUserLogin, + _activeAccountInfo = activeAccountInfo, + _account = account; + + final IList _localAccounts; + final TypedKey _activeUserLogin; + final ActiveAccountInfo _activeAccountInfo; + final proto.Account _account; @override - HomePageState createState() => HomePageState(); + HomeAccountReadyState createState() => HomeAccountReadyState(); } -class HomePageState extends State with TickerProviderStateMixin { - final _unfocusNode = FocusNode(); +class HomeAccountReadyState extends State + with TickerProviderStateMixin { + // + ContactInvitationRepository? _contactInvitationRepository; + // @override void initState() { super.initState(); - WidgetsBinding.instance.addPostFrameCallback((_) async { - await changeWindowSetup( - TitleBarStyle.normal, OrientationCapability.normal); + // Async initialize repositories for the active user + // xxx: this should not be necessary + // xxx: but RepositoryProvider doesn't call dispose() + Future.delayed(Duration.zero, () async { + // + final cir = await ContactInvitationRepository.open( + widget._activeAccountInfo, widget._account); + + setState(() { + _contactInvitationRepository = cir; + }); }); } @override void dispose() { - _unfocusNode.dispose(); super.dispose(); + _contactInvitationRepository?.dispose(); } // ignore: prefer_expression_function_bodies @@ -65,6 +92,8 @@ class HomePageState extends State with TickerProviderStateMixin { final theme = Theme.of(context); final scale = theme.extension()!; +xxx get rid of the cubit here and + return BlocProvider( create: (context) => AccountRecordCubit(record: accountRecord), child: Column(children: [ @@ -104,6 +133,8 @@ class HomePageState extends State with TickerProviderStateMixin { ])); } +xxx get rid of this whole function + Widget buildUserPanel() => Builder(builder: (context) { final activeUserLogin = context.watch().state; final localAccounts = context.watch().state; @@ -190,21 +221,15 @@ class HomePageState extends State with TickerProviderStateMixin { @override Widget build(BuildContext context) { - final theme = Theme.of(context); - final scale = theme.extension()!; + if (_contactInvitationRepository == null) { + return waitingPage(context); + } - return SafeArea( - child: GestureDetector( - onTap: () => FocusScope.of(context).requestFocus(_unfocusNode), - child: DecoratedBox( - decoration: BoxDecoration( - color: scale.primaryScale.activeElementBackground), - child: responsiveVisibility( - context: context, - phone: false, - ) - ? buildTablet() - : buildPhone(), - ))); + return responsiveVisibility( + context: context, + phone: false, + ) + ? buildTablet() + : buildPhone(); } } diff --git a/lib/layout/main_pager/account_page.dart b/lib/layout/home/home_account_ready/main_pager/account_page.dart similarity index 92% rename from lib/layout/main_pager/account_page.dart rename to lib/layout/home/home_account_ready/main_pager/account_page.dart index ef83d8e..3ebf2be 100644 --- a/lib/layout/main_pager/account_page.dart +++ b/lib/layout/home/home_account_ready/main_pager/account_page.dart @@ -5,11 +5,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_translate/flutter_translate.dart'; import 'package:veilid_support/veilid_support.dart'; -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'; +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({ diff --git a/lib/layout/main_pager/bottom_sheet_action_button.dart b/lib/layout/home/home_account_ready/main_pager/bottom_sheet_action_button.dart similarity index 100% rename from lib/layout/main_pager/bottom_sheet_action_button.dart rename to lib/layout/home/home_account_ready/main_pager/bottom_sheet_action_button.dart diff --git a/lib/layout/main_pager/chats_page.dart b/lib/layout/home/home_account_ready/main_pager/chats_page.dart similarity index 94% rename from lib/layout/main_pager/chats_page.dart rename to lib/layout/home/home_account_ready/main_pager/chats_page.dart index 56756e2..e227e8b 100644 --- a/lib/layout/main_pager/chats_page.dart +++ b/lib/layout/home/home_account_ready/main_pager/chats_page.dart @@ -2,9 +2,9 @@ import 'package:awesome_extensions/awesome_extensions.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:flutter/material.dart'; -import '../../../proto/proto.dart' as proto; -import '../../account_manager/account_manager.dart'; -import '../../tools/tools.dart'; +import '../../../../proto/proto.dart' as proto; +import '../../../account_manager/account_manager.dart'; +import '../../../tools/tools.dart'; class ChatsPage extends StatefulWidget { const ChatsPage({super.key}); diff --git a/lib/layout/main_pager/main_pager.dart b/lib/layout/home/home_account_ready/main_pager/main_pager.dart similarity index 96% rename from lib/layout/main_pager/main_pager.dart rename to lib/layout/home/home_account_ready/main_pager/main_pager.dart index 05e5979..c872224 100644 --- a/lib/layout/main_pager/main_pager.dart +++ b/lib/layout/home/home_account_ready/main_pager/main_pager.dart @@ -11,14 +11,14 @@ import 'package:stylish_bottom_bar/model/bar_items.dart'; import 'package:stylish_bottom_bar/stylish_bottom_bar.dart'; import 'package:veilid_support/veilid_support.dart'; -import '../../../proto/proto.dart' as proto; -import '../../../tools/tools.dart'; -import '../../account_manager/account_manager.dart'; -import '../../contact_invitation/contact_invitation.dart'; -import '../../theme/theme.dart'; -import 'account.dart'; +import '../../../../proto/proto.dart' as proto; +import '../../../../tools/tools.dart'; +import '../../../account_manager/account_manager.dart'; +import '../../../contact_invitation/contact_invitation.dart'; +import '../../../theme/theme.dart'; +import 'account_page.dart'; import 'bottom_sheet_action_button.dart'; -import 'chats.dart'; +import 'chats_page.dart'; class MainPager extends StatefulWidget { const MainPager( diff --git a/lib/layout/home/home_no_active.dart b/lib/layout/home/home_no_active.dart new file mode 100644 index 0000000..31e3378 --- /dev/null +++ b/lib/layout/home/home_no_active.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +import '../../tools/tools.dart'; + +class HomeNoActive extends StatefulWidget { + const HomeNoActive({super.key}); + + @override + HomeNoActiveState createState() => HomeNoActiveState(); +} + +class HomeNoActiveState extends State { + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) => waitingPage(context); +} diff --git a/lib/layout/layout.dart b/lib/layout/layout.dart index 34b7364..e896be6 100644 --- a/lib/layout/layout.dart +++ b/lib/layout/layout.dart @@ -1,4 +1,4 @@ -export 'chat_only.dart'; +export 'home/home_account_ready/chat_only.dart'; export 'default_app_bar.dart'; export 'edit_account.dart'; export 'edit_contact.dart'; diff --git a/packages/veilid_support/lib/dht_support/src/dht_record_pool.dart b/packages/veilid_support/lib/dht_support/src/dht_record_pool.dart index 1c09d45..fd47da4 100644 --- a/packages/veilid_support/lib/dht_support/src/dht_record_pool.dart +++ b/packages/veilid_support/lib/dht_support/src/dht_record_pool.dart @@ -60,11 +60,14 @@ class DHTRecordPool with TableDBBacked { parentByChild: IMap(), rootRecords: ISet()), _opened = {}, + _locks = AsyncTagLock(), _routingContext = routingContext, _veilid = veilid; // Persistent DHT record list DHTRecordPoolAllocations _state; + // Lock table to ensure we don't open the same record more than once + final AsyncTagLock _locks; // Which DHT records are currently open final Map _opened; // Default routing context to use for new keys @@ -115,6 +118,7 @@ class DHTRecordPool with TableDBBacked { if (rec == null) { throw StateError('record already closed'); } + _locks.unlockTag(key); } Future deleteDeep(TypedKey parent) async { @@ -247,6 +251,8 @@ class DHTRecordPool with TableDBBacked { TypedKey? parent, int defaultSubkey = 0, DHTRecordCrypto? crypto}) async { + await _locks.lockTag(recordKey); + final dhtctx = routingContext ?? _routingContext; late final DHTRecord rec; @@ -278,6 +284,8 @@ class DHTRecordPool with TableDBBacked { int defaultSubkey = 0, DHTRecordCrypto? crypto, }) async { + await _locks.lockTag(recordKey); + final dhtctx = routingContext ?? _routingContext; late final DHTRecord rec; @@ -325,7 +333,7 @@ class DHTRecordPool with TableDBBacked { crypto: crypto, ); - /// Look up an opened DHRRecord + /// Look up an opened DHTRecord DHTRecord? getOpenedRecord(TypedKey recordKey) => _opened[recordKey]; /// Get the parent of a DHTRecord key if it exists diff --git a/packages/veilid_support/lib/src/async_tag_lock.dart b/packages/veilid_support/lib/src/async_tag_lock.dart new file mode 100644 index 0000000..293b045 --- /dev/null +++ b/packages/veilid_support/lib/src/async_tag_lock.dart @@ -0,0 +1,45 @@ +import 'package:mutex/mutex.dart'; + +class _AsyncTagLockEntry { + _AsyncTagLockEntry() + : mutex = Mutex(), + waitingCount = 1; + // + Mutex mutex; + int waitingCount; +} + +class AsyncTagLock { + AsyncTagLock() + : _tableLock = Mutex(), + _locks = {}; + + Future lockTag(T tag) async { + await _tableLock.protect(() async { + var lockEntry = _locks[tag]; + if (lockEntry != null) { + lockEntry.waitingCount++; + } else { + lockEntry = _locks[tag] = _AsyncTagLockEntry(); + } + + await lockEntry.mutex.acquire(); + lockEntry.waitingCount--; + }); + } + + void unlockTag(T tag) { + final lockEntry = _locks[tag]!; + if (lockEntry.waitingCount == 0) { + // If nobody is waiting for the mutex we can just drop it + _locks.remove(tag); + } else { + // Someone's waiting for the tag lock so release the mutex for it + lockEntry.mutex.release(); + } + } + + // + final Mutex _tableLock; + final Map _locks; +} diff --git a/packages/veilid_support/lib/veilid_support.dart b/packages/veilid_support/lib/veilid_support.dart index fec2539..b0dfce0 100644 --- a/packages/veilid_support/lib/veilid_support.dart +++ b/packages/veilid_support/lib/veilid_support.dart @@ -6,6 +6,7 @@ library veilid_support; export 'package:veilid/veilid.dart'; export 'dht_support/dht_support.dart'; +export 'src/async_tag_lock.dart'; export 'src/async_value.dart'; export 'src/config.dart'; export 'src/identity.dart';