diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 6b757e795f..2067477980 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -2734,6 +2734,7 @@ public enum RelayStatus: String, Decodable, Equatable, Hashable { case new case invited case accepted + case acknowledgedRoster case active case inactive case rejected @@ -2809,6 +2810,7 @@ extension RelayStatus { case .new: "new" case .invited: "invited" case .accepted: "accepted" + case .acknowledgedRoster: "acknowledged roster" case .active: "active" case .inactive: "inactive" case .rejected: "rejected" diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index fa2051a761..ecfb58fdb0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -2415,6 +2415,7 @@ enum class RelayStatus { @SerialName("new") New, @SerialName("invited") Invited, @SerialName("accepted") Accepted, + @SerialName("acknowledgedRoster") AcknowledgedRoster, @SerialName("active") Active, @SerialName("inactive") Inactive, @SerialName("rejected") Rejected; @@ -2423,6 +2424,7 @@ enum class RelayStatus { New -> generalGetString(MR.strings.relay_status_new) Invited -> generalGetString(MR.strings.relay_status_invited) Accepted -> generalGetString(MR.strings.relay_status_accepted) + AcknowledgedRoster -> generalGetString(MR.strings.relay_status_acknowledged_roster) Active -> generalGetString(MR.strings.relay_status_active) Inactive -> generalGetString(MR.strings.relay_status_inactive) Rejected -> generalGetString(MR.strings.relay_status_rejected) diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index e943e0080a..4e25328157 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -3008,6 +3008,7 @@ new invited accepted + acknowledged roster active inactive rejected diff --git a/bots/api/TYPES.md b/bots/api/TYPES.md index 37eb4c20a0..3d9aee7d42 100644 --- a/bots/api/TYPES.md +++ b/bots/api/TYPES.md @@ -3453,6 +3453,7 @@ ParseError: - "new" - "invited" - "accepted" +- "acknowledgedRoster" - "active" - "inactive" - "rejected" diff --git a/packages/simplex-chat-client/types/typescript/src/types.ts b/packages/simplex-chat-client/types/typescript/src/types.ts index 8a1730add6..f44cbeafeb 100644 --- a/packages/simplex-chat-client/types/typescript/src/types.ts +++ b/packages/simplex-chat-client/types/typescript/src/types.ts @@ -3813,6 +3813,7 @@ export enum RelayStatus { New = "new", Invited = "invited", Accepted = "accepted", + AcknowledgedRoster = "acknowledgedRoster", Active = "active", Inactive = "inactive", Rejected = "rejected", diff --git a/packages/simplex-chat-python/src/simplex_chat/types/_types.py b/packages/simplex-chat-python/src/simplex_chat/types/_types.py index 5455ff76c1..744e86b735 100644 --- a/packages/simplex-chat-python/src/simplex_chat/types/_types.py +++ b/packages/simplex-chat-python/src/simplex_chat/types/_types.py @@ -2671,7 +2671,7 @@ class RelayProfile(TypedDict): shortDescr: NotRequired[str] image: NotRequired[str] -RelayStatus = Literal["new", "invited", "accepted", "active", "inactive", "rejected"] +RelayStatus = Literal["new", "invited", "accepted", "acknowledgedRoster", "active", "inactive", "rejected"] ReportReason = Literal["spam", "content", "community", "profile", "other"] diff --git a/src/Simplex/Chat/Library/Commands.hs b/src/Simplex/Chat/Library/Commands.hs index ee15c2bf3d..b0f9164048 100644 --- a/src/Simplex/Chat/Library/Commands.hs +++ b/src/Simplex/Chat/Library/Commands.hs @@ -4914,7 +4914,7 @@ runRelayGroupLinkChecks user = do then do -- TODO [relays] emit event to UI when relay own status promoted to RSActive -- CEvtGroupRelayUpdated requires GroupRelay (owner-side), not available on relay side - void $ withStore' $ \db -> updateRelayOwnStatusFromTo db gInfo RSAccepted RSActive + void $ withStore' $ \db -> updateRelayOwnStatus_ db gInfo RSActive else void $ withStore' $ \db -> updateRelayOwnStatusFromTo db gInfo RSActive RSInactive _ -> pure () _ -> pure () diff --git a/src/Simplex/Chat/Library/Subscriber.hs b/src/Simplex/Chat/Library/Subscriber.hs index e3757957cf..8e58d76f7d 100644 --- a/src/Simplex/Chat/Library/Subscriber.hs +++ b/src/Simplex/Chat/Library/Subscriber.hs @@ -1227,12 +1227,13 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = relayProfile <- liftIO (decodeLinkUserData cData) >>= \case Just RelayShortLinkData {relayProfile = p} -> pure p Nothing -> throwChatError $ CEException "relay link: no relay link data" - confId <- withStore $ \db -> do + (confId, m', relay) <- withStore $ \db -> do confId <- getRelayConfId db m liftIO $ updateGroupMemberStatus db userId m GSMemAccepted - void $ setRelayKey db cxt user m (MemberKey relayKey) relayProfile - pure confId + (m', relay) <- setRelayLinkAccepted db cxt user m (MemberKey relayKey) relayProfile + pure (confId, m', relay) allowAgentConnectionAsync user conn confId XOk + toView $ CEvtGroupRelayUpdated user gInfo m' relay else -- TODO [relays] owner: TBC failed RelayStatus? messageError "relay link: relay member ID mismatch" @@ -1399,7 +1400,7 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = updateRelay db relay@GroupRelay {groupMemberId, relayLink, relayStatus} (acc, changed, newlyActiveLinks, newlyActiveGMIds) = case relayLink of Just rLink - | rLink `elem` relayLinks && relayStatus == RSAccepted -> do + | rLink `elem` relayLinks && relayStatus == RSAcknowledgedRoster -> do relay' <- updateRelayStatus db relay RSActive pure (relay' : acc, True, rLink : newlyActiveLinks, groupMemberId : newlyActiveGMIds) | rLink `elem` relayLinks -> pure (relay : acc, changed, newlyActiveLinks, newlyActiveGMIds) @@ -3339,8 +3340,10 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = cleanupGroupRosterFile user gInfo forM_ results_ $ \results -> do emitRosterResults gInfo author rosterBrokerTs results - -- ack only while still setting up (own status RSAccepted); a serving relay must not ack broadcasts. - when (isRelay && relayOwnStatus gInfo == Just RSAccepted) $ sendRosterAck gInfo author pendingVer Nothing + -- ack while setting up (own status accepted/acknowledged); a serving (active) relay must not ack broadcasts. + when (isRelay && relayOwnStatus gInfo == Just RSAccepted || relayOwnStatus gInfo == Just RSAcknowledgedRoster) $ do + sendRosterAck gInfo author pendingVer Nothing + withStore' $ \db -> void $ updateRelayOwnStatusFromTo db gInfo RSAccepted RSAcknowledgedRoster where readAssembledRoster = case fileStatus of RFSAccepted fp -> readAt fp @@ -3405,18 +3408,18 @@ processAgentMessageConn cxt user@User {userId} corrId agentConnId agentMessage = xGrpRosterAck gInfo m ackVer err = do relay_ <- withStore' $ \db -> eitherToMaybe <$> runExceptT (getGroupRelayByGMId db (groupMemberId' m)) case relay_ of - Just relay@GroupRelay {relayStatus = RSInvited} -> case err of + Just relay@GroupRelay {relayStatus = RSAccepted} -> case err of Nothing | rosterVersion gInfo == Just ackVer -> do (relay', gLink) <- withStore $ \db -> do - relay' <- liftIO $ updateRelayStatus db relay RSAccepted + relay' <- liftIO $ updateRelayStatus db relay RSAcknowledgedRoster gLink <- getGroupLink db user gInfo pure (relay', gLink) setGroupLinkDataAsync user gInfo gLink toView $ CEvtGroupRelayUpdated user gInfo m relay' | otherwise -> messageWarning "x.grp.roster.ack: stale version, awaiting ack for the current roster" Just e -> do - relay' <- withStore' $ \db -> updateRelayStatusFromTo db relay RSInvited RSRejected + relay' <- withStore' $ \db -> updateRelayStatusFromTo db relay RSAccepted RSRejected toView $ CEvtGroupRelayUpdated user gInfo m relay' messageError $ "x.grp.roster.ack: relay could not save roster, marked rejected: " <> e _ -> pure () diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index ffd55c0d61..3ca6223a06 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -101,7 +101,7 @@ module Simplex.Chat.Store.Groups createRelayConnection, updateRelayStatus, updateRelayStatusFromTo, - setRelayKey, + setRelayLinkAccepted, setRelayLinkConfId, updateRelayCapabilities, getRelayConfId, @@ -1433,7 +1433,7 @@ getConnectedGroupRelays db GroupInfo {groupId} = AND gr.relay_status IN (?,?) |] ) - (groupId, GSMemConnected, RSAccepted, RSActive) + (groupId, GSMemConnected, RSAcknowledgedRoster, RSActive) groupRelayQuery :: Query groupRelayQuery = @@ -1665,10 +1665,18 @@ updateRelayStatus_ db relayId relayStatus = do currentTs <- getCurrentTime DB.execute db "UPDATE group_relays SET relay_status = ?, updated_at = ? WHERE group_relay_id = ?" (relayStatus, currentTs, relayId) -setRelayKey :: DB.Connection -> StoreCxt -> User -> GroupMember -> MemberKey -> Profile -> ExceptT StoreError IO (GroupMember, GroupRelay) -setRelayKey db cxt user m (MemberKey relayKey) profile = do +setRelayLinkAccepted :: DB.Connection -> StoreCxt -> User -> GroupMember -> MemberKey -> Profile -> ExceptT StoreError IO (GroupMember, GroupRelay) +setRelayLinkAccepted db cxt user m (MemberKey relayKey) profile = do let gmId = groupMemberId' m currentTs <- liftIO getCurrentTime + liftIO $ DB.execute + db + [sql| + UPDATE group_relays + SET relay_status = ?, updated_at = ? + WHERE group_member_id = ? + |] + (RSAccepted, currentTs, gmId) liftIO $ DB.execute db [sql| @@ -1861,9 +1869,9 @@ getRelayServedGroups db cxt User {userId, userContactId} = do <$> DB.query db ( groupInfoQuery - <> " WHERE g.user_id = ? AND mu.contact_id = ? AND g.relay_own_status IN (?, ?)" + <> " WHERE g.user_id = ? AND mu.contact_id = ? AND g.relay_own_status IN (?, ?, ?)" ) - (userId, userContactId, RSAccepted, RSActive) + (userId, userContactId, RSAccepted, RSAcknowledgedRoster, RSActive) getRelayInactiveGroups :: DB.Connection -> StoreCxt -> User -> NominalDiffTime -> IO [GroupInfo] getRelayInactiveGroups db cxt User {userId, userContactId} ttl = do diff --git a/src/Simplex/Chat/Types/Shared.hs b/src/Simplex/Chat/Types/Shared.hs index 1f3d9dd5a8..296268b58f 100644 --- a/src/Simplex/Chat/Types/Shared.hs +++ b/src/Simplex/Chat/Types/Shared.hs @@ -89,6 +89,7 @@ data RelayStatus = RSNew -- only for owner | RSInvited | RSAccepted + | RSAcknowledgedRoster | RSActive | RSInactive | RSRejected @@ -99,6 +100,7 @@ relayStatusText = \case RSNew -> "new" RSInvited -> "invited" RSAccepted -> "accepted" + RSAcknowledgedRoster -> "acknowledged_roster" RSActive -> "active" RSInactive -> "inactive" RSRejected -> "rejected" @@ -108,6 +110,7 @@ instance TextEncoding RelayStatus where RSNew -> "new" RSInvited -> "invited" RSAccepted -> "accepted" + RSAcknowledgedRoster -> "acknowledged_roster" RSActive -> "active" RSInactive -> "inactive" RSRejected -> "rejected" @@ -115,6 +118,7 @@ instance TextEncoding RelayStatus where "new" -> Just RSNew "invited" -> Just RSInvited "accepted" -> Just RSAccepted + "acknowledged_roster" -> Just RSAcknowledgedRoster "active" -> Just RSActive "inactive" -> Just RSInactive "rejected" -> Just RSRejected