mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-10-15 04:30:38 +00:00
Merge branch 'master' into ep/profile-badges
This commit is contained in:
@@ -178,31 +178,6 @@ checkProfileSize p@Profile {contactDomain} = checkInfoSize "Profile" (XInfo p' N
|
||||
where
|
||||
p' = (p :: Profile) {badge = Nothing, contactDomain = (\d -> d {proof = Nothing} :: SimplexDomainClaim) <$> contactDomain}
|
||||
|
||||
checkOtherUserName :: ContactName -> User -> CM ()
|
||||
checkOtherUserName name User {profile = LocalProfile {displayName}, activeUser, viewPwdHash} =
|
||||
when (displayName == name && (activeUser || isNothing viewPwdHash)) $ throwChatError $ CEUserExists name
|
||||
|
||||
checkOtherUsersName :: UserId -> ContactName -> CM ()
|
||||
checkOtherUsersName userId name = do
|
||||
users <- withFastStore' getUsers
|
||||
forM_ users $ \u@User {userId = uId} -> when (uId /= userId) $ checkOtherUserName name u
|
||||
|
||||
withUserNamesLock :: CM a -> CM a
|
||||
withUserNamesLock = withEntityLock "userNames" CLUserNames
|
||||
|
||||
withUserProfileLock :: User -> (User -> CM a) -> CM a
|
||||
withUserProfileLock User {userId} action =
|
||||
withUserProfileLock_ userId $ withFastStore (`getUser` userId) >>= action
|
||||
|
||||
withUserProfileLock_ :: UserId -> CM a -> CM a
|
||||
withUserProfileLock_ userId = withEntityLock "userProfile" (CLUserProfile userId)
|
||||
|
||||
updateCurrentUser :: User -> CM ()
|
||||
updateCurrentUser user'@User {userId} =
|
||||
chatModifyVar currentUser $ \case
|
||||
Just User {userId = cuId} | cuId == userId -> Just user'
|
||||
cu_ -> cu_
|
||||
|
||||
checkGroupProfileSize :: GroupProfile -> CM ()
|
||||
checkGroupProfileSize p = checkInfoSize "Group profile" (XGrpInfo p)
|
||||
|
||||
@@ -480,23 +455,23 @@ processChatCommand cxt nm = \case
|
||||
checkProfileSize p
|
||||
p@Profile {displayName} <- liftIO $ maybe generateRandomProfile pure profile
|
||||
u <- asks currentUser
|
||||
withUserNamesLock $ do
|
||||
users <- withFastStore' getUsers
|
||||
forM_ users $ \otherUser@User {userChatRelay = userChatRelay'} -> do
|
||||
checkOtherUserName displayName otherUser
|
||||
when (isTrue userChatRelay && isTrue userChatRelay') $ throwChatError CEChatRelayExists
|
||||
(uss, (smp', xftp')) <- chooseServers =<< readTVarIO u
|
||||
let service = isTrue clientService
|
||||
auId <- withAgent $ \a -> createUser a service smp' xftp'
|
||||
ts <- liftIO $ getCurrentTime >>= if pastTimestamp then coupleDaysAgo else pure
|
||||
user <- withFastStore $ \db -> do
|
||||
user <- createUserRecordAt db (AgentUserId auId) (isTrue userChatRelay) service p True ts
|
||||
mapM_ (setUserServers db user ts) uss
|
||||
createPresetContactCards db user `catchAllErrors` \_ -> pure ()
|
||||
createNoteFolder db user
|
||||
pure user
|
||||
atomically . writeTVar u $ Just user
|
||||
pure $ CRActiveUser user
|
||||
users <- withFastStore' getUsers
|
||||
forM_ users $ \User {profile = LocalProfile {displayName = n}, activeUser, viewPwdHash, userChatRelay = userChatRelay'} -> do
|
||||
when (n == displayName) . throwChatError $
|
||||
if activeUser || isNothing viewPwdHash then CEUserExists displayName else CEInvalidDisplayName {displayName, validName = ""}
|
||||
when (isTrue userChatRelay && isTrue userChatRelay') $ throwChatError CEChatRelayExists
|
||||
(uss, (smp', xftp')) <- chooseServers =<< readTVarIO u
|
||||
let service = isTrue clientService
|
||||
auId <- withAgent $ \a -> createUser a service smp' xftp'
|
||||
ts <- liftIO $ getCurrentTime >>= if pastTimestamp then coupleDaysAgo else pure
|
||||
user <- withFastStore $ \db -> do
|
||||
user <- createUserRecordAt db (AgentUserId auId) (isTrue userChatRelay) service p True ts
|
||||
mapM_ (setUserServers db user ts) uss
|
||||
createPresetContactCards db user `catchAllErrors` \_ -> pure ()
|
||||
createNoteFolder db user
|
||||
pure user
|
||||
atomically . writeTVar u $ Just user
|
||||
pure $ CRActiveUser user
|
||||
where
|
||||
createPresetContactCards :: DB.Connection -> User -> ExceptT StoreError IO ()
|
||||
createPresetContactCards db user = do
|
||||
@@ -1624,8 +1599,8 @@ processChatCommand cxt nm = \case
|
||||
APICallStatus contactId receivedStatus ->
|
||||
withCurrentCall contactId $ \user ct call ->
|
||||
updateCallItemStatus user ct call receivedStatus Nothing $> Just call
|
||||
APIUpdateProfile userId profile -> withUserId userId $ \user -> updateProfile user (const profile)
|
||||
APISetUserDomain userId strDomain_ -> withUserId userId $ \u -> withUserProfileLock u $ \user@User {profile = p@LocalProfile {contactLink, contactDomain}} -> do
|
||||
APIUpdateProfile userId profile -> withUserProfileLock "updateProfile" $ withUserId userId (`updateProfile` profile)
|
||||
APISetUserDomain userId strDomain_ -> withUserProfileLock "setUserDomain" $ withUserId userId $ \user@User {profile = p@LocalProfile {contactLink, contactDomain}} -> do
|
||||
let domain_ = unStrJSON <$> strDomain_
|
||||
if (claimDomain <$> contactDomain) == domain_
|
||||
then pure $ CRUserProfileNoChange user
|
||||
@@ -2509,7 +2484,7 @@ processChatCommand cxt nm = \case
|
||||
pure $ CRUserContactLinkCreated user ccLink''
|
||||
CreateMyAddress ratchetKeys_ -> withUser $ \User {userId} ->
|
||||
processChatCommand cxt nm $ APICreateMyAddress userId Nothing ratchetKeys_
|
||||
APIDeleteMyAddress userId -> withUserId userId $ \u -> withUserProfileLock u $ \user@User {profile = p} -> do
|
||||
APIDeleteMyAddress userId -> withUserProfileLock "deleteMyAddress" $ withUserId userId $ \user@User {profile = p} -> do
|
||||
conn <- withFastStore $ \db -> getUserAddressConnection db cxt user
|
||||
withChatLock "deleteMyAddress" $ do
|
||||
deleteAgentConnectionAsync $ aConnId conn
|
||||
@@ -2532,10 +2507,10 @@ processChatCommand cxt nm = \case
|
||||
CRUserContactLink user <$> (withFastStore (`getUserAddress` user) >>= setMyAddressData False pqInitKeys user)
|
||||
APIRotateAddressRatchetKeys userId -> withUserId' userId $ \u -> withUserProfileLock u $ \user ->
|
||||
CRUserContactLink user <$> (withFastStore (`getUserAddress` user) >>= setMyAddressData True (Just IKUsePQ) user)
|
||||
APISetProfileAddress userId False -> withUserId userId $ \u -> withUserProfileLock u $ \user@User {profile = p} -> do
|
||||
APISetProfileAddress userId False -> withUserProfileLock "setProfileAddress" $ withUserId userId $ \user@User {profile = p} -> do
|
||||
let p' = (fromLocalProfile p :: Profile) {contactLink = Nothing, contactDomain = Nothing}
|
||||
updateProfile_ user p' True $ withFastStore' $ \db -> setUserProfileContactLink db user Nothing
|
||||
APISetProfileAddress userId True -> withUserId userId $ \u -> withUserProfileLock u $ \user@User {profile = p} -> do
|
||||
APISetProfileAddress userId True -> withUserProfileLock "setProfileAddress" $ withUserId userId $ \user@User {profile = p} -> do
|
||||
ucl <- withFastStore (`getUserAddress` user)
|
||||
-- TODO [short links] replace with short links
|
||||
let p' = (fromLocalProfile p :: Profile) {contactLink = Just $ profileContactLink ucl}
|
||||
@@ -3610,20 +3585,24 @@ processChatCommand cxt nm = \case
|
||||
-- after the write, so the pass it signals arms a wake for the snooze rather than raising again
|
||||
lift $ startBadgeWork user
|
||||
CRBadgeState user <$> getUserBadgeState user
|
||||
SetBotCommands commands -> withUser $ \user -> updateProfile user $ \User {profile} ->
|
||||
SetBotCommands commands -> withUserProfileLock "updateProfile" $ withUser $ \user@User {profile} -> do
|
||||
let LocalProfile {preferences} = profile
|
||||
prefs = Just (fromMaybe emptyChatPrefs preferences :: Preferences) {commands = Just commands}
|
||||
in (fromLocalProfile profile :: Profile) {preferences = prefs, peerType = Just CPTBot}
|
||||
UpdateProfile displayName shortDescr -> withUser $ \user -> updateProfile user $ \User {profile} ->
|
||||
(fromLocalProfile profile :: Profile) {displayName, shortDescr, fullName = ""}
|
||||
UpdateProfileImage image -> withUser $ \user -> updateProfile user $ \User {profile} ->
|
||||
(fromLocalProfile profile :: Profile) {image}
|
||||
UpdateProfileImageFromFile path -> withUser $ \user -> do
|
||||
p = (fromLocalProfile profile :: Profile) {preferences = prefs, peerType = Just CPTBot}
|
||||
updateProfile user p
|
||||
UpdateProfile displayName shortDescr -> withUserProfileLock "updateProfile" $ withUser $ \user@User {profile} -> do
|
||||
let p = (fromLocalProfile profile :: Profile) {displayName, shortDescr, fullName = ""}
|
||||
updateProfile user p
|
||||
UpdateProfileImage image -> withUserProfileLock "updateProfile" $ withUser $ \user@User {profile} -> do
|
||||
let p = (fromLocalProfile profile :: Profile) {image}
|
||||
updateProfile user p
|
||||
UpdateProfileImageFromFile path -> withUserProfileLock "updateProfile" $ withUser $ \user@User {profile} -> do
|
||||
img <- readProfileImageFile path
|
||||
updateProfile user $ \User {profile} -> (fromLocalProfile profile :: Profile) {image = Just img}
|
||||
ShowProfileImage -> withUser $ \user@User {profile} -> pure $ CRUserProfileImage user $ fromLocalProfile profile
|
||||
SetUserFeature (ACF f) allowed -> withUser $ \user -> updateProfile user $ \user'@User {profile} ->
|
||||
(fromLocalProfile profile :: Profile) {preferences = Just . setPreference f (Just allowed) $ preferences' user'}
|
||||
SetUserFeature (ACF f) allowed -> withUserProfileLock "updateProfile" $ withUser $ \user@User {profile} -> do
|
||||
let p = (fromLocalProfile profile :: Profile) {preferences = Just . setPreference f (Just allowed) $ preferences' user}
|
||||
updateProfile user p
|
||||
SetContactFeature (ACF f) cName allowed_ -> withUser $ \user -> do
|
||||
ct@Contact {userPreferences} <- withFastStore $ \db -> getContactByName db cxt user cName
|
||||
let prefs' = setPreference f allowed_ $ Just userPreferences
|
||||
@@ -3639,7 +3618,7 @@ processChatCommand cxt nm = \case
|
||||
case memberAdmission of
|
||||
Nothing -> p {memberAdmission = Just (emptyGroupMemberAdmission :: GroupMemberAdmission) {review = reviewAdmissionApplication}}
|
||||
Just ma -> p {memberAdmission = Just (ma :: GroupMemberAdmission) {review = reviewAdmissionApplication}}
|
||||
SetUserTimedMessages onOff -> withUser $ \user -> updateProfile user $ \user'@User {profile} ->
|
||||
SetUserTimedMessages onOff -> withUserProfileLock "updateProfile" $ withUser $ \user@User {profile} -> do
|
||||
let allowed = if onOff then FAYes else FANo
|
||||
pref = TimedMessagesPreference allowed Nothing
|
||||
in (fromLocalProfile profile :: Profile) {preferences = Just . setPreference' SCFTimedMessages (Just pref) $ preferences' user'}
|
||||
@@ -3707,8 +3686,7 @@ processChatCommand cxt nm = \case
|
||||
CLContactRequest crId -> "ContactRequest " <> tshow crId
|
||||
CLFile fId -> "File " <> tshow fId
|
||||
CLBadgeUser uId -> "BadgeUser " <> tshow uId
|
||||
CLUserProfile uId -> "UserProfile " <> tshow uId
|
||||
CLUserNames -> "UserNames"
|
||||
CLUserProfile -> "UserProfile"
|
||||
DebugEvent event -> toView event >> ok_
|
||||
GetAgentSubsTotal userId -> withUserId userId $ \user -> do
|
||||
users <- withStore' $ \db -> getUsers db
|
||||
@@ -4036,37 +4014,33 @@ processChatCommand cxt nm = \case
|
||||
now <- liftIO getCurrentTime
|
||||
when (fileSize > maxSndXFTPFileSize lims now sndBadge) $ throwChatError $ CEFileSize f
|
||||
pure fileSize
|
||||
updateProfile :: User -> (User -> Profile) -> CM ChatResponse
|
||||
updateProfile user mkProfile = withUserProfileLock user $ \user'@User {profile = p} ->
|
||||
let p'@Profile {contactLink} = mkProfile user'
|
||||
Profile {contactDomain = claim} = fromLocalProfile p
|
||||
p'' = (p' :: Profile) {contactDomain = if isJust contactLink then claim else Nothing}
|
||||
in updateProfile_ user' p'' True $ withFastStore $ \db -> updateUserProfile db user' p''
|
||||
updateProfile :: User -> Profile -> CM ChatResponse
|
||||
updateProfile user@User {profile = LocalProfile {contactDomain}} p = updateProfile_ user p' True $ withFastStore $ \db -> updateUserProfile db user p'
|
||||
where
|
||||
p' = (p :: Profile) {contactDomain}
|
||||
updateProfile_ :: User -> Profile -> Bool -> CM User -> CM ChatResponse
|
||||
updateProfile_ user@User {userId, profile = p@LocalProfile {displayName = n}} p'@Profile {displayName = n', image = img'} shouldUpdateAddressData updateUser
|
||||
| p' == fromLocalProfile p = pure $ CRUserProfileNoChange user
|
||||
| n /= n' = do
|
||||
checkValidName n'
|
||||
withUserNamesLock $ do
|
||||
checkOtherUsersName userId n'
|
||||
update
|
||||
| otherwise = update
|
||||
where
|
||||
update = do
|
||||
| otherwise = do
|
||||
when (n /= n') $ do
|
||||
checkValidName n'
|
||||
users <- withFastStore' getUsers
|
||||
forM_ users $ \User {profile = LocalProfile {displayName}, activeUser, viewPwdHash} ->
|
||||
when (displayName == n') . throwChatError $
|
||||
if activeUser || isNothing viewPwdHash then CEUserExists n' else CEInvalidDisplayName {displayName = n', validName = ""}
|
||||
checkProfileImageSize img'
|
||||
checkProfileSize p'
|
||||
when shouldUpdateAddressData $ do
|
||||
ts <- liftIO getCurrentTime
|
||||
setMyAddressData' (user :: User) {profile = updatedUserProfile user p' ts} `catchAllErrors` \e -> restoreAddressData >> throwError e
|
||||
withChatLock "updateProfile" (tryAllErrors updateUser >>= mapM sendUpdate) >>= \case
|
||||
Right r -> pure r
|
||||
Left e -> restoreAddressData >> throwError e
|
||||
restoreAddressData = when shouldUpdateAddressData $ setMyAddressData' user `catchAllErrors` eToView
|
||||
sendUpdate user' = do
|
||||
updateCurrentUser user'
|
||||
setMyAddressData' (user :: User) {profile = updatedUserProfile user p' ts}
|
||||
-- read contacts before user update to correctly merge preferences
|
||||
contacts <- withFastStore' $ \db -> getUserContacts db cxt user
|
||||
summary <- sendUpdateToContacts user' contacts
|
||||
pure $ CRUserProfileUpdated user' (fromLocalProfile p) p' summary
|
||||
user' <- updateUser
|
||||
asks currentUser >>= atomically . (`writeTVar` Just user')
|
||||
withChatLock "updateProfile" $ do
|
||||
summary <- sendUpdateToContacts user' contacts
|
||||
pure $ CRUserProfileUpdated user' (fromLocalProfile p) p' summary
|
||||
where
|
||||
setMyAddressData' :: User -> CM ()
|
||||
setMyAddressData' user' =
|
||||
withFastStore' (\db -> runExceptT $ getUserAddress db user) >>= \case
|
||||
@@ -5673,7 +5647,7 @@ presentIssuedBadge user@User {userId} p@UserBadgePurchase {badgePurchaseId, show
|
||||
| otherwise = do
|
||||
cred_ <- withStore' (`getLatestIssuedCredential` badgePurchaseId)
|
||||
forM_ cred_ $ \cred@(BadgeCredential _ _ _ info) ->
|
||||
when (presentDue cred) $ withUserProfileLock_ userId $ do
|
||||
when (presentDue cred) $ withUserProfileLock "presentIssuedBadge" $ do
|
||||
user' <- withStore $ \db -> setUserBadge db user (Just $ OwnBadge cred (mkBadgeStatus now (Just True) info))
|
||||
presentUserBadgeToContacts user'
|
||||
where
|
||||
@@ -5686,7 +5660,7 @@ presentIssuedBadge user@User {userId} p@UserBadgePurchase {badgePurchaseId, show
|
||||
retireExpiredBadge :: User -> UserBadgePurchase -> UTCTime -> StatementEntry -> CM Bool
|
||||
retireExpiredBadge user@User {userId} UserBadgePurchase {badgePurchaseId, shown} now balance
|
||||
| not (shown && L.paidThrough balance <= now) = pure False
|
||||
| otherwise = withUserProfileLock_ userId $ do
|
||||
| otherwise = withUserProfileLock "retireExpiredBadge" $ do
|
||||
user' <- withStore $ \db -> do
|
||||
liftIO $ clearShownBadge db user badgePurchaseId
|
||||
setUserBadge db user Nothing
|
||||
|
||||
@@ -160,6 +160,10 @@ withFileLock :: Text -> Int64 -> CM a -> CM a
|
||||
withFileLock name = withEntityLock name . CLFile
|
||||
{-# INLINE withFileLock #-}
|
||||
|
||||
withUserProfileLock :: Text -> CM a -> CM a
|
||||
withUserProfileLock name = withEntityLock name CLUserProfile
|
||||
{-# INLINE withUserProfileLock #-}
|
||||
|
||||
useServerCfgs :: forall p. UserProtocol p => SProtocolType p -> RandomAgentServers -> [(Text, ServerOperator)] -> [UserServer p] -> NonEmpty (ServerCfg p)
|
||||
useServerCfgs p RandomAgentServers {smpServers, xftpServers} opDomains =
|
||||
fromMaybe (rndAgentServers p) . L.nonEmpty . agentServerCfgs p opDomains
|
||||
@@ -1417,11 +1421,8 @@ sendHistory user gInfo@GroupInfo {membership} m@GroupMember {activeConn = Just c
|
||||
-- (regular groups only; never channels) is an authored element -- all batch together in order.
|
||||
vr <- chatVersionRange
|
||||
welcomeEl <- welcomeElement
|
||||
let fwdMsgs = concat fwdMsgsByItem
|
||||
welcomeEls = maybe [] (: []) welcomeEl
|
||||
(batches, dropped)
|
||||
| m `supportsVersion` relayWebCapVersion = batchElements maxForwardBatchLength (map (uncurry encodeFwdElement) fwdMsgs <> welcomeEls)
|
||||
| otherwise = batchJsonElements maxForwardBatchLength (map (\(fwd, verifiedMsg) -> encodeLegacyFwdElement vr fwd (verifiedChatMsg verifiedMsg)) fwdMsgs <> welcomeEls)
|
||||
let fwdEls = map (uncurry encodeFwdElement) (concat fwdMsgsByItem)
|
||||
let (batches, dropped) = batchElements maxForwardBatchLength (fwdEls <> maybe [] (: []) welcomeEl)
|
||||
when (dropped > 0) $ toView $ CEvtChatErrors [ChatError $ CEInternalError ("sendHistory: dropped " <> show dropped <> " oversized history messages")]
|
||||
forM_ batches $ \body ->
|
||||
void $ withAgent $ \a -> sendMessages a [(aConnId conn, PQEncOff, MsgFlags False, VRValue Nothing body)]
|
||||
@@ -2515,7 +2516,7 @@ encodeConnInfoPQ :: MsgEncodingI e => PQSupport -> ChatMsgEvent e -> CM ByteStri
|
||||
encodeConnInfoPQ pqSup chatMsgEvent = do
|
||||
cxt <- chatStoreCxt
|
||||
let info = ChatMessage {chatVRange = vr cxt, msgId = Nothing, chatMsgEvent}
|
||||
case encodeChatMessage maxDecompressedMsgLength info of
|
||||
case encodeChatMessage maxEncodedProfileMsgLength info of
|
||||
ECMEncoded connInfo -> compressConnInfo pqSup connInfo
|
||||
ECMLarge -> throwChatError $ CEException "large info"
|
||||
|
||||
@@ -2524,7 +2525,7 @@ encodeSignedConnInfo :: MsgEncodingI e => PQSupport -> MsgSigning -> ChatMsgEven
|
||||
encodeSignedConnInfo pqSup signing chatMsgEvent = do
|
||||
vr <- chatVersionRange
|
||||
let info = ChatMessage {chatVRange = vr, msgId = Nothing, chatMsgEvent}
|
||||
case encodeChatMessage maxDecompressedMsgLength info of
|
||||
case encodeChatMessage maxEncodedProfileMsgLength info of
|
||||
ECMEncoded body -> compressConnInfo pqSup $ encodeBatchElement (Just $ signChatMsgBody signing body) body
|
||||
ECMLarge -> throwChatError $ CEException "large signed info"
|
||||
|
||||
|
||||
@@ -773,7 +773,7 @@ processAgentMessageConn cxt user@User {userId} entity gInfoKeys_ corrId agentCon
|
||||
hostConnId <- withStore $ \db -> do
|
||||
liftIO $ setConnConnReqInv db user connId cReq
|
||||
getHostConnId db user groupId
|
||||
sendXGrpMemInv hostConnId Nothing XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq}
|
||||
sendXGrpMemInv hostConnId XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq}
|
||||
_ -> throwChatError $ CECommandError "unexpected cmdFunction"
|
||||
CRContactUri _ _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type"
|
||||
CONF confId _pqSupport _ connInfo -> do
|
||||
@@ -3324,10 +3324,10 @@ processAgentMessageConn cxt user@User {userId} entity gInfoKeys_ corrId agentCon
|
||||
withAgent $ \a -> createConnectionAsync a (aCorrId cmdId) connId (chatHasNtfs chatSettings) SCMInvitation CR.IKPQOff True subMode
|
||||
_ -> messageError "x.grp.mem.intro can be only sent by host member"
|
||||
|
||||
sendXGrpMemInv :: Int64 -> Maybe ConnReqInvitation -> XGrpMemIntroCont -> CM ()
|
||||
sendXGrpMemInv hostConnId directConnReq XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} = do
|
||||
sendXGrpMemInv :: Int64 -> XGrpMemIntroCont -> CM ()
|
||||
sendXGrpMemInv hostConnId XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} = do
|
||||
hostConn <- withStore $ \db -> getConnectionById db cxt user hostConnId
|
||||
let msg = XGrpMemInv memberId IntroInvitation {groupConnReq, directConnReq}
|
||||
let msg = XGrpMemInv memberId IntroInvitation {groupConnReq}
|
||||
void $ sendDirectMemberMessage hostConn msg groupId
|
||||
withStore' $ \db -> updateGroupMemberStatusById db userId groupMemberId GSMemIntroInvited
|
||||
|
||||
@@ -3341,7 +3341,7 @@ processAgentMessageConn cxt user@User {userId} entity gInfoKeys_ corrId agentCon
|
||||
_ -> messageError "x.grp.mem.inv can be only sent by invitee member"
|
||||
|
||||
xGrpMemFwd :: GroupInfoKeys -> GroupMember -> MemberInfo -> IntroInvitation -> CM ()
|
||||
xGrpMemFwd g@(GIK gInfo@GroupInfo {membership, chatSettings} _) m memInfo@(MemberInfo memId memRole memChatVRange _ _) IntroInvitation {groupConnReq, directConnReq} = do
|
||||
xGrpMemFwd g@(GIK gInfo@GroupInfo {membership, chatSettings} _) m memInfo@(MemberInfo memId memRole memChatVRange _ _) IntroInvitation {groupConnReq} = do
|
||||
let GroupMember {memberId = membershipMemId} = membership
|
||||
checkHostRole m memRole
|
||||
toMember <- withStore $ \db -> do
|
||||
@@ -3363,7 +3363,7 @@ processAgentMessageConn cxt user@User {userId} entity gInfoKeys_ corrId agentCon
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
let mcvr = maybe chatInitialVRange fromChatVRange memChatVRange
|
||||
chatV = vr cxt `peerConnChatVersion` mcvr
|
||||
-- [incognito] send membership incognito profile, create direct connection as incognito
|
||||
-- [incognito] send membership incognito profile
|
||||
membershipProfile <- membershipHandshakeProfile gInfo
|
||||
let msg = XGrpMemInfo membershipMemId membershipProfile
|
||||
signing_ = if chatV >= relayWebCapVersion then groupMsgSigning False g msg else Nothing
|
||||
@@ -3371,12 +3371,8 @@ processAgentMessageConn cxt user@User {userId} entity gInfoKeys_ corrId agentCon
|
||||
-- [async agent commands] no continuation needed, but commands should be asynchronous for stability
|
||||
let enableNtfsGrp = chatHasNtfs chatSettings
|
||||
(groupConnIds@(gCmdId, gAcId), _) <- prepareAgentJoin user enableNtfsGrp groupConnReq
|
||||
directConnIds <- mapM (fmap fst . prepareAgentJoin user True) directConnReq
|
||||
let customUserProfileId = localProfileId <$> incognitoMembershipProfile gInfo
|
||||
withStore' $ \db -> createIntroToMemberContact db user m toMember chatV mcvr groupConnIds directConnIds customUserProfileId subMode
|
||||
withStore' $ \db -> createIntroToMemberContact db user m toMember chatV mcvr groupConnIds subMode
|
||||
joinAgentConnectionAsync gCmdId False gAcId enableNtfsGrp groupConnReq dm subMode
|
||||
forM_ ((,) <$> directConnIds <*> directConnReq) $ \((dCmdId, dAcId), dcr) ->
|
||||
joinAgentConnectionAsync dCmdId False dAcId True dcr dm subMode
|
||||
|
||||
membershipHandshakeProfile :: GroupInfo -> CM Profile
|
||||
membershipHandshakeProfile gInfo@GroupInfo {membership} =
|
||||
@@ -3971,7 +3967,7 @@ processAgentMessageConn cxt user@User {userId} entity gInfoKeys_ corrId agentCon
|
||||
let allowCreate = toCMEventTag chatMsgEvent /= XGrpLeave_
|
||||
withStore (\db -> getCreateUnknownGMByMemberId db cxt user gInfo memberId memberName unknownRole allowCreate) >>= \case
|
||||
Just (author, unknown)
|
||||
| groupMemberId' author == groupMemberId' membership ->
|
||||
| not (useRelays' gInfo) && groupMemberId' author == groupMemberId' membership ->
|
||||
messageError $ "x.grp.msg.forward: content attributed to own membership, forwarder " <> tshow (groupMemberId' m) <> ", event " <> tshow (toCMEventTag chatMsgEvent)
|
||||
| memberRemoved author ->
|
||||
logInfo $ "x.grp.msg.forward: ignoring content from removed member, group " <> tshow (groupId' gInfo) <> ", member " <> safeDecodeUtf8 (strEncode memberId) <> ", event " <> tshow (toCMEventTag chatMsgEvent)
|
||||
|
||||
@@ -55,7 +55,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..))
|
||||
import qualified Simplex.Messaging.Crypto.File as CF
|
||||
import Simplex.Messaging.Encoding.String
|
||||
import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, sumTypeJSON)
|
||||
import Simplex.Messaging.Protocol (BlockingInfo, MsgBody, XFTPServer)
|
||||
import Simplex.Messaging.Protocol (BlockingInfo, MsgBody, XFTPServer, unMsgId)
|
||||
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
|
||||
|
||||
data ChatType = CTDirect | CTGroup | CTLocal | CTContactRequest | CTContactConnection
|
||||
@@ -1228,7 +1228,7 @@ msgMetaToJson MsgMeta {integrity, recipient = (rcvId, rcvTs), broker = (serverId
|
||||
{ integrity = (decodeLatin1 . strEncode) integrity,
|
||||
rcvId,
|
||||
rcvTs,
|
||||
serverId = (decodeLatin1 . B64.encode) serverId,
|
||||
serverId = (decodeLatin1 . B64.encode . unMsgId) serverId,
|
||||
serverTs,
|
||||
sndId
|
||||
}
|
||||
|
||||
@@ -973,6 +973,9 @@ rosterBlobP = do
|
||||
maxEncodedInfoLength :: Int
|
||||
maxEncodedInfoLength = 14694
|
||||
|
||||
maxEncodedProfileMsgLength :: Int
|
||||
maxEncodedProfileMsgLength = 16384
|
||||
|
||||
data EncodedChatMessage = ECMEncoded ByteString | ECMLarge
|
||||
|
||||
encodeChatMessage :: MsgEncodingI e => Int -> ChatMessage e -> EncodedChatMessage
|
||||
|
||||
@@ -2661,42 +2661,12 @@ createIntroReMemberConn
|
||||
liftIO $ setCommandConnId db user groupCmdId groupConnId
|
||||
pure (reMember :: GroupMember) {activeConn = Just conn}
|
||||
|
||||
createIntroToMemberContact :: DB.Connection -> User -> GroupMember -> GroupMember -> VersionChat -> VersionRangeChat -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> SubscriptionMode -> IO ()
|
||||
createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = viaContactId, activeConn} _to@GroupMember {groupMemberId, localDisplayName} chatV mcvr (groupCmdId, groupAgentConnId) directConnIds customUserProfileId subMode = do
|
||||
createIntroToMemberContact :: DB.Connection -> User -> GroupMember -> GroupMember -> VersionChat -> VersionRangeChat -> (CommandId, ConnId) -> SubscriptionMode -> IO ()
|
||||
createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = viaContactId, activeConn} _to@GroupMember {groupMemberId} chatV mcvr (groupCmdId, groupAgentConnId) subMode = do
|
||||
let cLevel = 1 + maybe 0 (\Connection {connLevel} -> connLevel) activeConn
|
||||
currentTs <- getCurrentTime
|
||||
Connection {connId = groupConnId} <- createMemberConnection_ db userId groupMemberId groupAgentConnId chatV mcvr viaContactId cLevel currentTs subMode
|
||||
setCommandConnId db user groupCmdId groupConnId
|
||||
forM_ directConnIds $ \(directCmdId, directAgentConnId) -> do
|
||||
Connection {connId = directConnId} <- createConnection_ db userId ConnContact Nothing directAgentConnId ConnNew chatV mcvr viaContactId Nothing customUserProfileId cLevel currentTs subMode PQSupportOff
|
||||
setCommandConnId db user directCmdId directConnId
|
||||
contactId <- createMemberContact_ directConnId currentTs
|
||||
updateMember_ contactId currentTs
|
||||
where
|
||||
createMemberContact_ :: Int64 -> UTCTime -> IO Int64
|
||||
createMemberContact_ connId ts = do
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
INSERT INTO contacts (contact_profile_id local_display_name, user_id, created_at, updated_at, chat_ts)
|
||||
SELECT contact_profile_id, ?, ?, ?, ?, ?
|
||||
FROM group_members
|
||||
WHERE group_member_id = ?
|
||||
|]
|
||||
(localDisplayName, userId, ts, ts, ts, groupMemberId)
|
||||
contactId <- insertedRowId db
|
||||
DB.execute db "UPDATE connections SET contact_id = ?, updated_at = ? WHERE connection_id = ?" (contactId, ts, connId)
|
||||
pure contactId
|
||||
updateMember_ :: Int64 -> UTCTime -> IO ()
|
||||
updateMember_ contactId ts =
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE group_members
|
||||
SET contact_id = ?, updated_at = ?
|
||||
WHERE group_member_id = ?
|
||||
|]
|
||||
(contactId, ts, groupMemberId)
|
||||
|
||||
createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe Int64 -> Int -> UTCTime -> SubscriptionMode -> IO Connection
|
||||
createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange viaContact connLevel currentTs subMode =
|
||||
|
||||
@@ -160,30 +160,7 @@ createUserRecordAt db (AgentUserId auId) userChatRelay clientService Profile {di
|
||||
(profileId, ldn, userId, BI True, currentTs, currentTs, currentTs)
|
||||
contactId <- insertedRowId db
|
||||
DB.execute db "UPDATE users SET contact_id = ? WHERE user_id = ?" (contactId, userId)
|
||||
pure $ toUser currentTs $ (userId, auId, contactId, profileId, BI activeUser, order, ldn) :. (displayName, fullName, shortDescr, description, image, Nothing, peerType, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, BI autoAcceptMemberContacts, BI autoAcceptGroupInvitations, Nothing, Nothing, Nothing, BI userChatRelay, BI clientService, Nothing) :. localBadgeToRow Nothing :. (Nothing, Nothing, Nothing)
|
||||
where
|
||||
showNtfs = True
|
||||
sendRcptsContacts = True
|
||||
sendRcptsSmallGroups = True
|
||||
autoAcceptMemberContacts = False
|
||||
autoAcceptGroupInvitations = False
|
||||
insertUser :: Int64 -> Int -> Int -> ExceptT StoreError IO (UserId, Text, Int)
|
||||
insertUser _ _ 0 = throwError SEDuplicateName
|
||||
insertUser order ldnSuffix attempts =
|
||||
liftIO (withSavepoint db "user_insert" insertUserRow) >>= \case
|
||||
Right () -> (,ldn,ldnSuffix) <$> liftIO (insertedRowId db)
|
||||
Left e
|
||||
| constraintError e -> insertUser order (ldnSuffix + 1) (attempts - 1)
|
||||
| otherwise -> liftIO $ E.throwIO e
|
||||
where
|
||||
ldn = displayName <> (if ldnSuffix == 0 then "" else T.pack $ '_' : show ldnSuffix)
|
||||
insertUserRow =
|
||||
DB.execute
|
||||
db
|
||||
"INSERT INTO users (agent_user_id, local_display_name, active_user, is_user_chat_relay, active_order, contact_id, show_ntfs, send_rcpts_contacts, send_rcpts_small_groups, auto_accept_member_contacts, auto_accept_group_invitations, client_service, created_at, updated_at) VALUES (?,?,?,?,?,0,?,?,?,?,?,?,?,?)"
|
||||
( (auId, ldn, BI activeUser, BI userChatRelay, order)
|
||||
:. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, BI autoAcceptMemberContacts, BI autoAcceptGroupInvitations, BI clientService, currentTs, currentTs)
|
||||
)
|
||||
pure $ toUser currentTs $ (userId, auId, contactId, profileId, BI activeUser, order, displayName) :. (displayName, fullName, shortDescr, description, image, Nothing, peerType, userPreferences) :. (BI showNtfs, BI sendRcptsContacts, BI sendRcptsSmallGroups, BI autoAcceptMemberContacts, BI autoAcceptGroupInvitations, Nothing, Nothing, Nothing, BI userChatRelay, BI clientService, Nothing) :. localBadgeToRow Nothing :. (Nothing, Nothing, Nothing)
|
||||
|
||||
-- TODO [mentions]
|
||||
getUsersInfo :: DB.Connection -> IO [UserInfo]
|
||||
@@ -359,10 +336,10 @@ updateUserProfile db user p'
|
||||
updateUserProfileFields_' db userId profileId p' currentTs
|
||||
userMemberProfileUpdatedAt' <- updateUserMemberProfileUpdatedAt_ currentTs
|
||||
pure user {profile = updatedUserProfile user p' currentTs, fullPreferences, userMemberProfileUpdatedAt = userMemberProfileUpdatedAt'}
|
||||
| otherwise = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
let setUserLDN ldn = DB.execute db "UPDATE users SET local_display_name = ?, updated_at = ? WHERE user_id = ?" (ldn, currentTs, userId)
|
||||
ExceptT . withLocalDisplayName_ db userId newName (Just setUserLDN) $ \newLDN -> do
|
||||
| otherwise =
|
||||
checkConstraint SEDuplicateName . ExceptT . withLocalDisplayName db userId newName $ \newLDN -> do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute db "UPDATE users SET local_display_name = ?, updated_at = ? WHERE user_id = ?" (newLDN, currentTs, userId)
|
||||
userMemberProfileUpdatedAt' <- updateUserMemberProfileUpdatedAt_ currentTs
|
||||
updateUserProfileFields_' db userId profileId p' currentTs
|
||||
updateContactLDN_ db user userContactId localDisplayName newLDN currentTs
|
||||
|
||||
@@ -1398,6 +1398,10 @@ Query: SELECT conn_id FROM connections WHERE user_id = ?
|
||||
Plan:
|
||||
SEARCH connections USING COVERING INDEX idx_connections_user (user_id=?)
|
||||
|
||||
Query: SELECT conn_id FROM rcv_queues WHERE switch_status = ? AND deleted = 0
|
||||
Plan:
|
||||
SCAN rcv_queues
|
||||
|
||||
Query: SELECT conn_id, rc_verify_code_ad, rc_verify_code_pq, CASE WHEN rc_verify_code_ad IS NULL THEN ratchet_state END FROM ratchets WHERE conn_id = ?
|
||||
Plan:
|
||||
SEARCH ratchets USING PRIMARY KEY (conn_id=?)
|
||||
|
||||
@@ -5374,7 +5374,7 @@ SEARCH connections_sync USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query:
|
||||
UPDATE contact_profiles
|
||||
SET contact_link = ?, updated_at = ?
|
||||
SET contact_link = ?, contact_domain = ?, updated_at = ?
|
||||
WHERE user_id = ? AND contact_profile_id = ?
|
||||
|
||||
Plan:
|
||||
@@ -7714,6 +7714,10 @@ Query: SELECT member_id FROM group_members WHERE local_display_name = ?
|
||||
Plan:
|
||||
SCAN group_members
|
||||
|
||||
Query: SELECT member_id FROM group_members WHERE member_category = ?
|
||||
Plan:
|
||||
SCAN group_members
|
||||
|
||||
Query: SELECT member_id FROM group_members WHERE member_role = ? LIMIT 1
|
||||
Plan:
|
||||
SCAN group_members
|
||||
@@ -7990,6 +7994,10 @@ Query: UPDATE contact_profiles SET contact_domain = ?, updated_at = ? WHERE user
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE contact_profiles SET contact_domain_verified = NULL WHERE user_id = ? AND contact_profile_id = ?
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE contact_profiles SET image = ? WHERE display_name = ?
|
||||
Plan:
|
||||
SEARCH contact_profiles USING INDEX contact_profiles_index (display_name=?)
|
||||
@@ -8186,6 +8194,10 @@ Query: UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE g
|
||||
Plan:
|
||||
SEARCH group_members USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
Query: UPDATE group_profiles SET description = ? WHERE display_name = ?
|
||||
Plan:
|
||||
SCAN group_profiles
|
||||
|
||||
Query: UPDATE group_profiles SET public_group_id = ? WHERE group_profile_id = (SELECT group_profile_id FROM groups WHERE group_id = ?)
|
||||
Plan:
|
||||
SEARCH group_profiles USING INTEGER PRIMARY KEY (rowid=?)
|
||||
|
||||
@@ -74,8 +74,7 @@ data ChatLockEntity
|
||||
| CLContactRequest Int64
|
||||
| CLFile Int64
|
||||
| CLBadgeUser Int64 -- one signed badge request per profile in flight
|
||||
| CLUserProfile Int64
|
||||
| CLUserNames
|
||||
| CLUserProfile
|
||||
deriving (Eq, Ord)
|
||||
|
||||
-- These error type constructors must be added to mobile apps
|
||||
|
||||
@@ -1093,8 +1093,7 @@ data MemberIdRole = MemberIdRole
|
||||
deriving (Eq, Show)
|
||||
|
||||
data IntroInvitation = IntroInvitation
|
||||
{ groupConnReq :: ConnReqInvitation,
|
||||
directConnReq :: Maybe ConnReqInvitation
|
||||
{ groupConnReq :: ConnReqInvitation
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
|
||||
@@ -1961,6 +1961,7 @@ viewRcvQueuesInfo = plain . T.intercalate ", " . map showQueueInfo
|
||||
RSSendingQADD -> "switch started"
|
||||
RSSendingQUSE -> "switch confirmed"
|
||||
RSReceivedMessage -> "switch secured"
|
||||
RSReceivedQEND -> "switch completed"
|
||||
|
||||
viewSndQueuesInfo :: [SndQueueInfo] -> StyledString
|
||||
viewSndQueuesInfo = plain . T.intercalate ", " . map showQueueInfo
|
||||
|
||||
@@ -197,8 +197,7 @@ Events:
|
||||
{
|
||||
"memberId": "<member ID>",
|
||||
"memberIntro": {
|
||||
"groupConnReq": "<group conn req>",
|
||||
"directConnReq": "<direct conn req>"
|
||||
"groupConnReq": "<group conn req>"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -214,8 +213,7 @@ Events:
|
||||
},
|
||||
},
|
||||
"memberIntro": {
|
||||
"groupConnReq": "<group conn req>",
|
||||
"directConnReq": "<direct conn req>"
|
||||
"groupConnReq": "<group conn req>"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user