mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-04-25 07:42:15 +00:00
* rfc: member keys * update plan * new encoding for message batches * send new batch encoding in relay-based groups * mvp launch plan * update plan * core: verify group member keys (#6669) * core: verify group member keys * refactor, process forwards * refactor parsing * refactor parsing 2 * refactor parser 3 * update rfc * simplify * simplify * log tag * refactor tag logging * refactor withVerifiedSig * simplify * refactor more * comment * fix encoding * fix sending as group for the new binary batch encoding * unify types * update api docs * clean up --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> * core: signing messages with member keys (#6675) * core: signing messages with member keys (types) * sign messages * refactor batching * better * refactor * remove unused Eq --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> * core: forward signed messages as unchanged binary strings (#6678) * core: forward signed messages as unchanged binary strings * refactor * consolidate types * refactor VerifiedMsg * refactor more * undo rename Co-authored-by: Evgeny <evgeny@poberezkin.com> * update schema and plans * add signed status to chat items and events * test signed chat items * unify parser * PostgreSQL fix, remove unused fields, option to send inline files in the tests * change inline files config * revert inline config change * use different characters in batch encoding, to avoid conflict with inline files * fix test, api docs, query plans --------- Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com> --------- Co-authored-by: spaced4ndy <8711996+spaced4ndy@users.noreply.github.com> Co-authored-by: Evgeny @ SimpleX Chat <259188159+evgeny-simplex@users.noreply.github.com>
197 lines
6.3 KiB
Haskell
197 lines
6.3 KiB
Haskell
{-# LANGUAGE DataKinds #-}
|
|
{-# LANGUAGE DuplicateRecordFields #-}
|
|
{-# LANGUAGE LambdaCase #-}
|
|
{-# LANGUAGE NamedFieldPuns #-}
|
|
{-# LANGUAGE OverloadedStrings #-}
|
|
|
|
module Simplex.Chat.Delivery where
|
|
|
|
import Data.ByteString.Char8 (ByteString)
|
|
import Data.Int (Int64)
|
|
import Data.Maybe (fromMaybe)
|
|
import Data.Time.Clock (UTCTime)
|
|
import Simplex.Chat.Messages (GroupChatScopeInfo (..), MessageId, ShowGroupAsSender)
|
|
import Simplex.Chat.Options.DB (FromField (..), ToField (..))
|
|
import Simplex.Chat.Protocol
|
|
import Simplex.Chat.Types
|
|
import Simplex.Chat.Types.Shared
|
|
import Simplex.Messaging.Agent.Store.DB (fromTextField_)
|
|
import Simplex.Messaging.Encoding.String
|
|
|
|
type DeliveryWorkerKey = (GroupId, DeliveryWorkerScope)
|
|
|
|
data DeliveryWorkerScope
|
|
= DWSGroup
|
|
| DWSMemberSupport
|
|
-- | DWSMemberProfileUpdate
|
|
deriving (Eq, Ord, Show)
|
|
|
|
instance FromField DeliveryWorkerScope where fromField = fromTextField_ textDecode
|
|
|
|
instance ToField DeliveryWorkerScope where toField = toField . textEncode
|
|
|
|
instance TextEncoding DeliveryWorkerScope where
|
|
textDecode = \case
|
|
"group" -> Just DWSGroup
|
|
"member_support" -> Just DWSMemberSupport
|
|
-- "member_profile_update" -> Just DWSMemberProfileUpdate
|
|
_ -> Nothing
|
|
textEncode = \case
|
|
DWSGroup -> "group"
|
|
DWSMemberSupport -> "member_support"
|
|
-- DWSMemberProfileUpdate -> "member_profile_update"
|
|
|
|
-- Context for creating a delivery task. Separate from DeliveryJobScope because
|
|
-- sentAsGroup is only needed for task persistence and batching into XGrpMsgForward events.
|
|
-- Once batched into jobs, sentAsGroup=True and sentAsGroup=False messages can be mixed,
|
|
-- so jobs don't need this flag.
|
|
data DeliveryTaskContext = DeliveryTaskContext
|
|
{ jobScope :: DeliveryJobScope,
|
|
sentAsGroup :: ShowGroupAsSender
|
|
}
|
|
deriving (Show)
|
|
|
|
data DeliveryJobScope
|
|
= DJSGroup {jobSpec :: DeliveryJobSpec}
|
|
| DJSMemberSupport {supportGMId :: GroupMemberId}
|
|
-- | DJSMemberProfileUpdate
|
|
deriving (Show)
|
|
|
|
data DeliveryJobSpec
|
|
= DJDeliveryJob {includePending :: Bool}
|
|
| DJRelayRemoved
|
|
deriving (Show)
|
|
|
|
data DeliveryJobSpecTag
|
|
= DJSTDeliveryJob
|
|
| DJSTRelayRemoved
|
|
deriving (Show)
|
|
|
|
instance FromField DeliveryJobSpecTag where fromField = fromTextField_ textDecode
|
|
|
|
instance ToField DeliveryJobSpecTag where toField = toField . textEncode
|
|
|
|
instance TextEncoding DeliveryJobSpecTag where
|
|
textDecode = \case
|
|
"delivery_job" -> Just DJSTDeliveryJob
|
|
"relay_removed" -> Just DJSTRelayRemoved
|
|
_ -> Nothing
|
|
textEncode = \case
|
|
DJSTDeliveryJob -> "delivery_job"
|
|
DJSTRelayRemoved -> "relay_removed"
|
|
|
|
toWorkerScope :: DeliveryJobScope -> DeliveryWorkerScope
|
|
toWorkerScope = \case
|
|
DJSGroup _ -> DWSGroup
|
|
DJSMemberSupport _ -> DWSMemberSupport
|
|
-- DJSMemberProfileUpdate -> DWSMemberProfileUpdate
|
|
|
|
isRelayRemoved :: DeliveryJobScope -> Bool
|
|
isRelayRemoved = \case
|
|
DJSGroup {jobSpec} -> case jobSpec of
|
|
DJRelayRemoved -> True
|
|
_ -> False
|
|
_ -> False
|
|
|
|
jobScopeImpliedSpec :: DeliveryJobScope -> DeliveryJobSpec
|
|
jobScopeImpliedSpec = \case
|
|
DJSGroup {jobSpec} -> jobSpec
|
|
DJSMemberSupport {} -> DJDeliveryJob {includePending = False}
|
|
|
|
jobSpecImpliedPending :: DeliveryJobSpec -> Bool
|
|
jobSpecImpliedPending = \case
|
|
DJDeliveryJob {includePending} -> includePending
|
|
DJRelayRemoved -> True
|
|
|
|
infoToDeliveryContext :: GroupInfo -> Maybe GroupChatScopeInfo -> ShowGroupAsSender -> DeliveryTaskContext
|
|
infoToDeliveryContext GroupInfo {membership} scopeInfo sentAsGroup = DeliveryTaskContext {jobScope, sentAsGroup}
|
|
where
|
|
jobScope = case scopeInfo of
|
|
Nothing -> DJSGroup {jobSpec = DJDeliveryJob {includePending = False}}
|
|
Just GCSIMemberSupport {groupMember_} ->
|
|
let supportGMId = groupMemberId' $ fromMaybe membership groupMember_
|
|
in DJSMemberSupport {supportGMId}
|
|
|
|
memberEventDeliveryScope :: GroupMember -> Maybe DeliveryJobScope
|
|
memberEventDeliveryScope m@GroupMember {memberRole, memberStatus}
|
|
| memberStatus == GSMemPendingApproval = Nothing
|
|
| memberStatus == GSMemPendingReview = Just $ DJSMemberSupport {supportGMId = groupMemberId' m}
|
|
| memberRole >= GRModerator = Just DJSGroup {jobSpec = DJDeliveryJob {includePending = True}}
|
|
| otherwise = Just DJSGroup {jobSpec = DJDeliveryJob {includePending = False}}
|
|
|
|
data NewMessageDeliveryTask = NewMessageDeliveryTask
|
|
{ messageId :: MessageId,
|
|
taskContext :: DeliveryTaskContext
|
|
}
|
|
deriving (Show)
|
|
|
|
data MessageDeliveryTask = MessageDeliveryTask
|
|
{ taskId :: Int64,
|
|
jobScope :: DeliveryJobScope,
|
|
senderGMId :: GroupMemberId,
|
|
fwdSender :: FwdSender,
|
|
brokerTs :: UTCTime,
|
|
verifiedMsg :: VerifiedMsg 'Json
|
|
}
|
|
|
|
deliveryTaskId :: MessageDeliveryTask -> Int64
|
|
deliveryTaskId = taskId
|
|
|
|
data DeliveryTaskStatus
|
|
= DTSNew -- created for delivery task worker to pick up and convert into a delivery job
|
|
| DTSProcessed -- processed by delivery task worker, delivery job created, task can be deleted
|
|
| DTSError -- permanent error
|
|
deriving (Show)
|
|
|
|
instance FromField DeliveryTaskStatus where fromField = fromTextField_ textDecode
|
|
|
|
instance ToField DeliveryTaskStatus where toField = toField . textEncode
|
|
|
|
instance TextEncoding DeliveryTaskStatus where
|
|
textDecode = \case
|
|
"new" -> Just DTSNew
|
|
"processed" -> Just DTSProcessed
|
|
"error" -> Just DTSError
|
|
_ -> Nothing
|
|
textEncode = \case
|
|
DTSNew -> "new"
|
|
DTSProcessed -> "processed"
|
|
DTSError -> "error"
|
|
|
|
data MessageDeliveryJob = MessageDeliveryJob
|
|
{ jobId :: Int64,
|
|
jobScope :: DeliveryJobScope,
|
|
singleSenderGMId_ :: Maybe GroupMemberId, -- Just for single-sender deliveries, Nothing for multi-sender deliveries
|
|
body :: ByteString,
|
|
cursorGMId_ :: Maybe GroupMemberId
|
|
}
|
|
deriving (Show)
|
|
|
|
deliveryJobId :: MessageDeliveryJob -> Int64
|
|
deliveryJobId = jobId
|
|
|
|
data DeliveryJobStatus
|
|
= DJSPending -- created for delivery job worker to pick up
|
|
| DJSComplete -- complete by delivery job worker, job can be deleted
|
|
| DJSError -- permanent error
|
|
deriving (Show)
|
|
|
|
instance FromField DeliveryJobStatus where fromField = fromTextField_ textDecode
|
|
|
|
instance ToField DeliveryJobStatus where toField = toField . textEncode
|
|
|
|
instance TextEncoding DeliveryJobStatus where
|
|
textDecode = \case
|
|
"pending" -> Just DJSPending
|
|
"complete" -> Just DJSComplete
|
|
"error" -> Just DJSError
|
|
_ -> Nothing
|
|
textEncode = \case
|
|
DJSPending -> "pending"
|
|
DJSComplete -> "complete"
|
|
DJSError -> "error"
|
|
|
|
-- data MemberProfileUpdateTask = undefined
|
|
|
|
-- data MemberProfileUpdateJob = undefined
|