mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-07-12 07:29:03 +00:00
wip
This commit is contained in:
@@ -407,6 +407,7 @@ data ChatCommand
|
||||
| SetUserChatRelays [CLINewRelay]
|
||||
| APITestChatRelay UserId ShortLinkContact
|
||||
| TestChatRelay ShortLinkContact
|
||||
| APIAllowRelayGroup {groupId :: GroupId}
|
||||
| APIGetServerOperators
|
||||
| APISetServerOperators (NonEmpty ServerOperator)
|
||||
| SetServerOperators (NonEmpty ServerOperatorRoles)
|
||||
@@ -735,6 +736,7 @@ data ChatResponse
|
||||
| CRPublicGroupCreated {user :: User, groupInfo :: GroupInfo, groupLink :: GroupLink, groupRelays :: [GroupRelay]}
|
||||
| CRPublicGroupCreationFailed {user :: User, addRelayResults :: [AddRelayResult]}
|
||||
| CRGroupRelays {user :: User, groupInfo :: GroupInfo, groupRelays :: [GroupRelay]}
|
||||
| CRRelayGroupAllowed {user :: User, groupInfo :: GroupInfo}
|
||||
| CRGroupRelaysAdded {user :: User, groupInfo :: GroupInfo, groupLink :: GroupLink, groupRelays :: [GroupRelay]}
|
||||
| CRGroupRelaysAddFailed {user :: User, addRelayResults :: [AddRelayResult]}
|
||||
| CRGroupMembers {user :: User, group :: Group}
|
||||
@@ -945,6 +947,7 @@ data ChatEvent
|
||||
|
||||
data TerminalEvent
|
||||
= TEGroupLinkRejected {user :: User, groupInfo :: GroupInfo, groupRejectionReason :: GroupRejectionReason}
|
||||
| TERelayRejected {user :: User, groupInfo :: GroupInfo, relayRejectionReason :: RelayRejectionReason}
|
||||
| TERejectingGroupJoinRequestMember {user :: User, groupInfo :: GroupInfo, member :: GroupMember, groupRejectionReason :: GroupRejectionReason}
|
||||
| TENewMemberContact {user :: User, contact :: Contact, groupInfo :: GroupInfo, member :: GroupMember}
|
||||
| TEContactVerificationReset {user :: User, contact :: Contact}
|
||||
|
||||
@@ -1244,6 +1244,8 @@ processChatCommand vr nm = \case
|
||||
let isOwner = memberRole' membership == GROwner
|
||||
canDelete = isOwner || not (memberCurrent membership)
|
||||
unless canDelete $ throwChatError $ CEGroupUserRole gInfo GROwner
|
||||
when (relayOwnStatus gInfo == Just RSRejected) $
|
||||
throwChatError $ CECommandError "cannot delete a rejected channel; run /_relay allow <groupId> first"
|
||||
filesInfo <- withFastStore' $ \db -> getGroupFileInfo db user gInfo
|
||||
withGroupLock "deleteChat group" chatId $ do
|
||||
deleteCIFiles user filesInfo
|
||||
@@ -1578,6 +1580,9 @@ processChatCommand vr nm = \case
|
||||
Right (Just (Just failure)) -> pure $ CRChatRelayTestResult user (Just relayProfile) (Just failure)
|
||||
TestChatRelay address -> withUser $ \User {userId} ->
|
||||
processChatCommand vr nm $ APITestChatRelay userId address
|
||||
APIAllowRelayGroup groupId -> withUser $ \user -> do
|
||||
gInfo' <- withStore $ \db -> allowRelayGroupAndSiblings db vr user groupId
|
||||
pure $ CRRelayGroupAllowed user gInfo'
|
||||
GetUserChatRelays -> withUser $ \user -> do
|
||||
srvs <- withFastStore (`getUserServers` user)
|
||||
liftIO $ CRUserServers user <$> groupByOperator (onlyRelays srvs)
|
||||
@@ -2932,6 +2937,8 @@ processChatCommand vr nm = \case
|
||||
deleteGroupLinkIfExists user gInfo'
|
||||
-- member records are not deleted to keep history
|
||||
withFastStore' $ \db -> updateGroupMemberStatus db userId membership GSMemLeft
|
||||
when (useRelays' gInfo && isRelay membership) $
|
||||
withFastStore' $ \db -> updateRelayOwnStatus_ db gInfo RSRejected
|
||||
pure $ CRLeftMemberUser user gInfo' {membership = membership {memberStatus = GSMemLeft}}
|
||||
where
|
||||
-- Relay leaving channel: create delivery job for cursor-based sending and async connection cleanup.
|
||||
@@ -5032,6 +5039,8 @@ chatCommandP =
|
||||
"/xftp" $> GetUserProtoServers (AProtocolType SPXFTP),
|
||||
"/_relay test " *> (APITestChatRelay <$> A.decimal <* A.space <*> strP),
|
||||
"/relay test " *> (TestChatRelay <$> strP),
|
||||
"/_relay allow " *> (APIAllowRelayGroup <$> A.decimal),
|
||||
"/relay allow " *> (APIAllowRelayGroup <$> A.decimal),
|
||||
"/relays " *> (SetUserChatRelays <$> chatRelaysP),
|
||||
"/relays" $> GetUserChatRelays,
|
||||
"/_operators" $> APIGetServerOperators,
|
||||
|
||||
@@ -1059,6 +1059,32 @@ acceptRelayJoinRequestAsync
|
||||
ownerMember' <- getGroupMemberById db vr user groupMemberId
|
||||
pure (gInfo', ownerMember')
|
||||
|
||||
-- Asynchronous rejection of a relay invitation. Mirrors acceptGroupJoinSendRejectAsync:
|
||||
-- creates a transient groups row tagged RSRejected (so the worker ignores it) with the
|
||||
-- owner member in GSMemInvited so the eventual INFO arrival drives cleanup, then enqueues
|
||||
-- agentAcceptContactAsync to send XGrpRelayReject through the agent's normal retry path.
|
||||
acceptRelayJoinRequestRejectAsync
|
||||
:: User
|
||||
-> Int64
|
||||
-> VersionRangeChat
|
||||
-> GroupRelayInvitation
|
||||
-> InvitationId
|
||||
-> VersionRangeChat
|
||||
-> Int64
|
||||
-> RelayRejectionReason
|
||||
-> CM ()
|
||||
acceptRelayJoinRequestRejectAsync user uclId vr groupRelayInv invId reqChatVRange initialDelay reason = do
|
||||
(_gInfo, ownerMember) <- withStore $ \db ->
|
||||
createRelayRequestGroup db vr user groupRelayInv invId reqChatVRange initialDelay GSMemInvited RSRejected
|
||||
let GroupMember {groupMemberId} = ownerMember
|
||||
msg = XGrpRelayReject reason
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
chatVR <- chatVersionRange
|
||||
let chatV = chatVR `peerConnChatVersion` reqChatVRange
|
||||
connIds <- agentAcceptContactAsync user False invId msg subMode PQSupportOff chatV
|
||||
withStore' $ \db ->
|
||||
createJoiningMemberConnection db user uclId connIds chatV reqChatVRange groupMemberId subMode
|
||||
|
||||
businessGroupProfile :: Profile -> GroupPreferences -> GroupProfile
|
||||
businessGroupProfile Profile {displayName, fullName, shortDescr, image} groupPreferences =
|
||||
GroupProfile {displayName, fullName, description = Nothing, shortDescr, image, publicGroup = Nothing, groupPreferences = Just groupPreferences, memberAdmission = Nothing}
|
||||
|
||||
@@ -770,6 +770,18 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
withStore' $ \db -> setRelayLinkConfId db m confId relayLink
|
||||
void $ getAgentConnShortLinkAsync user CFGetRelayDataAccept (Just conn') relayLink
|
||||
| otherwise -> messageError "x.grp.relay.acpt: only owner can add relay"
|
||||
XGrpRelayReject reason
|
||||
| memberRole' membership == GROwner && isRelay m -> do
|
||||
relay <- withStore $ \db -> do
|
||||
relay <- getGroupRelayByGMId db (groupMemberId' m)
|
||||
liftIO $ updateRelayStatusFromTo db relay RSInvited RSRejected
|
||||
-- complete the contact handshake so the relay receives INFO and cleans
|
||||
-- up its transient bookkeeping (the new INFO arm at GCHostMember +
|
||||
-- RSRejected handles that), then tear down our chat-layer connection.
|
||||
allowAgentConnectionAsync user conn' confId XOk
|
||||
toView $ CEvtGroupRelayUpdated user gInfo m relay
|
||||
toViewTE $ TERelayRejected user gInfo reason
|
||||
| otherwise -> messageError "x.grp.relay.reject: only owner can receive relay rejection"
|
||||
_ -> messageError "CONF from invited member must have x.grp.acpt"
|
||||
GCHostMember ->
|
||||
case chatMsgEvent of
|
||||
@@ -812,14 +824,33 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
pure ()
|
||||
| otherwise -> messageError "x.grp.mem.info: memberId is different from expected"
|
||||
-- sent when connecting via group link
|
||||
XInfo _ ->
|
||||
-- TODO Keep rejected member to allow them to appeal against rejection.
|
||||
when (memberStatus m == GSMemRejected) $ do
|
||||
deleteMemberConnection' m True
|
||||
withStore' $ \db -> deleteGroupMember db user m
|
||||
XOk -> pure ()
|
||||
XInfo _
|
||||
| memberStatus m == GSMemRejected -> do
|
||||
-- TODO Keep rejected member to allow them to appeal against rejection.
|
||||
deleteMemberConnection' m True
|
||||
withStore' $ \db -> deleteGroupMember db user m
|
||||
| cleanupTransientRelayReject -> cleanupRelayRejectRow
|
||||
| otherwise -> pure ()
|
||||
XOk
|
||||
| cleanupTransientRelayReject -> cleanupRelayRejectRow
|
||||
| otherwise -> pure ()
|
||||
_ -> messageError "INFO from member must have x.grp.mem.info, x.info or x.ok"
|
||||
pure ()
|
||||
where
|
||||
-- Transient relay-reject row cleanup. The transient row is created with
|
||||
-- RSRejected + GCHostMember owner. The persistent /leave-time row has the
|
||||
-- same combination but its host connection is deleted by DJRelayRemoved
|
||||
-- before any INFO can fire, so this filter only matches transients.
|
||||
-- RSInactive is also matched to handle the case where APIAllowRelayGroup
|
||||
-- flipped the transient row mid-flight.
|
||||
cleanupTransientRelayReject =
|
||||
memberCategory m == GCHostMember
|
||||
&& maybe False (`elem` ([RSRejected, RSInactive] :: [RelayStatus])) (relayOwnStatus gInfo)
|
||||
cleanupRelayRejectRow = do
|
||||
deleteMemberConnection' m True
|
||||
withStore' $ \db -> do
|
||||
deleteGroupMember db user m
|
||||
deleteGroup db user gInfo
|
||||
CON _pqEnc -> unless (memberStatus m == GSMemRejected || memberStatus membership == GSMemRejected) $ do
|
||||
-- TODO [knocking] send pending messages after accepting?
|
||||
-- possible improvement: check for each pending message, requires keeping track of connection state
|
||||
@@ -933,7 +964,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
if isUserGrpFwdRelay gInfo' && not (blockedByAdmin m)
|
||||
then
|
||||
let tasks
|
||||
| relayOwnStatus gInfo' == Just RSInactive = filter relayRemovedNewTask newDeliveryTasks
|
||||
| relayNotServing (relayOwnStatus gInfo') = filter relayRemovedNewTask newDeliveryTasks
|
||||
| otherwise = newDeliveryTasks
|
||||
in createDeliveryTasks gInfo' m' tasks
|
||||
else pure False
|
||||
@@ -1522,10 +1553,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
mem <- acceptGroupJoinSendRejectAsync user uclId gInfo invId chatVRange p xContactId_ rjctReason
|
||||
toViewTE $ TERejectingGroupJoinRequestMember user gInfo mem rjctReason
|
||||
xGrpRelayInv :: InvitationId -> VersionRangeChat -> GroupRelayInvitation -> CM ()
|
||||
xGrpRelayInv invId chatVRange groupRelayInv = do
|
||||
xGrpRelayInv invId chatVRange groupRelayInv@GroupRelayInvitation {groupLink} = do
|
||||
refused <- withStore' $ \db -> isRelayGroupRefused db user groupLink
|
||||
initialDelay <- asks $ initialInterval . relayRequestRetryInterval . config
|
||||
(_gInfo, _ownerMember) <- withStore $ \db -> createRelayRequestGroup db vr user groupRelayInv invId chatVRange initialDelay
|
||||
lift $ void $ getRelayRequestWorker True
|
||||
if refused
|
||||
then acceptRelayJoinRequestRejectAsync user uclId vr groupRelayInv invId chatVRange initialDelay RRRRejoinRefused
|
||||
else do
|
||||
(_gInfo, _ownerMember) <- withStore $ \db ->
|
||||
createRelayRequestGroup db vr user groupRelayInv invId chatVRange initialDelay GSMemAccepted RSInvited
|
||||
lift $ void $ getRelayRequestWorker True
|
||||
xGrpRelayTest :: InvitationId -> VersionRangeChat -> ByteString -> CM ()
|
||||
xGrpRelayTest invId chatVRange challenge = do
|
||||
privKey_ <- withAgent $ \a -> getConnLinkPrivKey a (aConnId conn)
|
||||
@@ -3129,7 +3165,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
unless (isUserGrpFwdRelay gInfo) $ deleteGroupConnections user gInfo False
|
||||
withStore' $ \db -> do
|
||||
updateGroupMemberStatus db userId membership GSMemRemoved
|
||||
when (isJust $ relayOwnStatus gInfo) $ updateRelayOwnStatus_ db gInfo RSInactive
|
||||
when (maybe False (/= RSRejected) (relayOwnStatus gInfo)) $ updateRelayOwnStatus_ db gInfo RSInactive
|
||||
let membership' = membership {memberStatus = GSMemRemoved}
|
||||
when withMessages $ deleteMessages gInfo membership' SMDSnd
|
||||
deleteMemberItem msg gInfo RGEUserDeleted
|
||||
@@ -3568,7 +3604,7 @@ runDeliveryTaskWorker a deliveryKey Worker {doWork} = do
|
||||
processDeliveryTask task@MessageDeliveryTask {jobScope} =
|
||||
case jobScopeImpliedSpec jobScope of
|
||||
DJDeliveryJob _includePending
|
||||
| relayOwnStatus gInfo == Just RSInactive -> do
|
||||
| relayNotServing (relayOwnStatus gInfo) -> do
|
||||
logWarn "delivery task worker: relay inactive"
|
||||
withStore' $ \db -> setDeliveryTaskErrStatus db (deliveryTaskId task) "relay inactive"
|
||||
| otherwise ->
|
||||
@@ -3638,7 +3674,7 @@ runDeliveryJobWorker a deliveryKey Worker {doWork} = do
|
||||
processDeliveryJob job =
|
||||
case jobScopeImpliedSpec jobScope of
|
||||
DJDeliveryJob _includePending
|
||||
| relayOwnStatus gInfo == Just RSInactive -> do
|
||||
| relayNotServing (relayOwnStatus gInfo) -> do
|
||||
logWarn "delivery job worker: relay inactive"
|
||||
withStore' $ \db -> setDeliveryJobErrStatus db (deliveryJobId job) "relay inactive"
|
||||
| otherwise -> do
|
||||
|
||||
@@ -444,6 +444,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
|
||||
XGrpRelayAcpt :: ShortLinkContact -> ChatMsgEvent 'Json
|
||||
XGrpRelayTest :: ByteString -> Maybe ByteString -> ChatMsgEvent 'Json
|
||||
XGrpRelayNew :: ShortLinkContact -> ChatMsgEvent 'Json
|
||||
XGrpRelayReject :: RelayRejectionReason -> ChatMsgEvent 'Json
|
||||
XGrpMemNew :: MemberInfo -> Maybe MsgScope -> ChatMsgEvent 'Json
|
||||
XGrpMemIntro :: MemberInfo -> Maybe MemberRestrictions -> ChatMsgEvent 'Json
|
||||
XGrpMemInv :: MemberId -> IntroInvitation -> ChatMsgEvent 'Json
|
||||
@@ -989,6 +990,7 @@ data CMEventTag (e :: MsgEncoding) where
|
||||
XGrpRelayAcpt_ :: CMEventTag 'Json
|
||||
XGrpRelayTest_ :: CMEventTag 'Json
|
||||
XGrpRelayNew_ :: CMEventTag 'Json
|
||||
XGrpRelayReject_ :: CMEventTag 'Json
|
||||
XGrpMemNew_ :: CMEventTag 'Json
|
||||
XGrpMemIntro_ :: CMEventTag 'Json
|
||||
XGrpMemInv_ :: CMEventTag 'Json
|
||||
@@ -1047,6 +1049,7 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where
|
||||
XGrpRelayAcpt_ -> "x.grp.relay.acpt"
|
||||
XGrpRelayTest_ -> "x.grp.relay.test"
|
||||
XGrpRelayNew_ -> "x.grp.relay.new"
|
||||
XGrpRelayReject_ -> "x.grp.relay.reject"
|
||||
XGrpMemNew_ -> "x.grp.mem.new"
|
||||
XGrpMemIntro_ -> "x.grp.mem.intro"
|
||||
XGrpMemInv_ -> "x.grp.mem.inv"
|
||||
@@ -1106,6 +1109,7 @@ instance StrEncoding ACMEventTag where
|
||||
"x.grp.relay.acpt" -> XGrpRelayAcpt_
|
||||
"x.grp.relay.test" -> XGrpRelayTest_
|
||||
"x.grp.relay.new" -> XGrpRelayNew_
|
||||
"x.grp.relay.reject" -> XGrpRelayReject_
|
||||
"x.grp.mem.new" -> XGrpMemNew_
|
||||
"x.grp.mem.intro" -> XGrpMemIntro_
|
||||
"x.grp.mem.inv" -> XGrpMemInv_
|
||||
@@ -1161,6 +1165,7 @@ toCMEventTag msg = case msg of
|
||||
XGrpRelayAcpt _ -> XGrpRelayAcpt_
|
||||
XGrpRelayTest {} -> XGrpRelayTest_
|
||||
XGrpRelayNew _ -> XGrpRelayNew_
|
||||
XGrpRelayReject _ -> XGrpRelayReject_
|
||||
XGrpMemNew {} -> XGrpMemNew_
|
||||
XGrpMemIntro _ _ -> XGrpMemIntro_
|
||||
XGrpMemInv _ _ -> XGrpMemInv_
|
||||
@@ -1319,6 +1324,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
|
||||
sig_ <- fmap (\(B64UrlByteString s) -> s) <$> opt "signature"
|
||||
pure $ XGrpRelayTest challenge sig_
|
||||
XGrpRelayNew_ -> XGrpRelayNew <$> p "relayLink"
|
||||
XGrpRelayReject_ -> XGrpRelayReject <$> p "reason"
|
||||
XGrpMemNew_ -> XGrpMemNew <$> p "memberInfo" <*> opt "scope"
|
||||
XGrpMemIntro_ -> XGrpMemIntro <$> p "memberInfo" <*> opt "memberRestrictions"
|
||||
XGrpMemInv_ -> XGrpMemInv <$> p "memberId" <*> p "memberIntro"
|
||||
@@ -1389,6 +1395,7 @@ chatToAppMessage chatMsg@ChatMessage {chatVRange, msgId, chatMsgEvent} = case en
|
||||
("signature" .=? (B64UrlByteString <$> sig_))
|
||||
["challenge" .= B64UrlByteString challenge]
|
||||
XGrpRelayNew relayLink -> o ["relayLink" .= relayLink]
|
||||
XGrpRelayReject reason -> o ["reason" .= reason]
|
||||
XGrpMemNew memInfo scope -> o $ ("scope" .=? scope) ["memberInfo" .= memInfo]
|
||||
XGrpMemIntro memInfo memRestrictions -> o $ ("memberRestrictions" .=? memRestrictions) ["memberInfo" .= memInfo]
|
||||
XGrpMemInv memId memIntro -> o ["memberId" .= memId, "memberIntro" .= memIntro]
|
||||
|
||||
@@ -95,6 +95,8 @@ module Simplex.Chat.Store.Groups
|
||||
createRelayRequestGroup,
|
||||
updateRelayOwnStatusFromTo,
|
||||
updateRelayOwnStatus_,
|
||||
isRelayGroupRefused,
|
||||
allowRelayGroupAndSiblings,
|
||||
getRelayServedGroups,
|
||||
getRelayInactiveGroups,
|
||||
createNewContactMemberAsync,
|
||||
@@ -1523,8 +1525,8 @@ setGroupInProgressDone db GroupInfo {groupId} = do
|
||||
"UPDATE groups SET creating_in_progress = 0, updated_at = ? WHERE group_id = ?"
|
||||
(currentTs, groupId)
|
||||
|
||||
createRelayRequestGroup :: DB.Connection -> VersionRangeChat -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> Int64 -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange initialDelay = do
|
||||
createRelayRequestGroup :: DB.Connection -> VersionRangeChat -> User -> GroupRelayInvitation -> InvitationId -> VersionRangeChat -> Int64 -> GroupMemberStatus -> RelayStatus -> ExceptT StoreError IO (GroupInfo, GroupMember)
|
||||
createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMember, fromMemberProfile, relayMemberId, groupLink} invId reqChatVRange initialDelay ownerStatus relayStatus = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
-- Create group with placeholder profile
|
||||
let Profile {displayName = fromMemberLDN} = fromMemberProfile
|
||||
@@ -1538,10 +1540,10 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
|
||||
groupPreferences = Nothing,
|
||||
memberAdmission = Nothing
|
||||
}
|
||||
(groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just RSInvited) Nothing currentTs
|
||||
(groupId, _groupLDN) <- createGroup_ db userId placeholderProfile Nothing Nothing True (Just relayStatus) Nothing currentTs
|
||||
-- Store relay request data for recovery
|
||||
liftIO $ setRelayRequestData_ groupId currentTs
|
||||
ownerMemberId <- insertOwner_ currentTs groupId
|
||||
ownerMemberId <- insertOwner_ currentTs groupId ownerStatus
|
||||
let relayMember = MemberIdRole relayMemberId GRRelay
|
||||
-- TODO [member keys] should relays use member keys?
|
||||
_membership <- createContactMemberInv_ db user groupId (Just ownerMemberId) user relayMember GCUserMember GSMemAccepted IBUnknown Nothing Nothing currentTs vr
|
||||
@@ -1563,7 +1565,7 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
|
||||
WHERE group_id = ?
|
||||
|]
|
||||
(Binary invId, groupLink, minVersion reqChatVRange, maxVersion reqChatVRange, initialDelay, currentTs, groupId)
|
||||
insertOwner_ currentTs groupId = do
|
||||
insertOwner_ currentTs groupId ownerStatus_ = do
|
||||
let MemberIdRole {memberId, memberRole} = fromMember
|
||||
VersionRange minV maxV = reqChatVRange
|
||||
(localDisplayName, profileId) <- createNewMemberProfile_ db user fromMemberProfile currentTs
|
||||
@@ -1578,7 +1580,7 @@ createRelayRequestGroup db vr user@User {userId} GroupRelayInvitation {fromMembe
|
||||
peer_chat_min_version, peer_chat_max_version)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
( (groupId, indexInGroup, memberId, memberRole, GCHostMember, GSMemAccepted)
|
||||
( (groupId, indexInGroup, memberId, memberRole, GCHostMember, ownerStatus_)
|
||||
:. (userId, localDisplayName, Nothing :: (Maybe Int64), profileId, currentTs, currentTs)
|
||||
:. (minV, maxV)
|
||||
)
|
||||
@@ -1596,6 +1598,44 @@ updateRelayOwnStatus_ db GroupInfo {groupId} relayStatus = do
|
||||
let inactiveAt_ = if relayStatus == RSInactive then Just currentTs else Nothing
|
||||
DB.execute db "UPDATE groups SET relay_own_status = ?, relay_inactive_at = ?, updated_at = ? WHERE group_id = ?" (relayStatus, inactiveAt_, currentTs, groupId)
|
||||
|
||||
-- Atomically flip every RSRejected row sharing the targeted group's
|
||||
-- relay_request_group_link to RSInactive. Returns the refreshed GroupInfo
|
||||
-- for the targeted groupId (whether it was flipped or not). The subquery
|
||||
-- resolves the link in the same UPDATE statement so there is no
|
||||
-- read-then-write race with concurrent xGrpRelayInv handlers.
|
||||
allowRelayGroupAndSiblings :: DB.Connection -> VersionRangeChat -> User -> GroupId -> ExceptT StoreError IO GroupInfo
|
||||
allowRelayGroupAndSiblings db vr user@User {userId} groupId = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
liftIO $
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE groups
|
||||
SET relay_own_status = ?, relay_inactive_at = ?, updated_at = ?
|
||||
WHERE user_id = ?
|
||||
AND relay_request_group_link = (SELECT relay_request_group_link FROM groups WHERE group_id = ?)
|
||||
AND relay_own_status = ?
|
||||
|]
|
||||
(RSInactive, currentTs, currentTs, userId, groupId, RSRejected)
|
||||
getGroupInfo db vr user groupId
|
||||
|
||||
isRelayGroupRefused :: DB.Connection -> User -> ShortLinkContact -> IO Bool
|
||||
isRelayGroupRefused db User {userId} groupLink =
|
||||
fromMaybe False <$> maybeFirstRow fromOnly (
|
||||
DB.query
|
||||
db
|
||||
[sql|
|
||||
SELECT EXISTS (
|
||||
SELECT 1 FROM groups
|
||||
WHERE user_id = ?
|
||||
AND relay_request_group_link = ?
|
||||
AND relay_own_status = ?
|
||||
LIMIT 1
|
||||
)
|
||||
|]
|
||||
(userId, groupLink, RSRejected)
|
||||
)
|
||||
|
||||
getRelayServedGroups :: DB.Connection -> VersionRangeChat -> User -> IO [GroupInfo]
|
||||
getRelayServedGroups db vr User {userId, userContactId} = do
|
||||
map (toGroupInfo vr userContactId [])
|
||||
|
||||
@@ -30,6 +30,7 @@ import Simplex.Chat.Store.Postgres.Migrations.M20260222_chat_relays
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260403_item_viewed
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260429_relay_request_retries
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260507_relay_inactive_at
|
||||
import Simplex.Chat.Store.Postgres.Migrations.M20260514_relay_request_group_link_index
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Text, Maybe Text)]
|
||||
@@ -59,7 +60,8 @@ schemaMigrations =
|
||||
("20260222_chat_relays", m20260222_chat_relays, Just down_m20260222_chat_relays),
|
||||
("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed),
|
||||
("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries),
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at)
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at),
|
||||
("20260514_relay_request_group_link_index", m20260514_relay_request_group_link_index, Just down_m20260514_relay_request_group_link_index)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
{-# LANGUAGE OverloadedStrings #-}
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.Postgres.Migrations.M20260514_relay_request_group_link_index where
|
||||
|
||||
import Data.Text (Text)
|
||||
import Text.RawString.QQ (r)
|
||||
|
||||
m20260514_relay_request_group_link_index :: Text
|
||||
m20260514_relay_request_group_link_index =
|
||||
[r|
|
||||
CREATE INDEX idx_groups_relay_request_group_link
|
||||
ON groups(user_id, relay_request_group_link)
|
||||
WHERE relay_request_group_link IS NOT NULL;
|
||||
|]
|
||||
|
||||
down_m20260514_relay_request_group_link_index :: Text
|
||||
down_m20260514_relay_request_group_link_index =
|
||||
[r|
|
||||
DROP INDEX idx_groups_relay_request_group_link;
|
||||
|]
|
||||
@@ -153,6 +153,7 @@ import Simplex.Chat.Store.SQLite.Migrations.M20260222_chat_relays
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260403_item_viewed
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260429_relay_request_retries
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260507_relay_inactive_at
|
||||
import Simplex.Chat.Store.SQLite.Migrations.M20260514_relay_request_group_link_index
|
||||
import Simplex.Messaging.Agent.Store.Shared (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -305,7 +306,8 @@ schemaMigrations =
|
||||
("20260222_chat_relays", m20260222_chat_relays, Just down_m20260222_chat_relays),
|
||||
("20260403_item_viewed", m20260403_item_viewed, Just down_m20260403_item_viewed),
|
||||
("20260429_relay_request_retries", m20260429_relay_request_retries, Just down_m20260429_relay_request_retries),
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at)
|
||||
("20260507_relay_inactive_at", m20260507_relay_inactive_at, Just down_m20260507_relay_inactive_at),
|
||||
("20260514_relay_request_group_link_index", m20260514_relay_request_group_link_index, Just down_m20260514_relay_request_group_link_index)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Store.SQLite.Migrations.M20260514_relay_request_group_link_index where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20260514_relay_request_group_link_index :: Query
|
||||
m20260514_relay_request_group_link_index =
|
||||
[sql|
|
||||
CREATE INDEX idx_groups_relay_request_group_link
|
||||
ON groups(user_id, relay_request_group_link)
|
||||
WHERE relay_request_group_link IS NOT NULL;
|
||||
|]
|
||||
|
||||
down_m20260514_relay_request_group_link_index :: Query
|
||||
down_m20260514_relay_request_group_link_index =
|
||||
[sql|
|
||||
DROP INDEX idx_groups_relay_request_group_link;
|
||||
|]
|
||||
@@ -1295,6 +1295,12 @@ CREATE INDEX idx_chat_items_groups_item_viewed ON chat_items(
|
||||
item_viewed,
|
||||
item_ts
|
||||
);
|
||||
CREATE INDEX idx_groups_relay_request_group_link
|
||||
ON groups(
|
||||
user_id,
|
||||
relay_request_group_link
|
||||
)
|
||||
WHERE relay_request_group_link IS NOT NULL;
|
||||
CREATE TRIGGER on_group_members_insert_update_summary
|
||||
AFTER INSERT ON group_members
|
||||
FOR EACH ROW
|
||||
|
||||
@@ -916,6 +916,30 @@ instance ToJSON GroupRejectionReason where
|
||||
toJSON = strToJSON
|
||||
toEncoding = strToJEncoding
|
||||
|
||||
data RelayRejectionReason
|
||||
= RRRRejoinRefused
|
||||
| RRRUnknown {text :: Text}
|
||||
deriving (Eq, Show)
|
||||
|
||||
instance FromField RelayRejectionReason where fromField = blobFieldDecoder strDecode
|
||||
|
||||
instance ToField RelayRejectionReason where toField = toField . strEncode
|
||||
|
||||
instance StrEncoding RelayRejectionReason where
|
||||
strEncode = \case
|
||||
RRRRejoinRefused -> "rejoin_refused"
|
||||
RRRUnknown text -> encodeUtf8 text
|
||||
strP =
|
||||
"rejoin_refused" $> RRRRejoinRefused
|
||||
<|> RRRUnknown . safeDecodeUtf8 <$> A.takeByteString
|
||||
|
||||
instance FromJSON RelayRejectionReason where
|
||||
parseJSON = strParseJSON "RelayRejectionReason"
|
||||
|
||||
instance ToJSON RelayRejectionReason where
|
||||
toJSON = strToJSON
|
||||
toEncoding = strToJEncoding
|
||||
|
||||
data MemberIdRole = MemberIdRole
|
||||
{ memberId :: MemberId,
|
||||
memberRole :: GroupMemberRole
|
||||
|
||||
@@ -84,6 +84,7 @@ data RelayStatus
|
||||
| RSAccepted
|
||||
| RSActive
|
||||
| RSInactive
|
||||
| RSRejected
|
||||
deriving (Eq, Show)
|
||||
|
||||
relayStatusText :: RelayStatus -> Text
|
||||
@@ -93,6 +94,7 @@ relayStatusText = \case
|
||||
RSAccepted -> "accepted"
|
||||
RSActive -> "active"
|
||||
RSInactive -> "inactive"
|
||||
RSRejected -> "rejected"
|
||||
|
||||
instance TextEncoding RelayStatus where
|
||||
textEncode = \case
|
||||
@@ -101,12 +103,14 @@ instance TextEncoding RelayStatus where
|
||||
RSAccepted -> "accepted"
|
||||
RSActive -> "active"
|
||||
RSInactive -> "inactive"
|
||||
RSRejected -> "rejected"
|
||||
textDecode = \case
|
||||
"new" -> Just RSNew
|
||||
"invited" -> Just RSInvited
|
||||
"accepted" -> Just RSAccepted
|
||||
"active" -> Just RSActive
|
||||
"inactive" -> Just RSInactive
|
||||
"rejected" -> Just RSRejected
|
||||
_ -> Nothing
|
||||
|
||||
instance FromField RelayStatus where fromField = fromTextField_ textDecode
|
||||
@@ -115,6 +119,15 @@ instance ToField RelayStatus where toField = toField . textEncode
|
||||
|
||||
$(JQ.deriveJSON (enumJSON $ dropPrefix "RS") ''RelayStatus)
|
||||
|
||||
-- True for relay-own-status values that mean "relay not serving this group".
|
||||
-- Both RSInactive (relay removed or stopped) and RSRejected (relay refused to rejoin)
|
||||
-- block normal message delivery; DJRelayRemoved is handled in a status-independent branch.
|
||||
relayNotServing :: Maybe RelayStatus -> Bool
|
||||
relayNotServing = \case
|
||||
Just RSInactive -> True
|
||||
Just RSRejected -> True
|
||||
_ -> False
|
||||
|
||||
data MsgSigStatus = MSSVerified | MSSSignedNoKey
|
||||
deriving (Eq, Show)
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ chatResponseToView hu cfg@ChatConfig {logLevel, showReactions, testView} liveIte
|
||||
CRGroupRelays u g relays -> ttyUser u $ viewGroupRelays g relays
|
||||
CRGroupRelaysAdded u g _groupLink relays -> ttyUser u $ viewGroupRelays g relays
|
||||
CRGroupRelaysAddFailed u results -> ttyUser u $ viewGroupRelaysAddFailed results
|
||||
CRRelayGroupAllowed u g -> ttyUser u [ttyFullGroup g <> ": relay refusal cleared"]
|
||||
CRGroupMembers u g -> ttyUser u $ viewGroupMembers g
|
||||
CRMemberSupportChats u g ms -> ttyUser u $ viewMemberSupportChats g ms
|
||||
-- CRGroupConversationsArchived u _g _conversations -> ttyUser u []
|
||||
@@ -541,6 +542,7 @@ chatEventToView hu ChatConfig {logLevel, showReactions, showReceipts, testView}
|
||||
CEvtTerminalEvent te -> case te of
|
||||
TERejectingGroupJoinRequestMember _ g m reason -> [ttyFullMember m <> ": rejecting request to join group " <> ttyGroup' g <> ", reason: " <> sShow reason]
|
||||
TEGroupLinkRejected u g reason -> ttyUser u [ttyGroup' g <> ": join rejected, reason: " <> sShow reason]
|
||||
TERelayRejected u g reason -> ttyUser u [ttyGroup' g <> ": relay rejected, reason: " <> sShow reason]
|
||||
TENewMemberContact u _ g m -> ttyUser u ["contact for member " <> ttyGroup' g <> " " <> ttyMember m <> " is created"]
|
||||
TEContactVerificationReset u ct -> ttyUser u $ viewContactVerificationReset ct
|
||||
TEGroupMemberVerificationReset u g m -> ttyUser u $ viewGroupMemberVerificationReset g m
|
||||
@@ -1435,11 +1437,18 @@ viewGroupsList gs = map groupSS $ sortOn ldn_ gs
|
||||
where
|
||||
ldn_ :: GroupInfo -> Text
|
||||
ldn_ GroupInfo {localDisplayName} = T.toLower localDisplayName
|
||||
groupSS g@GroupInfo {membership, chatSettings = ChatSettings {enableNtfs}, groupSummary = GroupSummary {currentMembers}} =
|
||||
groupSS g@GroupInfo { membership
|
||||
, chatSettings = ChatSettings {enableNtfs}
|
||||
, groupSummary = GroupSummary {currentMembers}
|
||||
, relayOwnStatus
|
||||
} =
|
||||
case memberStatus membership of
|
||||
GSMemInvited -> groupInvitation' g
|
||||
s -> membershipIncognito g <> ttyFullGroup g <> viewMemberStatus s <> alias g
|
||||
s -> membershipIncognito g <> ttyFullGroup g <> viewMemberStatus s <> rejectionSuffix <> alias g
|
||||
where
|
||||
rejectionSuffix = case relayOwnStatus of
|
||||
Just RSRejected -> " [rejected]"
|
||||
_ -> ""
|
||||
viewMemberStatus = \case
|
||||
GSMemRejected -> delete "you are rejected"
|
||||
GSMemRemoved -> delete "you are removed"
|
||||
|
||||
Reference in New Issue
Block a user