core: include pending group link connections into chat previews on joining side (#1291)

This commit is contained in:
JRoberts 2022-11-04 12:00:03 +04:00 committed by GitHub
parent 5243613045
commit d432dfba21
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 33 additions and 26 deletions

View File

@ -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.

View File

@ -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": {

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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