mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
core: only update business chat preferences (#5325)
* core: only update business chat preferences * rework * migration * test, bump protocol version * fix tests
This commit is contained in:
@@ -154,6 +154,7 @@ library
|
||||
Simplex.Chat.Migrations.M20241027_server_operators
|
||||
Simplex.Chat.Migrations.M20241125_indexes
|
||||
Simplex.Chat.Migrations.M20241128_business_chats
|
||||
Simplex.Chat.Migrations.M20241205_business_chat_members
|
||||
Simplex.Chat.Mobile
|
||||
Simplex.Chat.Mobile.File
|
||||
Simplex.Chat.Mobile.Shared
|
||||
|
||||
+51
-20
@@ -2929,11 +2929,22 @@ processChatCommand' vr = \case
|
||||
lift . when (directOrUsed ct') $ createSndFeatureItems user ct ct'
|
||||
pure $ CRContactPrefsUpdated user ct ct'
|
||||
runUpdateGroupProfile :: User -> Group -> GroupProfile -> CM ChatResponse
|
||||
runUpdateGroupProfile user (Group g@GroupInfo {groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do
|
||||
runUpdateGroupProfile user (Group g@GroupInfo {businessChat, groupProfile = p@GroupProfile {displayName = n}} ms) p'@GroupProfile {displayName = n'} = do
|
||||
assertUserGroupRole g GROwner
|
||||
when (n /= n') $ checkValidName n'
|
||||
g' <- withStore $ \db -> updateGroupProfile db user g p'
|
||||
msg <- sendGroupMessage user g' ms (XGrpInfo p')
|
||||
msg <- case businessChat of
|
||||
Just BusinessChatInfo {businessId} -> do
|
||||
let (newMs, oldMs) = partition (\m -> maxVersion (memberChatVRange m) >= businessChatPrefsVersion) ms
|
||||
-- this is a fallback to send the members with the old version correct profile of the business when preferences change
|
||||
unless (null oldMs) $ do
|
||||
GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} <-
|
||||
withStore $ \db -> getGroupMemberByMemberId db vr user g businessId
|
||||
let p'' = p' {displayName, fullName, image} :: GroupProfile
|
||||
void $ sendGroupMessage user g' oldMs (XGrpInfo p'')
|
||||
let ps' = fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
|
||||
sendGroupMessage user g' newMs $ XGrpPrefs ps'
|
||||
Nothing -> sendGroupMessage user g' ms (XGrpInfo p')
|
||||
let cd = CDGroupSnd g'
|
||||
unless (sameGroupProfileInfo p p') $ do
|
||||
ci <- saveSndChatItem user cd msg (CISndGroupEvent $ SGEGroupUpdated p')
|
||||
@@ -3026,7 +3037,7 @@ processChatCommand' vr = \case
|
||||
invitedMember = MemberIdRole memberId memRole,
|
||||
connRequest = cReq,
|
||||
groupProfile,
|
||||
businessChat,
|
||||
business = businessChat,
|
||||
groupLinkId = Nothing,
|
||||
groupSize = Just currentMemCount
|
||||
}
|
||||
@@ -4003,7 +4014,7 @@ acceptGroupJoinRequestAsync
|
||||
fromMemberName = displayName,
|
||||
invitedMember = MemberIdRole memberId gLinkMemRole,
|
||||
groupProfile,
|
||||
businessChat,
|
||||
business = businessChat,
|
||||
groupSize = Just currentMemCount
|
||||
}
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
@@ -4038,7 +4049,7 @@ acceptBusinessJoinRequestAsync
|
||||
-- This refers to the "title member" that defines the group name and profile.
|
||||
-- This coincides with fromMember to be current user when accepting the connecting user,
|
||||
-- but it will be different when inviting somebody else.
|
||||
businessChat = Just $ BusinessChatInfo userMemberId BCBusiness,
|
||||
business = Just $ BusinessChatInfo {chatType = BCBusiness, businessId = userMemberId, customerId = memberId},
|
||||
groupSize = Just 1
|
||||
}
|
||||
subMode <- chatReadVar subscriptionMode
|
||||
@@ -5042,7 +5053,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
invitedMember = MemberIdRole memberId memRole,
|
||||
connRequest = cReq,
|
||||
groupProfile,
|
||||
businessChat = Nothing,
|
||||
business = Nothing,
|
||||
groupLinkId = groupLinkId,
|
||||
groupSize = Just currentMemCount
|
||||
}
|
||||
@@ -5299,6 +5310,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
XGrpLeave -> xGrpLeave gInfo m' msg brokerTs
|
||||
XGrpDel -> xGrpDel gInfo m' msg brokerTs
|
||||
XGrpInfo p' -> xGrpInfo gInfo m' p' msg brokerTs
|
||||
XGrpPrefs ps' -> xGrpPrefs gInfo m' ps'
|
||||
XGrpDirectInv connReq mContent_ -> memberCanSend m' $ xGrpDirectInv gInfo m' conn' connReq mContent_ msg brokerTs
|
||||
XGrpMsgForward memberId msg' msgTs -> xGrpMsgForward gInfo m' memberId msg' msgTs
|
||||
XInfoProbe probe -> xInfoProbe (COMGroupMember m') probe
|
||||
@@ -5416,8 +5428,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
let GroupInfo {businessChat} = gInfo
|
||||
GroupMember {memberId = joiningMemberId} = m
|
||||
case businessChat of
|
||||
Just BusinessChatInfo {memberId, chatType = BCCustomer}
|
||||
| joiningMemberId == memberId -> useReply <$> withStore (`getUserAddress` user)
|
||||
Just BusinessChatInfo {customerId, chatType = BCCustomer}
|
||||
| joiningMemberId == customerId -> useReply <$> withStore (`getUserAddress` user)
|
||||
where
|
||||
useReply UserContactLink {autoAccept} = case autoAccept of
|
||||
Just AutoAccept {businessAddress, autoReply} | businessAddress -> autoReply
|
||||
@@ -6465,7 +6477,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
processMemberProfileUpdate :: GroupInfo -> GroupMember -> Profile -> Bool -> Maybe UTCTime -> CM GroupMember
|
||||
processMemberProfileUpdate gInfo m@GroupMember {memberProfile = p, memberContactId} p' createItems itemTs_
|
||||
| redactedMemberProfile (fromLocalProfile p) /= redactedMemberProfile p' = do
|
||||
updateBusinessChatProfile gInfo m
|
||||
updateBusinessChatProfile gInfo
|
||||
case memberContactId of
|
||||
Nothing -> do
|
||||
m' <- withStore $ \db -> updateMemberProfile db user m p'
|
||||
@@ -6491,11 +6503,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
| otherwise =
|
||||
pure m
|
||||
where
|
||||
updateBusinessChatProfile g@GroupInfo {businessChat} GroupMember {memberId} = case businessChat of
|
||||
Just BusinessChatInfo {memberId = mId} | mId == memberId -> do
|
||||
updateBusinessChatProfile g@GroupInfo {businessChat} = case businessChat of
|
||||
Just bc | isMainBusinessMember bc m -> do
|
||||
g' <- withStore $ \db -> updateGroupProfileFromMember db user g p'
|
||||
toView $ CRGroupUpdated user g g' (Just m)
|
||||
_ -> pure ()
|
||||
isMainBusinessMember BusinessChatInfo {chatType, businessId, customerId} GroupMember {memberId} = case chatType of
|
||||
BCBusiness -> businessId == memberId
|
||||
BCCustomer -> customerId == memberId
|
||||
createProfileUpdatedItem m' =
|
||||
when createItems $ do
|
||||
let ciContent = CIRcvGroupEvent $ RGEMemberProfileUpdated (fromLocalProfile p) p'
|
||||
@@ -7008,16 +7023,31 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
toView $ CRGroupDeleted user gInfo {membership = membership {memberStatus = GSMemGroupDeleted}} m
|
||||
|
||||
xGrpInfo :: GroupInfo -> GroupMember -> GroupProfile -> RcvMessage -> UTCTime -> CM ()
|
||||
xGrpInfo g@GroupInfo {groupProfile = p} m@GroupMember {memberRole} p' msg brokerTs
|
||||
xGrpInfo g@GroupInfo {groupProfile = p, businessChat} m@GroupMember {memberRole} p' msg brokerTs
|
||||
| memberRole < GROwner = messageError "x.grp.info with insufficient member permissions"
|
||||
| otherwise = unless (p == p') $ do
|
||||
g' <- withStore $ \db -> updateGroupProfile db user g p'
|
||||
toView $ CRGroupUpdated user g g' (Just m)
|
||||
let cd = CDGroupRcv g' m
|
||||
unless (sameGroupProfileInfo p p') $ do
|
||||
ci <- saveRcvChatItem user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
|
||||
groupMsgToView g' ci
|
||||
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
|
||||
| otherwise = case businessChat of
|
||||
Nothing -> unless (p == p') $ do
|
||||
g' <- withStore $ \db -> updateGroupProfile db user g p'
|
||||
toView $ CRGroupUpdated user g g' (Just m)
|
||||
let cd = CDGroupRcv g' m
|
||||
unless (sameGroupProfileInfo p p') $ do
|
||||
ci <- saveRcvChatItem user cd msg brokerTs (CIRcvGroupEvent $ RGEGroupUpdated p')
|
||||
groupMsgToView g' ci
|
||||
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
|
||||
Just _ -> updateGroupPrefs_ g m $ fromMaybe defaultBusinessGroupPrefs $ groupPreferences p'
|
||||
|
||||
xGrpPrefs :: GroupInfo -> GroupMember -> GroupPreferences -> CM ()
|
||||
xGrpPrefs g m@GroupMember {memberRole} ps'
|
||||
| memberRole < GROwner = messageError "x.grp.prefs with insufficient member permissions"
|
||||
| otherwise = updateGroupPrefs_ g m ps'
|
||||
|
||||
updateGroupPrefs_ :: GroupInfo -> GroupMember -> GroupPreferences -> CM ()
|
||||
updateGroupPrefs_ g@GroupInfo {groupProfile = p} m ps' =
|
||||
unless (groupPreferences p == Just ps') $ do
|
||||
g' <- withStore' $ \db -> updateGroupPreferences db user g ps'
|
||||
toView $ CRGroupUpdated user g g' (Just m)
|
||||
let cd = CDGroupRcv g' m
|
||||
createGroupFeatureChangedItems user cd CIRcvGroupFeature g g'
|
||||
|
||||
xGrpDirectInv :: GroupInfo -> GroupMember -> Connection -> ConnReqInvitation -> Maybe MsgContent -> RcvMessage -> UTCTime -> CM ()
|
||||
xGrpDirectInv g m mConn connReq mContent_ msg brokerTs = do
|
||||
@@ -7098,6 +7128,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
|
||||
XGrpLeave -> xGrpLeave gInfo author rcvMsg msgTs
|
||||
XGrpDel -> xGrpDel gInfo author rcvMsg msgTs
|
||||
XGrpInfo p' -> xGrpInfo gInfo author p' rcvMsg msgTs
|
||||
XGrpPrefs ps' -> xGrpPrefs gInfo author ps'
|
||||
_ -> messageError $ "x.grp.msg.forward: unsupported forwarded event " <> T.pack (show $ toCMEventTag event)
|
||||
|
||||
createUnknownMember :: GroupInfo -> MemberId -> CM GroupMember
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
{-# LANGUAGE QuasiQuotes #-}
|
||||
|
||||
module Simplex.Chat.Migrations.M20241205_business_chat_members where
|
||||
|
||||
import Database.SQLite.Simple (Query)
|
||||
import Database.SQLite.Simple.QQ (sql)
|
||||
|
||||
m20241205_business_chat_members :: Query
|
||||
m20241205_business_chat_members =
|
||||
[sql|
|
||||
ALTER TABLE groups ADD COLUMN customer_member_id BLOB NULL;
|
||||
|]
|
||||
|
||||
down_m20241205_business_chat_members :: Query
|
||||
down_m20241205_business_chat_members =
|
||||
[sql|
|
||||
ALTER TABLE groups DROP COLUMN customer_member_id;
|
||||
|]
|
||||
@@ -130,7 +130,8 @@ CREATE TABLE groups(
|
||||
ui_themes TEXT,
|
||||
business_member_id BLOB NULL,
|
||||
business_chat TEXT NULL,
|
||||
business_xcontact_id BLOB NULL, -- received
|
||||
business_xcontact_id BLOB NULL,
|
||||
customer_member_id BLOB NULL, -- received
|
||||
FOREIGN KEY(user_id, local_display_name)
|
||||
REFERENCES display_names(user_id, local_display_name)
|
||||
ON DELETE CASCADE
|
||||
|
||||
@@ -46,6 +46,7 @@ import Database.SQLite.Simple.FromField (FromField (..))
|
||||
import Database.SQLite.Simple.ToField (ToField (..))
|
||||
import Simplex.Chat.Call
|
||||
import Simplex.Chat.Types
|
||||
import Simplex.Chat.Types.Preferences
|
||||
import Simplex.Chat.Types.Shared
|
||||
import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion)
|
||||
import Simplex.Messaging.Compression (Compressed, compress1, decompress1)
|
||||
@@ -67,12 +68,13 @@ import Simplex.Messaging.Version hiding (version)
|
||||
-- 8 - compress messages and PQ e2e encryption (2024-03-08)
|
||||
-- 9 - batch sending in direct connections (2024-07-24)
|
||||
-- 10 - business chats (2024-11-29)
|
||||
-- 11 - fix profile update in business chats (2024-12-05)
|
||||
|
||||
-- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig.
|
||||
-- This indirection is needed for backward/forward compatibility testing.
|
||||
-- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code.
|
||||
currentChatVersion :: VersionChat
|
||||
currentChatVersion = VersionChat 10
|
||||
currentChatVersion = VersionChat 11
|
||||
|
||||
-- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above)
|
||||
supportedChatVRange :: VersionRangeChat
|
||||
@@ -115,6 +117,10 @@ batchSend2Version = VersionChat 9
|
||||
businessChatsVersion :: VersionChat
|
||||
businessChatsVersion = VersionChat 10
|
||||
|
||||
-- support updating preferences in business chats (XGrpPrefs message)
|
||||
businessChatPrefsVersion :: VersionChat
|
||||
businessChatPrefsVersion = VersionChat 11
|
||||
|
||||
agentToChatVersion :: VersionSMPA -> VersionChat
|
||||
agentToChatVersion v
|
||||
| v < pqdrSMPAgentVersion = initialChatVersion
|
||||
@@ -299,6 +305,7 @@ data ChatMsgEvent (e :: MsgEncoding) where
|
||||
XGrpLeave :: ChatMsgEvent 'Json
|
||||
XGrpDel :: ChatMsgEvent 'Json
|
||||
XGrpInfo :: GroupProfile -> ChatMsgEvent 'Json
|
||||
XGrpPrefs :: GroupPreferences -> ChatMsgEvent 'Json
|
||||
XGrpDirectInv :: ConnReqInvitation -> Maybe MsgContent -> ChatMsgEvent 'Json
|
||||
XGrpMsgForward :: MemberId -> ChatMessage 'Json -> UTCTime -> ChatMsgEvent 'Json
|
||||
XInfoProbe :: Probe -> ChatMsgEvent 'Json
|
||||
@@ -339,6 +346,7 @@ isForwardedGroupMsg ev = case ev of
|
||||
XGrpLeave -> True
|
||||
XGrpDel -> True -- TODO there should be a special logic - host should forward before deleting connections
|
||||
XGrpInfo _ -> True
|
||||
XGrpPrefs _ -> True
|
||||
_ -> False
|
||||
|
||||
forwardedGroupMsg :: forall e. MsgEncodingI e => ChatMessage e -> Maybe (ChatMessage 'Json)
|
||||
@@ -721,6 +729,7 @@ data CMEventTag (e :: MsgEncoding) where
|
||||
XGrpLeave_ :: CMEventTag 'Json
|
||||
XGrpDel_ :: CMEventTag 'Json
|
||||
XGrpInfo_ :: CMEventTag 'Json
|
||||
XGrpPrefs_ :: CMEventTag 'Json
|
||||
XGrpDirectInv_ :: CMEventTag 'Json
|
||||
XGrpMsgForward_ :: CMEventTag 'Json
|
||||
XInfoProbe_ :: CMEventTag 'Json
|
||||
@@ -771,6 +780,7 @@ instance MsgEncodingI e => StrEncoding (CMEventTag e) where
|
||||
XGrpLeave_ -> "x.grp.leave"
|
||||
XGrpDel_ -> "x.grp.del"
|
||||
XGrpInfo_ -> "x.grp.info"
|
||||
XGrpPrefs_ -> "x.grp.prefs"
|
||||
XGrpDirectInv_ -> "x.grp.direct.inv"
|
||||
XGrpMsgForward_ -> "x.grp.msg.forward"
|
||||
XInfoProbe_ -> "x.info.probe"
|
||||
@@ -822,6 +832,7 @@ instance StrEncoding ACMEventTag where
|
||||
"x.grp.leave" -> XGrpLeave_
|
||||
"x.grp.del" -> XGrpDel_
|
||||
"x.grp.info" -> XGrpInfo_
|
||||
"x.grp.prefs" -> XGrpPrefs_
|
||||
"x.grp.direct.inv" -> XGrpDirectInv_
|
||||
"x.grp.msg.forward" -> XGrpMsgForward_
|
||||
"x.info.probe" -> XInfoProbe_
|
||||
@@ -869,6 +880,7 @@ toCMEventTag msg = case msg of
|
||||
XGrpLeave -> XGrpLeave_
|
||||
XGrpDel -> XGrpDel_
|
||||
XGrpInfo _ -> XGrpInfo_
|
||||
XGrpPrefs _ -> XGrpPrefs_
|
||||
XGrpDirectInv _ _ -> XGrpDirectInv_
|
||||
XGrpMsgForward {} -> XGrpMsgForward_
|
||||
XInfoProbe _ -> XInfoProbe_
|
||||
@@ -969,6 +981,7 @@ appJsonToCM AppMessageJson {v, msgId, event, params} = do
|
||||
XGrpLeave_ -> pure XGrpLeave
|
||||
XGrpDel_ -> pure XGrpDel
|
||||
XGrpInfo_ -> XGrpInfo <$> p "groupProfile"
|
||||
XGrpPrefs_ -> XGrpPrefs <$> p "groupPreferences"
|
||||
XGrpDirectInv_ -> XGrpDirectInv <$> p "connReq" <*> opt "content"
|
||||
XGrpMsgForward_ -> XGrpMsgForward <$> p "memberId" <*> p "msg" <*> p "msgTs"
|
||||
XInfoProbe_ -> XInfoProbe <$> p "probe"
|
||||
@@ -1030,6 +1043,7 @@ chatToAppMessage ChatMessage {chatVRange, msgId, chatMsgEvent} = case encoding @
|
||||
XGrpLeave -> JM.empty
|
||||
XGrpDel -> JM.empty
|
||||
XGrpInfo p -> o ["groupProfile" .= p]
|
||||
XGrpPrefs p -> o ["groupPreferences" .= p]
|
||||
XGrpDirectInv connReq content -> o $ ("content" .=? content) ["connReq" .= connReq]
|
||||
XGrpMsgForward memberId msg msgTs -> o ["memberId" .= memberId, "msg" .= msg, "msgTs" .= msgTs]
|
||||
XInfoProbe probe -> o ["probe" .= probe]
|
||||
|
||||
@@ -123,7 +123,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
|
||||
-- GroupInfo {membership}
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
|
||||
@@ -39,6 +39,7 @@ module Simplex.Chat.Store.Groups
|
||||
getGroupInfoByUserContactLinkConnReq,
|
||||
getGroupInfoByGroupLinkHash,
|
||||
updateGroupProfile,
|
||||
updateGroupPreferences,
|
||||
updateGroupProfileFromMember,
|
||||
getGroupIdByName,
|
||||
getGroupMemberIdByName,
|
||||
@@ -257,7 +258,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr =
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
|
||||
-- GroupInfo {membership}
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
@@ -339,7 +340,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc
|
||||
-- | creates a new group record for the group the current user was invited to, or returns an existing one
|
||||
createGroupInvitation :: DB.Connection -> VersionRangeChat -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId)
|
||||
createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName
|
||||
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, businessChat} incognitoProfileId = do
|
||||
createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile, business} incognitoProfileId = do
|
||||
liftIO getInvitationGroupId_ >>= \case
|
||||
Nothing -> createGroupInvitation_
|
||||
Just gId -> do
|
||||
@@ -377,10 +378,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
|
||||
[sql|
|
||||
INSERT INTO groups
|
||||
(group_profile_id, local_display_name, inv_queue_info, host_conn_custom_user_profile_id, user_id, enable_ntfs,
|
||||
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat)
|
||||
((profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
|
||||
insertedRowId db
|
||||
let hostVRange = adjustedMemberVRange vr peerChatVRange
|
||||
GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange
|
||||
@@ -406,10 +407,10 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ
|
||||
groupMemberId
|
||||
)
|
||||
|
||||
businessChatTuple :: Maybe BusinessChatInfo -> (Maybe MemberId, Maybe BusinessChatType)
|
||||
businessChatTuple = \case
|
||||
Just BusinessChatInfo {memberId, chatType} -> (Just memberId, Just chatType)
|
||||
Nothing -> (Nothing, Nothing)
|
||||
businessChatInfoRow :: Maybe BusinessChatInfo -> BusinessChatInfoRow
|
||||
businessChatInfoRow = \case
|
||||
Just BusinessChatInfo {chatType, businessId, customerId} -> (Just chatType, Just businessId, Just customerId)
|
||||
Nothing -> (Nothing, Nothing, Nothing)
|
||||
|
||||
adjustedMemberVRange :: VersionRangeChat -> VersionRangeChat -> VersionRangeChat
|
||||
adjustedMemberVRange chatVR vr@(VersionRange minV maxV) =
|
||||
@@ -497,7 +498,7 @@ createGroupInvitedViaLink
|
||||
vr
|
||||
user@User {userId, userContactId}
|
||||
Connection {connId, customUserProfileId}
|
||||
GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, businessChat} = do
|
||||
GroupLinkInvitation {fromMember, fromMemberName, invitedMember, groupProfile, business} = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
groupId <- insertGroup_ currentTs
|
||||
hostMemberId <- insertHost_ currentTs groupId
|
||||
@@ -521,10 +522,10 @@ createGroupInvitedViaLink
|
||||
[sql|
|
||||
INSERT INTO groups
|
||||
(group_profile_id, local_display_name, host_conn_custom_user_profile_id, user_id, enable_ntfs,
|
||||
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_member_id, business_chat)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?)
|
||||
created_at, updated_at, chat_ts, user_member_profile_sent_at, business_chat, business_member_id, customer_member_id)
|
||||
VALUES (?,?,?,?,?,?,?,?,?,?,?,?)
|
||||
|]
|
||||
((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatTuple businessChat)
|
||||
((profileId, localDisplayName, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) :. businessChatInfoRow business)
|
||||
insertedRowId db
|
||||
insertHost_ currentTs groupId = do
|
||||
let fromMemberProfile = profileFromName fromMemberName
|
||||
@@ -631,7 +632,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ =
|
||||
SELECT
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
|
||||
mu.group_member_id, g.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category, mu.member_status, mu.show_messages, mu.member_restriction,
|
||||
mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id, pu.display_name, pu.full_name, pu.image, pu.contact_link, pu.local_alias, pu.preferences
|
||||
FROM groups g
|
||||
@@ -918,9 +919,9 @@ createBusinessRequestGroup
|
||||
UserContactRequest {cReqChatVRange, xContactId, profile = Profile {displayName, fullName, image, contactLink, preferences}}
|
||||
groupPreferences = do
|
||||
currentTs <- liftIO getCurrentTime
|
||||
(groupId, membership) <- insertGroup_ currentTs
|
||||
(groupId, membership@GroupMember {memberId = userMemberId}) <- insertGroup_ currentTs
|
||||
(groupMemberId, memberId) <- insertClientMember_ currentTs groupId membership
|
||||
liftIO $ DB.execute db "UPDATE groups SET business_member_id = ? WHERE group_id = ?" (memberId, groupId)
|
||||
liftIO $ DB.execute db "UPDATE groups SET business_member_id = ?, customer_member_id = ? WHERE group_id = ?" (userMemberId, memberId, groupId)
|
||||
groupInfo <- getGroupInfo db vr user groupId
|
||||
clientMember <- getGroupMemberById db vr user groupMemberId
|
||||
pure (groupInfo, clientMember)
|
||||
@@ -1370,7 +1371,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} =
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
|
||||
-- GroupInfo {membership}
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
@@ -1456,6 +1457,23 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName,
|
||||
(ldn, currentTs, userId, groupId)
|
||||
safeDeleteLDN db user localDisplayName
|
||||
|
||||
updateGroupPreferences :: DB.Connection -> User -> GroupInfo -> GroupPreferences -> IO GroupInfo
|
||||
updateGroupPreferences db User {userId} g@GroupInfo {groupId, groupProfile = p} ps = do
|
||||
currentTs <- getCurrentTime
|
||||
DB.execute
|
||||
db
|
||||
[sql|
|
||||
UPDATE group_profiles
|
||||
SET preferences = ?, updated_at = ?
|
||||
WHERE group_profile_id IN (
|
||||
SELECT group_profile_id
|
||||
FROM groups
|
||||
WHERE user_id = ? AND group_id = ?
|
||||
)
|
||||
|]
|
||||
(ps, currentTs, userId, groupId)
|
||||
pure (g :: GroupInfo) {groupProfile = p {groupPreferences = Just ps}}
|
||||
|
||||
updateGroupProfileFromMember :: DB.Connection -> User -> GroupInfo -> Profile -> ExceptT StoreError IO GroupInfo
|
||||
updateGroupProfileFromMember db user g@GroupInfo {groupId} Profile {displayName = n, fullName = fn, image = img} = do
|
||||
p <- getGroupProfile -- to avoid any race conditions with UI
|
||||
|
||||
@@ -118,6 +118,7 @@ import Simplex.Chat.Migrations.M20241023_chat_item_autoincrement_id
|
||||
import Simplex.Chat.Migrations.M20241027_server_operators
|
||||
import Simplex.Chat.Migrations.M20241125_indexes
|
||||
import Simplex.Chat.Migrations.M20241128_business_chats
|
||||
import Simplex.Chat.Migrations.M20241205_business_chat_members
|
||||
import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..))
|
||||
|
||||
schemaMigrations :: [(String, Query, Maybe Query)]
|
||||
@@ -235,7 +236,8 @@ schemaMigrations =
|
||||
("20241023_chat_item_autoincrement_id", m20241023_chat_item_autoincrement_id, Just down_m20241023_chat_item_autoincrement_id),
|
||||
("20241027_server_operators", m20241027_server_operators, Just down_m20241027_server_operators),
|
||||
("20241125_indexes", m20241125_indexes, Just down_m20241125_indexes),
|
||||
("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats)
|
||||
("20241128_business_chats", m20241128_business_chats, Just down_m20241128_business_chats),
|
||||
("20241205_business_chat_members", m20241205_business_chat_members, Just down_m20241205_business_chat_members)
|
||||
]
|
||||
|
||||
-- | The list of migrations in ascending order by date
|
||||
|
||||
@@ -546,17 +546,19 @@ safeDeleteLDN db User {userId} localDisplayName = do
|
||||
|]
|
||||
(userId, localDisplayName, userId)
|
||||
|
||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime, Maybe MemberId, Maybe BusinessChatType, Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow
|
||||
type BusinessChatInfoRow = (Maybe BusinessChatType, Maybe MemberId, Maybe MemberId)
|
||||
|
||||
type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. BusinessChatInfoRow :. (Maybe UIThemeEntityOverrides, Maybe CustomData) :. GroupMemberRow
|
||||
|
||||
type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences))
|
||||
|
||||
toGroupInfo :: VersionRangeChat -> Int64 -> GroupInfoRow -> GroupInfo
|
||||
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt, businessMemberId, businessChatType, uiThemes, customData) :. userMemberRow) =
|
||||
toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. businessRow :. (uiThemes, customData) :. userMemberRow) =
|
||||
let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr}
|
||||
chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite}
|
||||
fullGroupPreferences = mergeGroupPreferences groupPreferences
|
||||
groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences}
|
||||
businessChat = BusinessChatInfo <$> businessMemberId <*> businessChatType
|
||||
businessChat = toBusinessChatInfo businessRow
|
||||
in GroupInfo {groupId, localDisplayName, groupProfile, businessChat, fullGroupPreferences, membership, hostConnCustomUserProfileId, chatSettings, createdAt, updatedAt, chatTs, userMemberProfileSentAt, uiThemes, customData}
|
||||
|
||||
toGroupMember :: Int64 -> GroupMemberRow -> GroupMember
|
||||
@@ -569,6 +571,10 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer,
|
||||
memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer
|
||||
in GroupMember {..}
|
||||
|
||||
toBusinessChatInfo :: BusinessChatInfoRow -> Maybe BusinessChatInfo
|
||||
toBusinessChatInfo (Just chatType, Just businessId, Just customerId) = Just BusinessChatInfo {chatType, businessId, customerId}
|
||||
toBusinessChatInfo _ = Nothing
|
||||
|
||||
groupInfoQuery :: Query
|
||||
groupInfoQuery =
|
||||
[sql|
|
||||
@@ -576,7 +582,7 @@ groupInfoQuery =
|
||||
-- GroupInfo
|
||||
g.group_id, g.local_display_name, gp.display_name, gp.full_name, gp.description, gp.image,
|
||||
g.host_conn_custom_user_profile_id, g.enable_ntfs, g.send_rcpts, g.favorite, gp.preferences,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_member_id, g.business_chat, g.ui_themes, g.custom_data,
|
||||
g.created_at, g.updated_at, g.chat_ts, g.user_member_profile_sent_at, g.business_chat, g.business_member_id, g.customer_member_id, g.ui_themes, g.custom_data,
|
||||
-- GroupMember - membership
|
||||
mu.group_member_id, mu.group_id, mu.member_id, mu.peer_chat_min_version, mu.peer_chat_max_version, mu.member_role, mu.member_category,
|
||||
mu.member_status, mu.show_messages, mu.member_restriction, mu.invited_by, mu.invited_by_group_member_id, mu.local_display_name, mu.contact_id, mu.contact_profile_id, pu.contact_profile_id,
|
||||
|
||||
@@ -617,7 +617,7 @@ data GroupInvitation = GroupInvitation
|
||||
invitedMember :: MemberIdRole,
|
||||
connRequest :: ConnReqInvitation,
|
||||
groupProfile :: GroupProfile,
|
||||
businessChat :: Maybe BusinessChatInfo,
|
||||
business :: Maybe BusinessChatInfo,
|
||||
groupLinkId :: Maybe GroupLinkId,
|
||||
groupSize :: Maybe Int
|
||||
}
|
||||
@@ -628,7 +628,7 @@ data GroupLinkInvitation = GroupLinkInvitation
|
||||
fromMemberName :: ContactName,
|
||||
invitedMember :: MemberIdRole,
|
||||
groupProfile :: GroupProfile,
|
||||
businessChat :: Maybe BusinessChatInfo,
|
||||
business :: Maybe BusinessChatInfo,
|
||||
groupSize :: Maybe Int
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
@@ -654,8 +654,9 @@ data MemberInfo = MemberInfo
|
||||
deriving (Eq, Show)
|
||||
|
||||
data BusinessChatInfo = BusinessChatInfo
|
||||
{ memberId :: MemberId,
|
||||
chatType :: BusinessChatType
|
||||
{ chatType :: BusinessChatType,
|
||||
businessId :: MemberId,
|
||||
customerId :: MemberId
|
||||
}
|
||||
deriving (Eq, Show)
|
||||
|
||||
|
||||
@@ -734,8 +734,8 @@ testBusinessAddress = testChat3 businessProfile aliceProfile {fullName = "Alice
|
||||
(biz <# "#bob bob_1> hey there")
|
||||
|
||||
testBusinessUpdateProfiles :: HasCallStack => FilePath -> IO ()
|
||||
testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $
|
||||
\biz alice bob -> do
|
||||
testBusinessUpdateProfiles = testChat4 businessProfile aliceProfile bobProfile cathProfile $
|
||||
\biz alice bob cath -> do
|
||||
biz ##> "/ad"
|
||||
cLink <- getContactLink biz True
|
||||
biz ##> "/auto_accept on business text Welcome"
|
||||
@@ -794,9 +794,37 @@ testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $
|
||||
bob #> "#biz hi there" -- profile update is sent to group with message
|
||||
alice <# "#biz robert> hi there"
|
||||
biz <# "#alisa robert> hi there"
|
||||
-- add business team member
|
||||
connectUsers biz cath
|
||||
biz ##> "/a #alisa cath"
|
||||
biz <## "invitation to join the group #alisa sent to cath"
|
||||
cath <## "#alisa: biz invites you to join the group as member"
|
||||
cath <## "use /j alisa to accept"
|
||||
cath ##> "/j alisa"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
cath <## "#alisa: you joined the group"
|
||||
cath
|
||||
<###
|
||||
[ WithTime "#alisa biz> Welcome [>>]",
|
||||
WithTime "#alisa biz> hi [>>]",
|
||||
WithTime "#alisa alisa_1> hello [>>]",
|
||||
WithTime "#alisa alisa_1> hello again [>>]",
|
||||
WithTime "#alisa robert> hi there [>>]"
|
||||
]
|
||||
cath <## "#alisa: member alisa_1 is connected"
|
||||
cath <## "#alisa: member robert is connected",
|
||||
biz <## "#alisa: cath joined the group",
|
||||
do
|
||||
alice <## "#biz: biz_1 added cath (Catherine) to the group (connecting...)"
|
||||
alice <## "#biz: new member cath is connected",
|
||||
do
|
||||
bob <## "#biz: biz_1 added cath (Catherine) to the group (connecting...)"
|
||||
bob <## "#biz: new member cath is connected"
|
||||
]
|
||||
-- both customers receive business profile change
|
||||
biz ##> "/p business"
|
||||
biz <## "user profile is changed to business (your 0 contacts are notified)"
|
||||
biz <## "user profile is changed to business (your 1 contacts are notified)"
|
||||
biz #> "#alisa hey"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
@@ -806,7 +834,28 @@ testBusinessUpdateProfiles = testChat3 businessProfile aliceProfile bobProfile $
|
||||
do
|
||||
bob <## "biz_1 updated group #biz:"
|
||||
bob <## "changed to #business"
|
||||
bob <# "#business business_1> hey"
|
||||
bob <# "#business business_1> hey",
|
||||
do
|
||||
cath <## "contact biz changed to business"
|
||||
cath <## "use @business <message> to send messages"
|
||||
cath <# "#alisa business> hey"
|
||||
]
|
||||
biz ##> "/set voice #alisa on"
|
||||
biz <## "updated group preferences:"
|
||||
biz <## "Voice messages: on"
|
||||
concurrentlyN_
|
||||
[ do
|
||||
alice <## "business_1 updated group #business:"
|
||||
alice <## "updated group preferences:"
|
||||
alice <## "Voice messages: on",
|
||||
do
|
||||
bob <## "business_1 updated group #business:"
|
||||
bob <## "updated group preferences:"
|
||||
bob <## "Voice messages: on",
|
||||
do
|
||||
cath <## "business updated group #alisa:"
|
||||
cath <## "updated group preferences:"
|
||||
cath <## "Voice messages: on"
|
||||
]
|
||||
|
||||
testPlanAddressOkKnown :: HasCallStack => FilePath -> IO ()
|
||||
@@ -2512,7 +2561,7 @@ testSetUITheme =
|
||||
a <## "you've shared main profile with this contact"
|
||||
a <## "connection not verified, use /code command to see security code"
|
||||
a <## "quantum resistant end-to-end encryption"
|
||||
a <## "peer chat protocol version range: (Version 1, Version 10)"
|
||||
a <## "peer chat protocol version range: (Version 1, Version 11)"
|
||||
groupInfo a = do
|
||||
a <## "group ID: 1"
|
||||
a <## "current members: 1"
|
||||
|
||||
@@ -133,7 +133,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||
it "x.msg.new chat message with chat version range" $
|
||||
"{\"v\":\"1-10\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
"{\"v\":\"1-11\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}"
|
||||
##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing)))
|
||||
it "x.msg.new quote" $
|
||||
"{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}"
|
||||
@@ -232,10 +232,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||
==# XContact testProfile Nothing
|
||||
it "x.grp.inv" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}"
|
||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Nothing, groupSize = Nothing}
|
||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Nothing, groupSize = Nothing}
|
||||
it "x.grp.inv with group link id" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}"
|
||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, businessChat = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing}
|
||||
#==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, business = Nothing, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing}
|
||||
it "x.grp.acpt without incognito profile" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}"
|
||||
#==# XGrpAcpt (MemberId "\1\2\3\4")
|
||||
@@ -243,13 +243,13 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile}
|
||||
it "x.grp.mem.new with member chat version range" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-11\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile}
|
||||
it "x.grp.mem.intro" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing
|
||||
it "x.grp.mem.intro with member chat version range" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-11\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} Nothing
|
||||
it "x.grp.mem.intro with member restrictions" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
@@ -264,7 +264,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq}
|
||||
it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-10\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-3%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-11\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}"
|
||||
#==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing}
|
||||
it "x.grp.mem.info" $
|
||||
"{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}"
|
||||
|
||||
Reference in New Issue
Block a user