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