Merge branch 'master' into ep/profile-badges

This commit is contained in:
Evgeny @ SimpleX Chat
2026-10-09 07:20:50 +00:00
595 changed files with 398 additions and 538 deletions
+58 -84
View File
@@ -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
+8 -7
View File
@@ -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"
+8 -12
View File
@@ -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)
+2 -2
View File
@@ -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
}
+3
View File
@@ -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
+2 -32
View File
@@ -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 =
+5 -28
View File
@@ -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=?)
+1 -2
View File
@@ -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
+1 -2
View File
@@ -1093,8 +1093,7 @@ data MemberIdRole = MemberIdRole
deriving (Eq, Show)
data IntroInvitation = IntroInvitation
{ groupConnReq :: ConnReqInvitation,
directConnReq :: Maybe ConnReqInvitation
{ groupConnReq :: ConnReqInvitation
}
deriving (Eq, Show)
+1
View File
@@ -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
+2 -4
View File
@@ -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>"
}
}