@@ -10,9 +10,11 @@ import { formatChatListTimestamp } from "./format-time.js";
1010import { DeliveryStateIcon } from "./MessageLog.js" ;
1111import { chatPath , MESSAGE_REQUESTS_PATH , SETTINGS_PATH } from "./paths.js" ;
1212import { pwaReleaseVersion } from "./pwa-release.js" ;
13+ import { sendContactRequest } from "../connectivity/seal-and-send.js" ;
1314import { useBranchID } from "../identity/use-branch-id.js" ;
1415import { useChatList , useContactDiscoveries , useContactFolders , useContactPresence , useContactTyping , useContacts , useIdentity , useInboundAttachmentOffer , useIncomingMessageRequests , useTransportStatus , type ChatListEntry } from "../state/hooks.js" ;
1516import { filterChatListByFolder } from "../state/contact-folder-filter.js" ;
17+ import { useAppStoreApi } from "../state/StoreProvider.js" ;
1618import { parseBranchID } from "@code4bones/branch-core" ;
1719
1820export function ChatListSidebar ( ) : React . JSX . Element {
@@ -286,6 +288,8 @@ function IncomingAttachmentBadge({ peerId }: { readonly peerId: string }): React
286288function AddContactModal ( { open, onClose } : { readonly open : boolean ; readonly onClose : ( ) => void } ) : React . JSX . Element {
287289 const contacts = useContacts ( ) ;
288290 const discovery = useContactDiscoveries ( ) ;
291+ const identity = useIdentity ( ) ;
292+ const storeApi = useAppStoreApi ( ) ;
289293 const [ branchId , setBranchId ] = useState ( "" ) ;
290294 const [ error , setError ] = useState < string | null > ( null ) ;
291295
@@ -303,8 +307,28 @@ function AddContactModal({ open, onClose }: { readonly open: boolean; readonly o
303307
304308 const add = ( row : typeof discovery . rows [ number ] ) : void => {
305309 if ( row . displayName === null || row . peerId === null || row . hpkePublicKey === null ) return ;
306- if ( ! contacts . contacts . some ( ( contact ) => contact . peerId === row . peerId ) ) contacts . upsertContact ( { contactId : crypto . randomUUID ( ) , displayName : row . displayName , peerId : row . peerId , hpkePublicKey : row . hpkePublicKey , lastRouteHint : null } ) ;
310+ if ( contacts . contacts . some ( ( contact ) => contact . peerId === row . peerId ) ) {
311+ discovery . remove ( row . branchId ) ;
312+ return ;
313+ }
314+ contacts . upsertContact ( { contactId : crypto . randomUUID ( ) , displayName : row . displayName , peerId : row . peerId , hpkePublicKey : row . hpkePublicKey , lastRouteHint : null } ) ;
307315 discovery . remove ( row . branchId ) ;
316+ const localIdentity = identity . identity ;
317+ if ( localIdentity === null || localIdentity . displayName === null ) return ;
318+ void sendContactRequest ( {
319+ senderPeerId : localIdentity . peerId ,
320+ senderHpkePublicKey : localIdentity . hpkePublicKey ,
321+ senderDisplayName : localIdentity . displayName ,
322+ recipientPeerId : row . peerId ,
323+ recipientHpkePublicKey : row . hpkePublicKey
324+ } ) . then ( ( ) => {
325+ storeApi . getState ( ) . recordTransportTrace ( "contact request: sent" ) ;
326+ } ) . catch ( ( ) => {
327+ // The contact card is still useful locally. A missed live request is
328+ // neither a rejection nor an offline-delivery promise, so do not queue
329+ // or fabricate an invite outcome.
330+ storeApi . getState ( ) . recordTransportTrace ( "contact request: not sent" ) ;
331+ } ) ;
308332 } ;
309333
310334 return (
0 commit comments