core: include pending group link connections into chat previews on joining side (#1291)
This commit is contained in:
parent
5243613045
commit
d432dfba21
@ -199,7 +199,7 @@ Currently members can have one of three roles - `owner`, `admin` and `member`. T
|
|||||||
|
|
||||||
### Messages to manage groups and add members
|
### Messages to manage groups and add members
|
||||||
|
|
||||||
`x.grp.inv` message is sent to invite contact to the group via contact's direct connection and includes group member connection address. This message MUST only be sent by members with `admin` or `owner` role.
|
`x.grp.inv` message is sent to invite contact to the group via contact's direct connection and includes group member connection address. This message MUST only be sent by members with `admin` or `owner` role. Optional `groupLinkId` is included when this message is sent to contacts connected via the user's group link. This identifier is a random byte sequence, with no global or even local uniqueness - it is only used for the user's invitations to a given group to provide confirmation to the contact that the group invitation is for the same group the contact was connecting to via the group link, so that the invitation can be automatically accepted by the contact - the contact compares it with the group link id contained in the group link uri's data field.
|
||||||
|
|
||||||
`x.grp.acpt` message is sent as part of group member connection handshake, only to the inviting user.
|
`x.grp.acpt` message is sent as part of group member connection handshake, only to the inviting user.
|
||||||
|
|
||||||
|
@ -108,6 +108,12 @@
|
|||||||
"invitedMember": {"ref": "memberIdRole"},
|
"invitedMember": {"ref": "memberIdRole"},
|
||||||
"connRequest": {"ref": "connReqUri"},
|
"connRequest": {"ref": "connReqUri"},
|
||||||
"groupProfile": {"ref": "profile"}
|
"groupProfile": {"ref": "profile"}
|
||||||
|
},
|
||||||
|
"optionalProperties": {
|
||||||
|
"groupLinkId": {"ref": "base64url"},
|
||||||
|
"metadata": {
|
||||||
|
"comment": "used to identify invitation via group link"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"memberIdRole": {
|
"memberIdRole": {
|
||||||
|
@ -987,7 +987,7 @@ processChatCommand = \case
|
|||||||
when (memberStatus membership == GSMemInvited) $ throwChatError (CEGroupNotJoined gInfo)
|
when (memberStatus membership == GSMemInvited) $ throwChatError (CEGroupNotJoined gInfo)
|
||||||
unless (memberActive membership) $ throwChatError CEGroupMemberNotActive
|
unless (memberActive membership) $ throwChatError CEGroupMemberNotActive
|
||||||
groupLinkId <- GroupLinkId <$> (asks idsDrg >>= liftIO . (`randomBytes` 16))
|
groupLinkId <- GroupLinkId <$> (asks idsDrg >>= liftIO . (`randomBytes` 16))
|
||||||
let crClientData = encodeJson $ CRGroupData groupLinkId
|
let crClientData = encodeJSON $ CRGroupData groupLinkId
|
||||||
(connId, cReq) <- withAgent $ \a -> createConnection a True SCMContact $ Just crClientData
|
(connId, cReq) <- withAgent $ \a -> createConnection a True SCMContact $ Just crClientData
|
||||||
withStore $ \db -> createGroupLink db user gInfo connId cReq groupLinkId
|
withStore $ \db -> createGroupLink db user gInfo connId cReq groupLinkId
|
||||||
pure $ CRGroupLinkCreated gInfo cReq
|
pure $ CRGroupLinkCreated gInfo cReq
|
||||||
@ -1125,7 +1125,7 @@ processChatCommand = \case
|
|||||||
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing
|
||||||
let profileToSend = fromMaybe profile incognitoProfile
|
let profileToSend = fromMaybe profile incognitoProfile
|
||||||
connId <- withAgent $ \a -> joinConnection a True cReq $ directMessage (XContact profileToSend $ Just xContactId)
|
connId <- withAgent $ \a -> joinConnection a True cReq $ directMessage (XContact profileToSend $ Just xContactId)
|
||||||
let groupLinkId = crClientData >>= decodeJson >>= \(CRGroupData gli) -> Just gli
|
let groupLinkId = crClientData >>= decodeJSON >>= \(CRGroupData gli) -> Just gli
|
||||||
conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId
|
conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId
|
||||||
toView $ CRNewContactConnection conn
|
toView $ CRNewContactConnection conn
|
||||||
pure $ CRSentInvitation incognitoProfile
|
pure $ CRSentInvitation incognitoProfile
|
||||||
|
@ -21,7 +21,7 @@ import Data.Time.Clock (UTCTime)
|
|||||||
import Database.SQLite.Simple.FromField (FromField (..))
|
import Database.SQLite.Simple.FromField (FromField (..))
|
||||||
import Database.SQLite.Simple.ToField (ToField (..))
|
import Database.SQLite.Simple.ToField (ToField (..))
|
||||||
import GHC.Generics (Generic)
|
import GHC.Generics (Generic)
|
||||||
import Simplex.Chat.Types (Contact, ContactId, decodeJson, encodeJson)
|
import Simplex.Chat.Types (Contact, ContactId, decodeJSON, encodeJSON)
|
||||||
import qualified Simplex.Messaging.Crypto as C
|
import qualified Simplex.Messaging.Crypto as C
|
||||||
import Simplex.Messaging.Encoding.String
|
import Simplex.Messaging.Encoding.String
|
||||||
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
|
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
|
||||||
@ -100,10 +100,10 @@ instance ToJSON CallState where
|
|||||||
toEncoding = J.genericToEncoding $ singleFieldJSON fstToLower
|
toEncoding = J.genericToEncoding $ singleFieldJSON fstToLower
|
||||||
|
|
||||||
instance ToField CallState where
|
instance ToField CallState where
|
||||||
toField = toField . encodeJson
|
toField = toField . encodeJSON
|
||||||
|
|
||||||
instance FromField CallState where
|
instance FromField CallState where
|
||||||
fromField = fromTextField_ decodeJson
|
fromField = fromTextField_ decodeJSON
|
||||||
|
|
||||||
newtype CallId = CallId ByteString
|
newtype CallId = CallId ByteString
|
||||||
deriving (Eq, Show)
|
deriving (Eq, Show)
|
||||||
|
@ -726,7 +726,7 @@ msgDirToDeletedContent_ msgDir mode = case msgDir of
|
|||||||
|
|
||||||
-- platform independent
|
-- platform independent
|
||||||
instance ToField (CIContent d) where
|
instance ToField (CIContent d) where
|
||||||
toField = toField . encodeJson . dbJsonCIContent
|
toField = toField . encodeJSON . dbJsonCIContent
|
||||||
|
|
||||||
-- platform specific
|
-- platform specific
|
||||||
instance ToJSON (CIContent d) where
|
instance ToJSON (CIContent d) where
|
||||||
@ -742,7 +742,7 @@ instance FromJSON ACIContent where
|
|||||||
parseJSON = fmap aciContentJSON . J.parseJSON
|
parseJSON = fmap aciContentJSON . J.parseJSON
|
||||||
|
|
||||||
-- platform independent
|
-- platform independent
|
||||||
instance FromField ACIContent where fromField = fromTextField_ $ fmap aciContentDBJSON . decodeJson
|
instance FromField ACIContent where fromField = fromTextField_ $ fmap aciContentDBJSON . decodeJSON
|
||||||
|
|
||||||
-- platform specific
|
-- platform specific
|
||||||
data JSONCIContent
|
data JSONCIContent
|
||||||
|
@ -391,10 +391,10 @@ instance ToJSON MsgContent where
|
|||||||
MCFile t -> J.pairs $ "type" .= MCFile_ <> "text" .= t
|
MCFile t -> J.pairs $ "type" .= MCFile_ <> "text" .= t
|
||||||
|
|
||||||
instance ToField MsgContent where
|
instance ToField MsgContent where
|
||||||
toField = toField . encodeJson
|
toField = toField . encodeJSON
|
||||||
|
|
||||||
instance FromField MsgContent where
|
instance FromField MsgContent where
|
||||||
fromField = fromTextField_ decodeJson
|
fromField = fromTextField_ decodeJSON
|
||||||
|
|
||||||
data CMEventTag (e :: MsgEncoding) where
|
data CMEventTag (e :: MsgEncoding) where
|
||||||
XMsgNew_ :: CMEventTag 'Json
|
XMsgNew_ :: CMEventTag 'Json
|
||||||
|
@ -434,7 +434,7 @@ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile grou
|
|||||||
|]
|
|]
|
||||||
((userId, acId, pccConnStatus, ConnContact, cReqHash, xContactId) :. (customUserProfileId, isJust groupLinkId, groupLinkId, createdAt, createdAt))
|
((userId, acId, pccConnStatus, ConnContact, cReqHash, xContactId) :. (customUserProfileId, isJust groupLinkId, groupLinkId, createdAt, createdAt))
|
||||||
pccConnId <- insertedRowId db
|
pccConnId <- insertedRowId db
|
||||||
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt}
|
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt}
|
||||||
|
|
||||||
getConnReqContactXContactId :: DB.Connection -> UserId -> ConnReqUriHash -> IO (Maybe Contact, Maybe XContactId)
|
getConnReqContactXContactId :: DB.Connection -> UserId -> ConnReqUriHash -> IO (Maybe Contact, Maybe XContactId)
|
||||||
getConnReqContactXContactId db userId cReqHash = do
|
getConnReqContactXContactId db userId cReqHash = do
|
||||||
@ -482,7 +482,7 @@ createDirectConnection db userId acId cReq pccConnStatus incognitoProfile = do
|
|||||||
|]
|
|]
|
||||||
(userId, acId, cReq, pccConnStatus, ConnContact, customUserProfileId, createdAt, createdAt)
|
(userId, acId, cReq, pccConnStatus, ConnContact, customUserProfileId, createdAt, createdAt)
|
||||||
pccConnId <- insertedRowId db
|
pccConnId <- insertedRowId db
|
||||||
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt}
|
pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt}
|
||||||
|
|
||||||
createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Profile -> IO Int64
|
createIncognitoProfile_ :: DB.Connection -> UserId -> UTCTime -> Profile -> IO Int64
|
||||||
createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, image} = do
|
createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, image} = do
|
||||||
@ -1203,7 +1203,7 @@ getPendingContactConnections db User {userId} = do
|
|||||||
<$> DB.queryNamed
|
<$> DB.queryNamed
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||||
FROM connections
|
FROM connections
|
||||||
WHERE user_id = :user_id
|
WHERE user_id = :user_id
|
||||||
AND conn_type = :conn_type
|
AND conn_type = :conn_type
|
||||||
@ -3342,13 +3342,13 @@ getContactConnectionChatPreviews_ db User {userId} _ =
|
|||||||
<$> DB.query
|
<$> DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||||
FROM connections
|
FROM connections
|
||||||
WHERE user_id = ? AND conn_type = ? AND contact_id IS NULL AND conn_level = 0 AND via_group_link = 0 AND via_contact IS NULL
|
WHERE user_id = ? AND conn_type = ? AND contact_id IS NULL AND conn_level = 0 AND via_contact IS NULL AND (via_group_link = 0 || (via_group_link = 1 AND group_link_id IS NOT NULL))
|
||||||
|]
|
|]
|
||||||
(userId, ConnContact)
|
(userId, ConnContact)
|
||||||
where
|
where
|
||||||
toContactConnectionChatPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChat
|
toContactConnectionChatPreview :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> AChat
|
||||||
toContactConnectionChatPreview connRow =
|
toContactConnectionChatPreview connRow =
|
||||||
let conn = toPendingContactConnection connRow
|
let conn = toPendingContactConnection connRow
|
||||||
stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
|
stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False}
|
||||||
@ -3360,7 +3360,7 @@ getPendingContactConnection db userId connId = do
|
|||||||
DB.query
|
DB.query
|
||||||
db
|
db
|
||||||
[sql|
|
[sql|
|
||||||
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
SELECT connection_id, agent_conn_id, conn_status, via_contact_uri_hash, via_user_contact_link, group_link_id, custom_user_profile_id, conn_req_inv, local_alias, created_at, updated_at
|
||||||
FROM connections
|
FROM connections
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
AND connection_id = ?
|
AND connection_id = ?
|
||||||
@ -3394,9 +3394,9 @@ updateGroupSettings :: DB.Connection -> User -> Int64 -> ChatSettings -> IO ()
|
|||||||
updateGroupSettings db User {userId} groupId ChatSettings {enableNtfs} =
|
updateGroupSettings db User {userId} groupId ChatSettings {enableNtfs} =
|
||||||
DB.execute db "UPDATE groups SET enable_ntfs = ? WHERE user_id = ? AND group_id = ?" (enableNtfs, userId, groupId)
|
DB.execute db "UPDATE groups SET enable_ntfs = ? WHERE user_id = ? AND group_id = ?" (enableNtfs, userId, groupId)
|
||||||
|
|
||||||
toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> PendingContactConnection
|
toPendingContactConnection :: (Int64, ConnId, ConnStatus, Maybe ByteString, Maybe Int64, Maybe GroupLinkId, Maybe Int64, Maybe ConnReqInvitation, LocalAlias, UTCTime, UTCTime) -> PendingContactConnection
|
||||||
toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt) =
|
toPendingContactConnection (pccConnId, acId, pccConnStatus, connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt) =
|
||||||
PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt}
|
PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = isJust connReqHash, viaUserContactLink, groupLinkId, customUserProfileId, connReqInv, localAlias, createdAt, updatedAt}
|
||||||
|
|
||||||
getDirectChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect)
|
getDirectChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect)
|
||||||
getDirectChat db user contactId pagination search_ = do
|
getDirectChat db user contactId pagination search_ = do
|
||||||
|
@ -252,10 +252,10 @@ instance ToJSON ChatPreferences where
|
|||||||
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
|
||||||
|
|
||||||
instance ToField ChatPreferences where
|
instance ToField ChatPreferences where
|
||||||
toField = toField . encodeJson
|
toField = toField . encodeJSON
|
||||||
|
|
||||||
instance FromField ChatPreferences where
|
instance FromField ChatPreferences where
|
||||||
fromField = fromTextField_ decodeJson
|
fromField = fromTextField_ decodeJSON
|
||||||
|
|
||||||
data Preference = Preference
|
data Preference = Preference
|
||||||
{enable :: PrefSwitch}
|
{enable :: PrefSwitch}
|
||||||
@ -935,6 +935,7 @@ data PendingContactConnection = PendingContactConnection
|
|||||||
pccConnStatus :: ConnStatus,
|
pccConnStatus :: ConnStatus,
|
||||||
viaContactUri :: Bool,
|
viaContactUri :: Bool,
|
||||||
viaUserContactLink :: Maybe Int64,
|
viaUserContactLink :: Maybe Int64,
|
||||||
|
groupLinkId :: Maybe GroupLinkId,
|
||||||
customUserProfileId :: Maybe Int64,
|
customUserProfileId :: Maybe Int64,
|
||||||
connReqInv :: Maybe ConnReqInvitation,
|
connReqInv :: Maybe ConnReqInvitation,
|
||||||
localAlias :: Text,
|
localAlias :: Text,
|
||||||
@ -1164,8 +1165,8 @@ data XGrpMemIntroCont = XGrpMemIntroCont
|
|||||||
}
|
}
|
||||||
deriving (Show)
|
deriving (Show)
|
||||||
|
|
||||||
encodeJson :: ToJSON a => a -> Text
|
encodeJSON :: ToJSON a => a -> Text
|
||||||
encodeJson = safeDecodeUtf8 . LB.toStrict . J.encode
|
encodeJSON = safeDecodeUtf8 . LB.toStrict . J.encode
|
||||||
|
|
||||||
decodeJson :: FromJSON a => Text -> Maybe a
|
decodeJSON :: FromJSON a => Text -> Maybe a
|
||||||
decodeJson = J.decode . LB.fromStrict . encodeUtf8
|
decodeJSON = J.decode . LB.fromStrict . encodeUtf8
|
||||||
|
Loading…
Reference in New Issue
Block a user