core: report an invitation the agent already used

Accepting a contact request that was already accepted reached the user as

  error agent CMD PROHIBITED SEInvitationNotFound acceptContact, invitationId = "..."

The chat layer cannot tell that case from an accept that failed: the contact
connection is committed at ConnNew before acceptContact is called, and a
successful accept leaves it at ConnNew as well, until the peer's CONF arrives.
A guard on the connection would reject retrying an accept that failed with a
network error - covered by "retry connecting via contact link".

The agent can tell, because it owns the invitation and clears it in the right
order: getInvitation requires accepted = 0, joinConn can fail, and only then
does acceptInvitation set accepted = 1. So SEInvitationNotFound means the
request was accepted, while a join that failed leaves the invitation for the
retry to find. Its CMD PROHIBITED is translated rather than re-derived; within
acceptContact' that error has no other source.
This commit is contained in:
Narasimha-sc committed 2026-08-20 14:49:16 +00:00
1 parent 66d6466a9e
commit 226c43f395
1 file changed
+8 -5
+8 -5
View File
@@ -914,16 +914,19 @@ acceptContactRequest nm user@User {userId} UserContactRequest {agentInvitationId
forM_ xContactId $ \xcId -> setContactAcceptedXContactId db ct xcId
createAcceptedContactConn db user userContactLinkId_ contactId connId chatV cReqChatVRange pqSup' incognitoProfile subMode currentTs
pure (ct {activeConn = Just conn} :: Contact, conn, incognitoProfile)
-- contact connection in any status other than prepared is created when the request is accepted
-- (see createAcceptedContactConn), so the request was already accepted -
-- accepting it again would fail in the agent, as the invitation is already used.
Just Connection {connStatus} | connStatus /= ConnPrepared -> throwCmdError "contact request already accepted"
Just conn@Connection {customUserProfileId} -> do
incognitoProfile <- forM customUserProfileId $ \pId -> withFastStore $ \db -> getProfileById db userId pId
pure (ct, conn, ExistingIncognito <$> incognitoProfile)
profileToSend <- presentUserBadge user incognitoProfile $ userProfileDirect user (fromIncognitoProfile <$> incognitoProfile) (Just ct) True
dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend
(ct,conn,) <$> withAgent (\a -> acceptContact a nm (aUserId user) (aConnId conn) True invId dm pqSup' subMode)
sqSecured <-
withAgent (\a -> acceptContact a nm (aUserId user) (aConnId conn) True invId dm pqSup' subMode)
-- the agent marks the invitation accepted only once joining succeeded (see acceptContact'),
-- so not finding it is what tells an accepted request from an accept that failed and can be retried
`catchAllErrors` \case
ChatErrorAgent {agentError = CMD PROHIBITED _} -> throwCmdError "contact request already accepted"
e -> throwError e
pure (ct, conn, sqSecured)
acceptContactRequestAsync :: User -> Int64 -> Contact -> UserContactRequest -> Maybe IncognitoProfile -> CM Contact
acceptContactRequestAsync