core: announce added relay (#6956)

This commit is contained in:
spaced4ndy
2026-05-12 12:36:23 +00:00
committed by GitHub
parent e63c403623
commit 24859e1281
9 changed files with 899 additions and 22 deletions
+3 -3
View File
@@ -2138,7 +2138,8 @@ processChatCommand vr nm = \case
_ -> Nothing
void $ createLinkOwnerMember db vr user gInfo' ctId_ (MemberId ownerId) ownerKey
pure gInfo'
rs <- mapConcurrently (connectToRelay user gInfo') relays
rs <- withGroupLock "connectPreparedGroup" groupId $
mapConcurrently (connectToRelay user gInfo') relays
let relayFailed = \case (_, _, Left _) -> True; _ -> False
(failed, succeeded) = partition relayFailed rs
if null succeeded
@@ -3619,7 +3620,7 @@ processChatCommand vr nm = \case
localRelayLinks = mapMaybe memberRelayLink activeRelayMembers
newRelayLinks = filter (`notElem` localRelayLinks) currentRelayLinks
forM_ newRelayLinks $ \rlnk -> void . tryAllErrors $
connectToRelay user gInfo rlnk
connectToRelayAsync user gInfo rlnk
forM_ localRelayMembers $ \m ->
case memberRelayLink m of
-- Remove relay if its link is no longer in the current link data.
@@ -3631,7 +3632,6 @@ processChatCommand vr nm = \case
deleteMemberConnection m
deleteOrUpdateMemberRecord user gInfo m
_ -> pure ()
prepareContact :: User -> ConnReqContact -> PQSupport -> CM (ConnId, VersionChat)
prepareContact user cReq pqSup = do
-- 0) toggle disabled - PQSupportOff
+12
View File
@@ -1321,6 +1321,18 @@ setGroupLinkDataAsync user gInfo gLink = do
let (userLinkData, crClientData) = groupLinkData gInfo gLink groupRelays
setAgentConnShortLinkAsync user conn userLinkData (Just crClientData)
connectToRelayAsync :: User -> GroupInfo -> ShortLinkContact -> CM ()
connectToRelayAsync user gInfo relayLink = do
vr <- chatVersionRange
gVar <- asks random
relayMember@GroupMember {activeConn} <- withFastStore $ \db -> getCreateRelayForMember db vr gVar user gInfo relayLink
case activeConn of
Just _ -> pure ()
Nothing -> do
subMode <- chatReadVar subscriptionMode
newConnIds <- getAgentConnShortLinkAsync user CFGetRelayDataJoin Nothing relayLink
withFastStore' $ \db -> createRelayMemberConnectionAsync db user gInfo relayMember relayLink newConnIds subMode
updatePublicGroupData :: User -> GroupInfo -> CM GroupInfo
updatePublicGroupData user gInfo
| useRelays' gInfo && memberRole' (membership gInfo) == GROwner = do
+33 -9
View File
@@ -1008,6 +1008,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
XInfo p -> fmap ctx <$> xInfoMember gInfo' m'' p msg brokerTs
XGrpLinkMem p -> Nothing <$ xGrpLinkMem gInfo' m'' conn' p
XGrpLinkAcpt acceptance role memberId -> Nothing <$ xGrpLinkAcpt gInfo' m'' acceptance role memberId msg brokerTs
XGrpRelayNew rl -> fmap ctx <$> xGrpRelayNew gInfo' m'' rl
XGrpMemNew memInfo msgScope -> fmap ctx <$> xGrpMemNew gInfo' m'' memInfo msgScope msg brokerTs
XGrpMemIntro memInfo memRestrictions_ -> Nothing <$ xGrpMemIntro gInfo' m'' memInfo memRestrictions_
XGrpMemInv memId introInv -> Nothing <$ xGrpMemInv gInfo' m'' memId introInv
@@ -1303,23 +1304,38 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
CFSetShortLink ->
case (ucGroupId_, auData) of
(Just groupId, UserContactLinkData UserContactData {relays = relayLinks}) -> do
(gInfo, gLink, relays, relaysChanged) <- withStore $ \db -> do
(gInfo, gLink, relays, relaysChanged, newlyActiveLinks) <- withStore $ \db -> do
gInfo <- getGroupInfo db vr user groupId
gLink <- getGroupLink db user gInfo
relays <- liftIO $ getGroupRelays db gInfo
(relays', changed) <- liftIO $ foldrM (updateRelay db) ([], False) relays
(relays', changed, newlyActive) <- liftIO $ foldrM (updateRelay db) ([], False, []) relays
liftIO $ setGroupInProgressDone db gInfo
pure (gInfo, gLink, relays', changed)
pure (gInfo, gLink, relays', changed, newlyActive)
toView $ CEvtGroupLinkDataUpdated user gInfo gLink relays relaysChanged
let GroupSummary {publicMemberCount} = groupSummary gInfo
-- Owner is counted in publicMemberCount; > 1 means at least one subscriber.
-- TODO [relays] multi-owner: with N owners, threshold should be > N (or use a
-- dedicated subscriber count).
when (fromMaybe 0 publicMemberCount > 1) $
forM_ (L.nonEmpty newlyActiveLinks) $ \newlyActive -> do
allRelayMembers <- withFastStore' $ \db -> getGroupRelayMembers db vr user gInfo
let recipients =
filter
(\GroupMember {memberStatus, relayLink} ->
memberStatus == GSMemConnected && relayLink `notElem` map Just newlyActiveLinks)
allRelayMembers
events = XGrpRelayNew <$> newlyActive
unless (null recipients) $
void $ sendGroupMessages user gInfo Nothing False recipients events
where
updateRelay :: DB.Connection -> GroupRelay -> ([GroupRelay], Bool) -> IO ([GroupRelay], Bool)
updateRelay db relay@GroupRelay {relayLink, relayStatus} (acc, changed) =
updateRelay :: DB.Connection -> GroupRelay -> ([GroupRelay], Bool, [ShortLinkContact]) -> IO ([GroupRelay], Bool, [ShortLinkContact])
updateRelay db relay@GroupRelay {relayLink, relayStatus} (acc, changed, newlyActive) =
case relayLink of
Just rLink
| rLink `elem` relayLinks && relayStatus == RSAccepted -> do
relay' <- updateRelayStatus db relay RSActive
pure (relay' : acc, True)
| rLink `elem` relayLinks -> pure (relay : acc, changed)
pure (relay' : acc, True, rLink : newlyActive)
| rLink `elem` relayLinks -> pure (relay : acc, changed, newlyActive)
| relayStatus == RSActive -> do
-- Relay link absent from link data — deactivate.
-- RSAccepted relays are not deactivated: their own link data update
@@ -1328,8 +1344,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
-- TODO the SMP server, but this owner won't receive a LINK callback for it
-- TODO (LINK only fires in response to own setConnShortLink calls).
relay' <- updateRelayStatus db relay RSInactive
pure (relay' : acc, True)
_ -> pure (relay : acc, changed)
pure (relay' : acc, True, newlyActive)
_ -> pure (relay : acc, changed, newlyActive)
_ -> throwChatError $ CECommandError "LINK event expected for a group link only"
_ -> throwChatError $ CECommandError "unexpected cmdFunction"
MERR _ err -> do
@@ -3247,6 +3263,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
let cd = CDGroupRcv g'' scopeInfo m'
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g''
xGrpRelayNew :: GroupInfo -> GroupMember -> ShortLinkContact -> CM (Maybe DeliveryJobScope)
xGrpRelayNew gInfo GroupMember {memberRole} rl
| memberRole < GROwner = messageError "x.grp.relay.new with insufficient member permissions" $> Nothing
| otherwise = do
unless (isUserGrpFwdRelay gInfo) $ connectToRelayAsync user gInfo rl
pure $ Just DJSGroup {jobSpec = DJDeliveryJob {includePending = False}}
xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> CM ()
xGrpDirectInv g@GroupInfo {groupId, groupProfile = gp} m mConn@Connection {connId = mConnId} connReq mContent_ msg brokerTs
| not (groupFeatureMemberAllowed SGFDirectMessages m g) = messageError "x.grp.direct.inv: direct messages not allowed"
@@ -3368,6 +3391,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
XMsgReact sharedMsgId memId scope_ reaction add -> withAuthor XMsgReact_ $ \author -> void $ groupMsgReaction gInfo author sharedMsgId memId scope_ reaction add rcvMsg msgTs
XFileCancel sharedMsgId -> void $ xFileCancelGroup gInfo author_ sharedMsgId
XInfo p -> withAuthor XInfo_ $ \author -> void $ xInfoMember gInfo author p rcvMsg msgTs
XGrpRelayNew rl -> withAuthor XGrpRelayNew_ $ \author -> void $ xGrpRelayNew gInfo author rl
XGrpMemNew memInfo msgScope -> withAuthor XGrpMemNew_ $ \author -> void $ xGrpMemNew gInfo author memInfo msgScope rcvMsg msgTs
XGrpMemRole memId memRole -> withAuthor XGrpMemRole_ $ \author -> void $ xGrpMemRole gInfo author memId memRole rcvMsg msgTs
XGrpMemRestrict memId memRestrictions -> withAuthor XGrpMemRestrict_ $ \author -> void $ xGrpMemRestrict gInfo author memId memRestrictions rcvMsg msgTs
+9
View File
@@ -443,6 +443,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
XGrpRelayInv :: GroupRelayInvitation -> ChatMsgEvent 'Json
XGrpRelayAcpt :: ShortLinkContact -> ChatMsgEvent 'Json
XGrpRelayTest :: ByteString -> Maybe ByteString -> ChatMsgEvent 'Json
XGrpRelayNew :: ShortLinkContact -> ChatMsgEvent 'Json
XGrpMemNew :: MemberInfo -> Maybe MsgScope -> ChatMsgEvent 'Json
XGrpMemIntro :: MemberInfo -> Maybe MemberRestrictions -> ChatMsgEvent 'Json
XGrpMemInv :: MemberId -> IntroInvitation -> ChatMsgEvent 'Json
@@ -492,6 +493,7 @@ isForwardedGroupMsg ev = case ev of
XMsgReact {} -> True
XFileCancel _ -> True
XInfo _ -> True
XGrpRelayNew _ -> True
XGrpMemNew {} -> True
XGrpMemRole {} -> True
XGrpMemRestrict {} -> True
@@ -986,6 +988,7 @@ data CMEventTag (e :: MsgEncoding) where
XGrpRelayInv_ :: CMEventTag 'Json
XGrpRelayAcpt_ :: CMEventTag 'Json
XGrpRelayTest_ :: CMEventTag 'Json
XGrpRelayNew_ :: CMEventTag 'Json
XGrpMemNew_ :: CMEventTag 'Json
XGrpMemIntro_ :: CMEventTag 'Json
XGrpMemInv_ :: CMEventTag 'Json
@@ -1043,6 +1046,7 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where
XGrpRelayInv_ -> "x.grp.relay.inv"
XGrpRelayAcpt_ -> "x.grp.relay.acpt"
XGrpRelayTest_ -> "x.grp.relay.test"
XGrpRelayNew_ -> "x.grp.relay.new"
XGrpMemNew_ -> "x.grp.mem.new"
XGrpMemIntro_ -> "x.grp.mem.intro"
XGrpMemInv_ -> "x.grp.mem.inv"
@@ -1101,6 +1105,7 @@ instance StrEncoding ACMEventTag where
"x.grp.relay.inv" -> XGrpRelayInv_
"x.grp.relay.acpt" -> XGrpRelayAcpt_
"x.grp.relay.test" -> XGrpRelayTest_
"x.grp.relay.new" -> XGrpRelayNew_
"x.grp.mem.new" -> XGrpMemNew_
"x.grp.mem.intro" -> XGrpMemIntro_
"x.grp.mem.inv" -> XGrpMemInv_
@@ -1155,6 +1160,7 @@ toCMEventTag msg = case msg of
XGrpRelayInv _ -> XGrpRelayInv_
XGrpRelayAcpt _ -> XGrpRelayAcpt_
XGrpRelayTest {} -> XGrpRelayTest_
XGrpRelayNew _ -> XGrpRelayNew_
XGrpMemNew {} -> XGrpMemNew_
XGrpMemIntro _ _ -> XGrpMemIntro_
XGrpMemInv _ _ -> XGrpMemInv_
@@ -1227,6 +1233,7 @@ requiresSignature = \case
XGrpMemRole_ -> True
XGrpMemRestrict_ -> True
XGrpLeave_ -> True
XGrpRelayNew_ -> True
XInfo_ -> True
_ -> False
@@ -1311,6 +1318,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
B64UrlByteString challenge <- p "challenge"
sig_ <- fmap (\(B64UrlByteString s) -> s) <$> opt "signature"
pure $ XGrpRelayTest challenge sig_
XGrpRelayNew_ -> XGrpRelayNew <$> p "relayLink"
XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo" <*> opt "scope"
XGrpMemIntro_ -> XGrpMemIntro <$> p "memberInfo" <*> opt "memberRestrictions"
XGrpMemInv_ -> XGrpMemInv <$> p "memberId" <*> p "memberIntro"
@@ -1380,6 +1388,7 @@ chatToAppMessage chatMsg@ChatMessage {chatVRange, msgId, chatMsgEvent} = case en
XGrpRelayTest challenge sig_ -> o $
("signature" .=? (B64UrlByteString <$> sig_))
["challenge" .= B64UrlByteString challenge]
XGrpRelayNew relayLink -> o ["relayLink" .= relayLink]
XGrpMemNew memInfo scope -> o $ ("scope" .=? scope) ["memberInfo" .= memInfo]
XGrpMemIntro memInfo memRestrictions -> o $ ("memberRestrictions" .=? memRestrictions) ["memberInfo" .= memInfo]
XGrpMemInv memId memIntro -> o ["memberId" .= memId, "memberIntro" .= memIntro]
+12 -7
View File
@@ -1381,7 +1381,12 @@ getCreateRelayForMember db vr gVar user@User {userId, userContactId} GroupInfo {
maybeFirstRow (toContactMember vr user) $
DB.query
db
(groupMemberQuery <> " WHERE m.group_id = ? AND m.relay_link = ?")
#if defined(dbPostgres)
(groupMemberQuery <> " WHERE m.group_id = ? AND m.relay_link = ? AND is_current_member(m.member_status)")
#else
-- skips GSMemLeft historical rows so re-add allocates a fresh row instead of resurrecting
(groupMemberQuery <> " JOIN group_member_status_predicates sp ON m.member_status = sp.member_status WHERE m.group_id = ? AND m.relay_link = ? AND sp.current_member = 1")
#endif
(groupId, relayLink)
createRelayMember = do
currentTs <- liftIO getCurrentTime
@@ -1839,12 +1844,12 @@ updatePublicMemberCount db vr user GroupInfo {groupId} = do
relayCount <- fromMaybe 0 <$> maybeFirstRow fromOnly
(DB.query
db
[sql|
SELECT COUNT(1) FROM group_members
WHERE group_id = ? AND member_role = ?
AND member_status IN (?,?,?,?,?,?,?)
|]
(groupId, GRRelay, GSMemIntroduced, GSMemIntroInvited, GSMemAccepted, GSMemAnnounced, GSMemConnected, GSMemComplete, GSMemCreator))
#if defined(dbPostgres)
"SELECT COUNT(1) FROM group_members WHERE group_id = ? AND member_role = ? AND is_current_member(member_status)"
#else
"SELECT COUNT(1) FROM group_members m JOIN group_member_status_predicates sp ON m.member_status = sp.member_status WHERE m.group_id = ? AND m.member_role = ? AND sp.current_member = 1"
#endif
(groupId, GRRelay))
let publicCount = max 0 (totalCount - relayCount) :: Int64
currentTs <- getCurrentTime
DB.execute db "UPDATE groups SET public_member_count = ?, updated_at = ? WHERE group_id = ?" (publicCount, currentTs, groupId)