core: derive JSON with TH (#3275)

* core: derive JSON with TH

* fix tests

* simplify events

* reduce diff

* fix

* update simplexmq

* update simplexmq
This commit is contained in:
Evgeny Poberezkin 2023-10-26 15:44:50 +01:00 committed by GitHub
parent 3790752378
commit 16bda26022
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 849 additions and 1136 deletions

View file

@ -9,7 +9,7 @@ constraints: zip +disable-bzip2 +disable-zstd
source-repository-package source-repository-package
type: git type: git
location: https://github.com/simplex-chat/simplexmq.git location: https://github.com/simplex-chat/simplexmq.git
tag: d920a2504b6d4653748da7d297cb13cd0a0f1f48 tag: 511d793b927b1e2f12999e0829718671b3a8f0cb
source-repository-package source-repository-package
type: git type: git

View file

@ -1,5 +1,5 @@
{ {
"https://github.com/simplex-chat/simplexmq.git"."d920a2504b6d4653748da7d297cb13cd0a0f1f48" = "0r53wn01z044h6myvd458n3hiqsz64kpv59khgybzwdw5mmqnp34"; "https://github.com/simplex-chat/simplexmq.git"."511d793b927b1e2f12999e0829718671b3a8f0cb" = "14zk7g33x4a1g5d1dihaklvwzll86ks6fk87kf6l6l5back581zi";
"https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38";
"https://github.com/kazu-yamamoto/http2.git"."804fa283f067bd3fd89b8c5f8d25b3047813a517" = "1j67wp7rfybfx3ryx08z6gqmzj85j51hmzhgx47ihgmgr47sl895"; "https://github.com/kazu-yamamoto/http2.git"."804fa283f067bd3fd89b8c5f8d25b3047813a517" = "1j67wp7rfybfx3ryx08z6gqmzj85j51hmzhgx47ihgmgr47sl895";
"https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "0kiwhvml42g9anw4d2v0zd1fpc790pj9syg5x3ik4l97fnkbbwpp";

View file

@ -36,6 +36,7 @@ library
Simplex.Chat.Markdown Simplex.Chat.Markdown
Simplex.Chat.Messages Simplex.Chat.Messages
Simplex.Chat.Messages.CIContent Simplex.Chat.Messages.CIContent
Simplex.Chat.Messages.CIContent.Events
Simplex.Chat.Migrations.M20220101_initial Simplex.Chat.Migrations.M20220101_initial
Simplex.Chat.Migrations.M20220122_v1_1 Simplex.Chat.Migrations.M20220122_v1_1
Simplex.Chat.Migrations.M20220205_chat_item_status Simplex.Chat.Migrations.M20220205_chat_item_status

View file

@ -58,6 +58,7 @@ import Simplex.Chat.Controller
import Simplex.Chat.Markdown import Simplex.Chat.Markdown
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Messages.CIContent.Events
import Simplex.Chat.Options import Simplex.Chat.Options
import Simplex.Chat.ProfileGenerator (generateRandomProfile) import Simplex.Chat.ProfileGenerator (generateRandomProfile)
import Simplex.Chat.Protocol import Simplex.Chat.Protocol

View file

@ -1,18 +1,18 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use newtype instead of data" #-} {-# HLINT ignore "Use newtype instead of data" #-}
module Simplex.Chat.Call where module Simplex.Chat.Call where
import Data.Aeson (FromJSON, ToJSON) import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as J import qualified Data.Aeson.TH as J
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import Data.Int (Int64) import Data.Int (Int64)
@ -20,12 +20,11 @@ import Data.Text (Text)
import Data.Time.Clock (UTCTime) 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 Simplex.Chat.Types (Contact, ContactId, User) import Simplex.Chat.Types (Contact, ContactId, User)
import Simplex.Chat.Types.Util (decodeJSON, encodeJSON) import Simplex.Chat.Types.Util (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 (defaultJSON, dropPrefix, enumJSON, fromTextField_, fstToLower, singleFieldJSON)
data Call = Call data Call = Call
{ contactId :: ContactId, { contactId :: ContactId,
@ -47,14 +46,7 @@ data CallStateTag
| CSTCallOfferSent | CSTCallOfferSent
| CSTCallOfferReceived | CSTCallOfferReceived
| CSTCallNegotiated | CSTCallNegotiated
deriving (Show, Generic) deriving (Show)
instance FromJSON CallStateTag where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CSTCall"
instance ToJSON CallStateTag where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CSTCall"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CSTCall"
callStateTag :: CallState -> CallStateTag callStateTag :: CallState -> CallStateTag
callStateTag = \case callStateTag = \case
@ -93,21 +85,7 @@ data CallState
peerCallSession :: WebRTCSession, peerCallSession :: WebRTCSession,
sharedKey :: Maybe C.Key sharedKey :: Maybe C.Key
} }
deriving (Show, Generic) deriving (Show)
-- database representation
instance FromJSON CallState where
parseJSON = J.genericParseJSON $ singleFieldJSON fstToLower
instance ToJSON CallState where
toJSON = J.genericToJSON $ singleFieldJSON fstToLower
toEncoding = J.genericToEncoding $ singleFieldJSON fstToLower
instance ToField CallState where
toField = toField . encodeJSON
instance FromField CallState where
fromField = fromTextField_ decodeJSON
newtype CallId = CallId ByteString newtype CallId = CallId ByteString
deriving (Eq, Show) deriving (Eq, Show)
@ -135,17 +113,13 @@ data RcvCallInvitation = RcvCallInvitation
sharedKey :: Maybe C.Key, sharedKey :: Maybe C.Key,
callTs :: UTCTime callTs :: UTCTime
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON RcvCallInvitation where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data CallType = CallType data CallType = CallType
{ media :: CallMedia, { media :: CallMedia,
capabilities :: CallCapabilities capabilities :: CallCapabilities
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
defaultCallType :: CallType defaultCallType :: CallType
defaultCallType = CallType CMVideo $ CallCapabilities {encryption = True} defaultCallType = CallType CMVideo $ CallCapabilities {encryption = True}
@ -153,95 +127,54 @@ defaultCallType = CallType CMVideo $ CallCapabilities {encryption = True}
encryptedCall :: CallType -> Bool encryptedCall :: CallType -> Bool
encryptedCall CallType {capabilities = CallCapabilities {encryption}} = encryption encryptedCall CallType {capabilities = CallCapabilities {encryption}} = encryption
instance ToJSON CallType where toEncoding = J.genericToEncoding J.defaultOptions
-- | * Types for chat protocol -- | * Types for chat protocol
data CallInvitation = CallInvitation data CallInvitation = CallInvitation
{ callType :: CallType, { callType :: CallType,
callDhPubKey :: Maybe C.PublicKeyX25519 callDhPubKey :: Maybe C.PublicKeyX25519
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON CallInvitation where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data CallMedia = CMAudio | CMVideo data CallMedia = CMAudio | CMVideo
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON CallMedia where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CM"
instance ToJSON CallMedia where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CM"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CM"
data CallCapabilities = CallCapabilities data CallCapabilities = CallCapabilities
{ encryption :: Bool { encryption :: Bool
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON CallCapabilities where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
data CallOffer = CallOffer data CallOffer = CallOffer
{ callType :: CallType, { callType :: CallType,
rtcSession :: WebRTCSession, rtcSession :: WebRTCSession,
callDhPubKey :: Maybe C.PublicKeyX25519 callDhPubKey :: Maybe C.PublicKeyX25519
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON CallOffer where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data WebRTCCallOffer = WebRTCCallOffer data WebRTCCallOffer = WebRTCCallOffer
{ callType :: CallType, { callType :: CallType,
rtcSession :: WebRTCSession rtcSession :: WebRTCSession
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON WebRTCCallOffer where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data CallAnswer = CallAnswer data CallAnswer = CallAnswer
{ rtcSession :: WebRTCSession { rtcSession :: WebRTCSession
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON CallAnswer where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
data CallExtraInfo = CallExtraInfo data CallExtraInfo = CallExtraInfo
{ rtcExtraInfo :: WebRTCExtraInfo { rtcExtraInfo :: WebRTCExtraInfo
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON CallExtraInfo where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
data WebRTCSession = WebRTCSession data WebRTCSession = WebRTCSession
{ rtcSession :: Text, -- LZW compressed JSON encoding of offer or answer { rtcSession :: Text, -- LZW compressed JSON encoding of offer or answer
rtcIceCandidates :: Text -- LZW compressed JSON encoding of array of ICE candidates rtcIceCandidates :: Text -- LZW compressed JSON encoding of array of ICE candidates
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON WebRTCSession where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
data WebRTCExtraInfo = WebRTCExtraInfo data WebRTCExtraInfo = WebRTCExtraInfo
{ rtcIceCandidates :: Text -- LZW compressed JSON encoding of array of ICE candidates { rtcIceCandidates :: Text -- LZW compressed JSON encoding of array of ICE candidates
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON WebRTCExtraInfo where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
data WebRTCCallStatus = WCSConnecting | WCSConnected | WCSDisconnected | WCSFailed data WebRTCCallStatus = WCSConnecting | WCSConnected | WCSDisconnected | WCSFailed
deriving (Show) deriving (Show)
@ -259,3 +192,37 @@ instance StrEncoding WebRTCCallStatus where
"disconnected" -> pure WCSDisconnected "disconnected" -> pure WCSDisconnected
"failed" -> pure WCSFailed "failed" -> pure WCSFailed
_ -> fail "bad WebRTCCallStatus" _ -> fail "bad WebRTCCallStatus"
$(J.deriveJSON (enumJSON $ dropPrefix "CSTCall") ''CallStateTag)
$(J.deriveJSON (enumJSON $ dropPrefix "CM") ''CallMedia)
$(J.deriveJSON defaultJSON ''CallCapabilities)
$(J.deriveJSON defaultJSON ''CallType)
$(J.deriveJSON defaultJSON ''CallInvitation)
$(J.deriveJSON defaultJSON ''WebRTCSession)
$(J.deriveJSON defaultJSON ''CallOffer)
$(J.deriveJSON defaultJSON ''WebRTCCallOffer)
$(J.deriveJSON defaultJSON ''CallAnswer)
$(J.deriveJSON defaultJSON ''WebRTCExtraInfo)
$(J.deriveJSON defaultJSON ''CallExtraInfo)
-- database representation
$(J.deriveJSON (singleFieldJSON fstToLower) ''CallState)
instance ToField CallState where
toField = toField . encodeJSON
instance FromField CallState where
fromField = fromTextField_ decodeJSON
$(J.deriveJSON defaultJSON ''RcvCallInvitation)

View file

@ -2,7 +2,6 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
@ -41,7 +40,6 @@ import Data.String
import Data.Text (Text) import Data.Text (Text)
import Data.Time (NominalDiffTime, UTCTime) import Data.Time (NominalDiffTime, UTCTime)
import Data.Version (showVersion) import Data.Version (showVersion)
import GHC.Generics (Generic)
import Language.Haskell.TH (Exp, Q, runIO) import Language.Haskell.TH (Exp, Q, runIO)
import Numeric.Natural import Numeric.Natural
import qualified Paths_simplex_chat as SC import qualified Paths_simplex_chat as SC
@ -67,7 +65,7 @@ import Simplex.Messaging.Crypto.File (CryptoFile (..))
import qualified Simplex.Messaging.Crypto.File as CF import qualified Simplex.Messaging.Crypto.File as CF
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus)
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON)
import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, MsgFlags, NtfServer, ProtoServerWithAuth, ProtocolTypeI, QueueId, SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServerWithAuth, userProtocol) import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, MsgFlags, NtfServer, ProtoServerWithAuth, ProtocolTypeI, QueueId, SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServerWithAuth, userProtocol)
import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.TMap (TMap)
import Simplex.Messaging.Transport (simplexMQVersion) import Simplex.Messaging.Transport (simplexMQVersion)
@ -196,14 +194,7 @@ data ChatController = ChatController
} }
data HelpSection = HSMain | HSFiles | HSGroups | HSContacts | HSMyAddress | HSIncognito | HSMarkdown | HSMessages | HSSettings | HSDatabase data HelpSection = HSMain | HSFiles | HSGroups | HSContacts | HSMyAddress | HSIncognito | HSMarkdown | HSMessages | HSSettings | HSDatabase
deriving (Show, Generic) deriving (Show)
instance FromJSON HelpSection where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "HS"
instance ToJSON HelpSection where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "HS"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "HS"
data ChatCommand data ChatCommand
= ShowActiveUser = ShowActiveUser
@ -698,28 +689,14 @@ data ConnectionPlan
= CPInvitationLink {invitationLinkPlan :: InvitationLinkPlan} = CPInvitationLink {invitationLinkPlan :: InvitationLinkPlan}
| CPContactAddress {contactAddressPlan :: ContactAddressPlan} | CPContactAddress {contactAddressPlan :: ContactAddressPlan}
| CPGroupLink {groupLinkPlan :: GroupLinkPlan} | CPGroupLink {groupLinkPlan :: GroupLinkPlan}
deriving (Show, Generic) deriving (Show)
instance FromJSON ConnectionPlan where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "CP"
instance ToJSON ConnectionPlan where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CP"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CP"
data InvitationLinkPlan data InvitationLinkPlan
= ILPOk = ILPOk
| ILPOwnLink | ILPOwnLink
| ILPConnecting {contact_ :: Maybe Contact} | ILPConnecting {contact_ :: Maybe Contact}
| ILPKnown {contact :: Contact} | ILPKnown {contact :: Contact}
deriving (Show, Generic) deriving (Show)
instance FromJSON InvitationLinkPlan where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "ILP"
instance ToJSON InvitationLinkPlan where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "ILP"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "ILP"
data ContactAddressPlan data ContactAddressPlan
= CAPOk = CAPOk
@ -727,14 +704,7 @@ data ContactAddressPlan
| CAPConnectingConfirmReconnect | CAPConnectingConfirmReconnect
| CAPConnectingProhibit {contact :: Contact} | CAPConnectingProhibit {contact :: Contact}
| CAPKnown {contact :: Contact} | CAPKnown {contact :: Contact}
deriving (Show, Generic) deriving (Show)
instance FromJSON ContactAddressPlan where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "CAP"
instance ToJSON ContactAddressPlan where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CAP"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CAP"
data GroupLinkPlan data GroupLinkPlan
= GLPOk = GLPOk
@ -742,14 +712,7 @@ data GroupLinkPlan
| GLPConnectingConfirmReconnect | GLPConnectingConfirmReconnect
| GLPConnectingProhibit {groupInfo_ :: Maybe GroupInfo} | GLPConnectingProhibit {groupInfo_ :: Maybe GroupInfo}
| GLPKnown {groupInfo :: GroupInfo} | GLPKnown {groupInfo :: GroupInfo}
deriving (Show, Generic) deriving (Show)
instance FromJSON GroupLinkPlan where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "GLP"
instance ToJSON GroupLinkPlan where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "GLP"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "GLP"
connectionPlanProceed :: ConnectionPlan -> Bool connectionPlanProceed :: ConnectionPlan -> Bool
connectionPlanProceed = \case connectionPlanProceed = \case
@ -794,7 +757,7 @@ instance ToJSON AgentQueueId where
toEncoding = strToJEncoding toEncoding = strToJEncoding
data ProtoServersConfig p = ProtoServersConfig {servers :: [ServerCfg p]} data ProtoServersConfig p = ProtoServersConfig {servers :: [ServerCfg p]}
deriving (Show, Generic, FromJSON) deriving (Show)
data AProtoServersConfig = forall p. ProtocolTypeI p => APSC (SProtocolType p) (ProtoServersConfig p) data AProtoServersConfig = forall p. ProtocolTypeI p => APSC (SProtocolType p) (ProtoServersConfig p)
@ -805,36 +768,17 @@ data UserProtoServers p = UserProtoServers
protoServers :: NonEmpty (ServerCfg p), protoServers :: NonEmpty (ServerCfg p),
presetServers :: NonEmpty (ProtoServerWithAuth p) presetServers :: NonEmpty (ProtoServerWithAuth p)
} }
deriving (Show, Generic) deriving (Show)
instance ProtocolTypeI p => FromJSON (UserProtoServers p) where
parseJSON = J.genericParseJSON J.defaultOptions
instance ProtocolTypeI p => ToJSON (UserProtoServers p) where
toEncoding = J.genericToEncoding J.defaultOptions
data AUserProtoServers = forall p. (ProtocolTypeI p, UserProtocol p) => AUPS (UserProtoServers p) data AUserProtoServers = forall p. (ProtocolTypeI p, UserProtocol p) => AUPS (UserProtoServers p)
instance FromJSON AUserProtoServers where
parseJSON v = J.withObject "AUserProtoServers" parse v
where
parse o = do
AProtocolType (p :: SProtocolType p) <- o .: "serverProtocol"
case userProtocol p of
Just Dict -> AUPS <$> J.parseJSON @(UserProtoServers p) v
Nothing -> fail $ "AUserProtoServers: unsupported protocol " <> show p
instance ToJSON AUserProtoServers where
toJSON (AUPS s) = J.genericToJSON J.defaultOptions s
toEncoding (AUPS s) = J.genericToEncoding J.defaultOptions s
deriving instance Show AUserProtoServers deriving instance Show AUserProtoServers
data ArchiveConfig = ArchiveConfig {archivePath :: FilePath, disableCompression :: Maybe Bool, parentTempDirectory :: Maybe FilePath} data ArchiveConfig = ArchiveConfig {archivePath :: FilePath, disableCompression :: Maybe Bool, parentTempDirectory :: Maybe FilePath}
deriving (Show, Generic, FromJSON) deriving (Show)
data DBEncryptionConfig = DBEncryptionConfig {currentKey :: DBEncryptionKey, newKey :: DBEncryptionKey} data DBEncryptionConfig = DBEncryptionConfig {currentKey :: DBEncryptionKey, newKey :: DBEncryptionKey}
deriving (Show, Generic, FromJSON) deriving (Show)
newtype DBEncryptionKey = DBEncryptionKey String newtype DBEncryptionKey = DBEncryptionKey String
deriving (Show) deriving (Show)
@ -852,41 +796,25 @@ data ContactSubStatus = ContactSubStatus
{ contact :: Contact, { contact :: Contact,
contactError :: Maybe ChatError contactError :: Maybe ChatError
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON ContactSubStatus where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data MemberSubStatus = MemberSubStatus data MemberSubStatus = MemberSubStatus
{ member :: GroupMember, { member :: GroupMember,
memberError :: Maybe ChatError memberError :: Maybe ChatError
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON MemberSubStatus where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data UserContactSubStatus = UserContactSubStatus data UserContactSubStatus = UserContactSubStatus
{ userContact :: UserContact, { userContact :: UserContact,
userContactError :: Maybe ChatError userContactError :: Maybe ChatError
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON UserContactSubStatus where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data PendingSubStatus = PendingSubStatus data PendingSubStatus = PendingSubStatus
{ connection :: PendingContactConnection, { connection :: PendingContactConnection,
connError :: Maybe ChatError connError :: Maybe ChatError
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON PendingSubStatus where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data UserProfileUpdateSummary = UserProfileUpdateSummary data UserProfileUpdateSummary = UserProfileUpdateSummary
{ notChanged :: Int, { notChanged :: Int,
@ -894,16 +822,14 @@ data UserProfileUpdateSummary = UserProfileUpdateSummary
updateFailures :: Int, updateFailures :: Int,
changedContacts :: [Contact] changedContacts :: [Contact]
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON UserProfileUpdateSummary where toEncoding = J.genericToEncoding J.defaultOptions
data ComposedMessage = ComposedMessage data ComposedMessage = ComposedMessage
{ fileSource :: Maybe CryptoFile, { fileSource :: Maybe CryptoFile,
quotedItemId :: Maybe ChatItemId, quotedItemId :: Maybe ChatItemId,
msgContent :: MsgContent msgContent :: MsgContent
} }
deriving (Show, Generic) deriving (Show)
-- This instance is needed for backward compatibility, can be removed in v6.0 -- This instance is needed for backward compatibility, can be removed in v6.0
instance FromJSON ComposedMessage where instance FromJSON ComposedMessage where
@ -918,24 +844,16 @@ instance FromJSON ComposedMessage where
parseJSON invalid = parseJSON invalid =
JT.prependFailure "bad ComposedMessage, " (JT.typeMismatch "Object" invalid) JT.prependFailure "bad ComposedMessage, " (JT.typeMismatch "Object" invalid)
instance ToJSON ComposedMessage where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data XFTPFileConfig = XFTPFileConfig data XFTPFileConfig = XFTPFileConfig
{ minFileSize :: Integer { minFileSize :: Integer
} }
deriving (Show, Generic, FromJSON) deriving (Show)
defaultXFTPFileConfig :: XFTPFileConfig defaultXFTPFileConfig :: XFTPFileConfig
defaultXFTPFileConfig = XFTPFileConfig {minFileSize = 0} defaultXFTPFileConfig = XFTPFileConfig {minFileSize = 0}
instance ToJSON XFTPFileConfig where toEncoding = J.genericToEncoding J.defaultOptions
data NtfMsgInfo = NtfMsgInfo {msgTs :: UTCTime, msgFlags :: MsgFlags} data NtfMsgInfo = NtfMsgInfo {msgTs :: UTCTime, msgFlags :: MsgFlags}
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON NtfMsgInfo where toEncoding = J.genericToEncoding J.defaultOptions
crNtfToken :: (DeviceToken, NtfTknStatus, NotificationsMode) -> ChatResponse crNtfToken :: (DeviceToken, NtfTknStatus, NotificationsMode) -> ChatResponse
crNtfToken (token, status, ntfMode) = CRNtfToken {token, status, ntfMode} crNtfToken (token, status, ntfMode) = CRNtfToken {token, status, ntfMode}
@ -945,25 +863,19 @@ data SwitchProgress = SwitchProgress
switchPhase :: SwitchPhase, switchPhase :: SwitchPhase,
connectionStats :: ConnectionStats connectionStats :: ConnectionStats
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON SwitchProgress where toEncoding = J.genericToEncoding J.defaultOptions
data RatchetSyncProgress = RatchetSyncProgress data RatchetSyncProgress = RatchetSyncProgress
{ ratchetSyncStatus :: RatchetSyncState, { ratchetSyncStatus :: RatchetSyncState,
connectionStats :: ConnectionStats connectionStats :: ConnectionStats
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON RatchetSyncProgress where toEncoding = J.genericToEncoding J.defaultOptions
data ParsedServerAddress = ParsedServerAddress data ParsedServerAddress = ParsedServerAddress
{ serverAddress :: Maybe ServerAddress, { serverAddress :: Maybe ServerAddress,
parseError :: String parseError :: String
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON ParsedServerAddress where toEncoding = J.genericToEncoding J.defaultOptions
data ServerAddress = ServerAddress data ServerAddress = ServerAddress
{ serverProtocol :: AProtocolType, { serverProtocol :: AProtocolType,
@ -972,9 +884,7 @@ data ServerAddress = ServerAddress
keyHash :: String, keyHash :: String,
basicAuth :: String basicAuth :: String
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON ServerAddress where toEncoding = J.genericToEncoding J.defaultOptions
data TimedMessagesEnabled data TimedMessagesEnabled
= TMEEnableSetTTL Int = TMEEnableSetTTL Int
@ -996,22 +906,18 @@ data CoreVersionInfo = CoreVersionInfo
simplexmqVersion :: String, simplexmqVersion :: String,
simplexmqCommit :: String simplexmqCommit :: String
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON CoreVersionInfo where toEncoding = J.genericToEncoding J.defaultOptions
data SendFileMode data SendFileMode
= SendFileSMP (Maybe InlineFileMode) = SendFileSMP (Maybe InlineFileMode)
| SendFileXFTP | SendFileXFTP
deriving (Show, Generic) deriving (Show)
data SlowSQLQuery = SlowSQLQuery data SlowSQLQuery = SlowSQLQuery
{ query :: Text, { query :: Text,
queryStats :: SlowQueryStats queryStats :: SlowQueryStats
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON SlowSQLQuery where toEncoding = J.genericToEncoding J.defaultOptions
data ChatError data ChatError
= ChatError {errorType :: ChatErrorType} = ChatError {errorType :: ChatErrorType}
@ -1020,14 +926,7 @@ data ChatError
| ChatErrorDatabase {databaseError :: DatabaseError} | ChatErrorDatabase {databaseError :: DatabaseError}
| ChatErrorRemoteCtrl {remoteCtrlError :: RemoteCtrlError} | ChatErrorRemoteCtrl {remoteCtrlError :: RemoteCtrlError}
| ChatErrorRemoteHost {remoteHostId :: RemoteHostId, remoteHostError :: RemoteHostError} | ChatErrorRemoteHost {remoteHostId :: RemoteHostId, remoteHostError :: RemoteHostError}
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON ChatError where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "Chat"
instance ToJSON ChatError where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "Chat"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "Chat"
data ChatErrorType data ChatErrorType
= CENoActiveUser = CENoActiveUser
@ -1107,14 +1006,7 @@ data ChatErrorType
| CEPeerChatVRangeIncompatible | CEPeerChatVRangeIncompatible
| CEInternalError {message :: String} | CEInternalError {message :: String}
| CEException {message :: String} | CEException {message :: String}
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON ChatErrorType where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "CE"
instance ToJSON ChatErrorType where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CE"
data DatabaseError data DatabaseError
= DBErrorEncrypted = DBErrorEncrypted
@ -1122,24 +1014,10 @@ data DatabaseError
| DBErrorNoFile {dbFile :: String} | DBErrorNoFile {dbFile :: String}
| DBErrorExport {sqliteError :: SQLiteError} | DBErrorExport {sqliteError :: SQLiteError}
| DBErrorOpen {sqliteError :: SQLiteError} | DBErrorOpen {sqliteError :: SQLiteError}
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON DatabaseError where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "DB"
instance ToJSON DatabaseError where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "DB"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "DB"
data SQLiteError = SQLiteErrorNotADatabase | SQLiteError String data SQLiteError = SQLiteErrorNotADatabase | SQLiteError String
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON SQLiteError where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "SQLite"
instance ToJSON SQLiteError where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "SQLite"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "SQLite"
throwDBError :: ChatMonad m => DatabaseError -> m () throwDBError :: ChatMonad m => DatabaseError -> m ()
throwDBError = throwError . ChatErrorDatabase throwDBError = throwError . ChatErrorDatabase
@ -1153,14 +1031,7 @@ data RemoteHostError
| RHDisconnected {reason :: Text} -- ^ A session disconnected by a host | RHDisconnected {reason :: Text} -- ^ A session disconnected by a host
| RHConnectionLost {reason :: Text} -- ^ A session disconnected due to transport issues | RHConnectionLost {reason :: Text} -- ^ A session disconnected due to transport issues
| RHProtocolError RemoteProtocolError | RHProtocolError RemoteProtocolError
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON RemoteHostError where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RH"
instance ToJSON RemoteHostError where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "RH"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RH"
-- TODO review errors, some of it can be covered by HTTP2 errors -- TODO review errors, some of it can be covered by HTTP2 errors
data RemoteCtrlError data RemoteCtrlError
@ -1176,26 +1047,12 @@ data RemoteCtrlError
| RCEHTTP2RespStatus {statusCode :: Maybe Int} -- TODO remove | RCEHTTP2RespStatus {statusCode :: Maybe Int} -- TODO remove
| RCEInvalidResponse {responseError :: String} | RCEInvalidResponse {responseError :: String}
| RCEProtocolError {protocolError :: RemoteProtocolError} | RCEProtocolError {protocolError :: RemoteProtocolError}
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON RemoteCtrlError where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RCE"
instance ToJSON RemoteCtrlError where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "RCE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RCE"
data ArchiveError data ArchiveError
= AEImport {chatError :: ChatError} = AEImport {chatError :: ChatError}
| AEImportFile {file :: String, chatError :: ChatError} | AEImportFile {file :: String, chatError :: ChatError}
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON ArchiveError where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "AE"
instance ToJSON ArchiveError where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "AE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "AE"
data RemoteCtrlSession = RemoteCtrlSession data RemoteCtrlSession = RemoteCtrlSession
{ -- | Host (mobile) side of transport to process remote commands and forward notifications { -- | Host (mobile) side of transport to process remote commands and forward notifications
@ -1295,4 +1152,83 @@ withStoreCtx ctx_ action = do
handleInternal :: String -> SomeException -> IO (Either StoreError a) handleInternal :: String -> SomeException -> IO (Either StoreError a)
handleInternal ctxStr e = pure . Left . SEInternalError $ show e <> ctxStr handleInternal ctxStr e = pure . Left . SEInternalError $ show e <> ctxStr
$(JQ.deriveJSON (enumJSON $ dropPrefix "HS") ''HelpSection)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "ILP") ''InvitationLinkPlan)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CAP") ''ContactAddressPlan)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "GLP") ''GroupLinkPlan)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CP") ''ConnectionPlan)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CE") ''ChatErrorType)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RH") ''RemoteHostError)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RCE") ''RemoteCtrlError)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "SQLite") ''SQLiteError)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "DB") ''DatabaseError)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "Chat") ''ChatError)
$(JQ.deriveJSON defaultJSON ''ContactSubStatus)
$(JQ.deriveJSON defaultJSON ''MemberSubStatus)
$(JQ.deriveJSON defaultJSON ''UserContactSubStatus)
$(JQ.deriveJSON defaultJSON ''PendingSubStatus)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "AE") ''ArchiveError)
$(JQ.deriveJSON defaultJSON ''UserProfileUpdateSummary)
$(JQ.deriveJSON defaultJSON ''NtfMsgInfo)
$(JQ.deriveJSON defaultJSON ''SwitchProgress)
$(JQ.deriveJSON defaultJSON ''RatchetSyncProgress)
$(JQ.deriveJSON defaultJSON ''ServerAddress)
$(JQ.deriveJSON defaultJSON ''ParsedServerAddress)
$(JQ.deriveJSON defaultJSON ''CoreVersionInfo)
$(JQ.deriveJSON defaultJSON ''SlowSQLQuery)
instance ProtocolTypeI p => FromJSON (ProtoServersConfig p) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''ProtoServersConfig)
instance ProtocolTypeI p => FromJSON (UserProtoServers p) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''UserProtoServers)
instance ProtocolTypeI p => ToJSON (UserProtoServers p) where
toJSON = $(JQ.mkToJSON defaultJSON ''UserProtoServers)
toEncoding = $(JQ.mkToEncoding defaultJSON ''UserProtoServers)
instance FromJSON AUserProtoServers where
parseJSON v = J.withObject "AUserProtoServers" parse v
where
parse o = do
AProtocolType (p :: SProtocolType p) <- o .: "serverProtocol"
case userProtocol p of
Just Dict -> AUPS <$> J.parseJSON @(UserProtoServers p) v
Nothing -> fail $ "AUserProtoServers: unsupported protocol " <> show p
instance ToJSON AUserProtoServers where
toJSON (AUPS s) = $(JQ.mkToJSON defaultJSON ''UserProtoServers) s
toEncoding (AUPS s) = $(JQ.mkToEncoding defaultJSON ''UserProtoServers) s
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CR") ''ChatResponse) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "CR") ''ChatResponse)
$(JQ.deriveFromJSON defaultJSON ''ArchiveConfig)
$(JQ.deriveFromJSON defaultJSON ''DBEncryptionConfig)
$(JQ.deriveJSON defaultJSON ''XFTPFileConfig)
$(JQ.deriveToJSON defaultJSON ''ComposedMessage)

View file

@ -1,9 +1,9 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -Wno-unrecognised-pragmas #-} {-# OPTIONS_GHC -Wno-unrecognised-pragmas #-}
{-# HLINT ignore "Use newtype instead of data" #-} {-# HLINT ignore "Use newtype instead of data" #-}
@ -13,6 +13,7 @@ module Simplex.Chat.Markdown where
import Control.Applicative (optional, (<|>)) import Control.Applicative (optional, (<|>))
import Data.Aeson (FromJSON, ToJSON) import Data.Aeson (FromJSON, ToJSON)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import Data.Attoparsec.Text (Parser) import Data.Attoparsec.Text (Parser)
import qualified Data.Attoparsec.Text as A import qualified Data.Attoparsec.Text as A
import Data.Char (isDigit) import Data.Char (isDigit)
@ -27,12 +28,11 @@ import Data.String
import Data.Text (Text) import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8)
import GHC.Generics
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Util import Simplex.Chat.Types.Util
import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..))
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, sumTypeJSON)
import Simplex.Messaging.Protocol (ProtocolServer (..)) import Simplex.Messaging.Protocol (ProtocolServer (..))
import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Util (safeDecodeUtf8)
import System.Console.ANSI.Types import System.Console.ANSI.Types
@ -52,17 +52,10 @@ data Format
| SimplexLink {linkType :: SimplexLinkType, simplexUri :: Text, smpHosts :: NonEmpty Text} | SimplexLink {linkType :: SimplexLinkType, simplexUri :: Text, smpHosts :: NonEmpty Text}
| Email | Email
| Phone | Phone
deriving (Eq, Show, Generic) deriving (Eq, Show)
data SimplexLinkType = XLContact | XLInvitation | XLGroup data SimplexLinkType = XLContact | XLInvitation | XLGroup
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON SimplexLinkType where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "XL"
instance ToJSON SimplexLinkType where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "XL"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "XL"
colored :: Color -> Format colored :: Color -> Format
colored = Colored . FormatColor colored = Colored . FormatColor
@ -70,13 +63,6 @@ colored = Colored . FormatColor
markdown :: Format -> Text -> Markdown markdown :: Format -> Text -> Markdown
markdown = Markdown . Just markdown = Markdown . Just
instance FromJSON Format where
parseJSON = J.genericParseJSON $ sumTypeJSON fstToLower
instance ToJSON Format where
toJSON = J.genericToJSON $ sumTypeJSON fstToLower
toEncoding = J.genericToEncoding $ sumTypeJSON fstToLower
instance Semigroup Markdown where instance Semigroup Markdown where
m <> (Markdown _ "") = m m <> (Markdown _ "") = m
(Markdown _ "") <> m = m (Markdown _ "") <> m = m
@ -122,10 +108,7 @@ instance ToJSON FormatColor where
White -> "white" White -> "white"
data FormattedText = FormattedText {format :: Maybe Format, text :: Text} data FormattedText = FormattedText {format :: Maybe Format, text :: Text}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON FormattedText where
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
instance IsString FormattedText where instance IsString FormattedText where
fromString = FormattedText Nothing . T.pack fromString = FormattedText Nothing . T.pack
@ -133,11 +116,6 @@ instance IsString FormattedText where
type MarkdownList = [FormattedText] type MarkdownList = [FormattedText]
data ParsedMarkdown = ParsedMarkdown {formattedText :: Maybe MarkdownList} data ParsedMarkdown = ParsedMarkdown {formattedText :: Maybe MarkdownList}
deriving (Generic)
instance ToJSON ParsedMarkdown where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
unmarked :: Text -> Markdown unmarked :: Text -> Markdown
unmarked = Markdown Nothing unmarked = Markdown Nothing
@ -257,3 +235,11 @@ markdownP = mconcat <$> A.many' fragmentP
linkType' ConnReqUriData {crClientData} = case crClientData >>= decodeJSON of linkType' ConnReqUriData {crClientData} = case crClientData >>= decodeJSON of
Just (CRDataGroup _) -> XLGroup Just (CRDataGroup _) -> XLGroup
Nothing -> XLContact Nothing -> XLContact
$(JQ.deriveJSON (enumJSON $ dropPrefix "XL") ''SimplexLinkType)
$(JQ.deriveJSON (sumTypeJSON fstToLower) ''Format)
$(JQ.deriveJSON defaultJSON ''FormattedText)
$(JQ.deriveToJSON defaultJSON ''ParsedMarkdown)

View file

@ -1,6 +1,5 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-} {-# LANGUAGE KindSignatures #-}
@ -10,6 +9,7 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
@ -20,6 +20,7 @@ import Control.Applicative ((<|>))
import Data.Aeson (FromJSON, ToJSON, (.:)) import Data.Aeson (FromJSON, ToJSON, (.:))
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.Encoding as JE
import qualified Data.Aeson.TH as JQ
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Base64 as B64
import qualified Data.ByteString.Lazy.Char8 as LB import qualified Data.ByteString.Lazy.Char8 as LB
@ -33,7 +34,6 @@ import Data.Type.Equality
import Data.Typeable (Typeable) import Data.Typeable (Typeable)
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 Simplex.Chat.Markdown import Simplex.Chat.Markdown
import Simplex.Chat.Messages.CIContent import Simplex.Chat.Messages.CIContent
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
@ -43,17 +43,15 @@ import Simplex.Messaging.Agent.Protocol (AgentMsgId, MsgMeta (..), MsgReceiptSta
import Simplex.Messaging.Crypto.File (CryptoFile (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..))
import qualified Simplex.Messaging.Crypto.File as CF import qualified Simplex.Messaging.Crypto.File as CF
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, parseAll, enumJSON, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, parseAll, enumJSON, sumTypeJSON)
import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Protocol (MsgBody)
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
data ChatType = CTDirect | CTGroup | CTContactRequest | CTContactConnection data ChatType = CTDirect | CTGroup | CTContactRequest | CTContactConnection
deriving (Eq, Show, Ord, Generic) deriving (Eq, Show, Ord)
data ChatName = ChatName {chatType :: ChatType, chatName :: Text} data ChatName = ChatName {chatType :: ChatType, chatName :: Text}
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON ChatName where toEncoding = J.genericToEncoding J.defaultOptions
chatTypeStr :: ChatType -> String chatTypeStr :: ChatType -> String
chatTypeStr = \case chatTypeStr = \case
@ -68,13 +66,6 @@ chatNameStr (ChatName cType name) = chatTypeStr cType <> T.unpack name
data ChatRef = ChatRef ChatType Int64 data ChatRef = ChatRef ChatType Int64
deriving (Eq, Show, Ord) deriving (Eq, Show, Ord)
instance FromJSON ChatType where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "CT"
instance ToJSON ChatType where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CT"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CT"
data ChatInfo (c :: ChatType) where data ChatInfo (c :: ChatType) where
DirectChat :: Contact -> ChatInfo 'CTDirect DirectChat :: Contact -> ChatInfo 'CTDirect
GroupChat :: GroupInfo -> ChatInfo 'CTGroup GroupChat :: GroupInfo -> ChatInfo 'CTGroup
@ -113,14 +104,8 @@ data JSONChatInfo
| JCInfoGroup {groupInfo :: GroupInfo} | JCInfoGroup {groupInfo :: GroupInfo}
| JCInfoContactRequest {contactRequest :: UserContactRequest} | JCInfoContactRequest {contactRequest :: UserContactRequest}
| JCInfoContactConnection {contactConnection :: PendingContactConnection} | JCInfoContactConnection {contactConnection :: PendingContactConnection}
deriving (Generic)
instance FromJSON JSONChatInfo where $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "JCInfo") ''JSONChatInfo)
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "JCInfo"
instance ToJSON JSONChatInfo where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCInfo"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCInfo"
instance ChatTypeI c => FromJSON (ChatInfo c) where instance ChatTypeI c => FromJSON (ChatInfo c) where
parseJSON v = (\(AChatInfo _ c) -> checkChatType c) <$?> J.parseJSON v parseJSON v = (\(AChatInfo _ c) -> checkChatType c) <$?> J.parseJSON v
@ -163,14 +148,7 @@ data ChatItem (c :: ChatType) (d :: MsgDirection) = ChatItem
reactions :: [CIReactionCount], reactions :: [CIReactionCount],
file :: Maybe (CIFile d) file :: Maybe (CIFile d)
} }
deriving (Show, Generic) deriving (Show)
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (ChatItem c d) where
parseJSON = J.genericParseJSON J.defaultOptions
instance (ChatTypeI c, MsgDirectionI d) => ToJSON (ChatItem c d) where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
isMention :: ChatItem c d -> Bool isMention :: ChatItem c d -> Bool
isMention ChatItem {chatDir, quotedItem} = case chatDir of isMention ChatItem {chatDir, quotedItem} = case chatDir of
@ -195,34 +173,14 @@ deriving instance Show (CIDirection c d)
data CCIDirection c = forall d. MsgDirectionI d => CCID (SMsgDirection d) (CIDirection c d) data CCIDirection c = forall d. MsgDirectionI d => CCID (SMsgDirection d) (CIDirection c d)
instance ChatTypeI c => FromJSON (CCIDirection c) where
parseJSON v = (\(ACID _ d x) -> checkChatType (CCID d x)) <$?> J.parseJSON v
data ACIDirection = forall c d. (ChatTypeI c, MsgDirectionI d) => ACID (SChatType c) (SMsgDirection d) (CIDirection c d) data ACIDirection = forall c d. (ChatTypeI c, MsgDirectionI d) => ACID (SChatType c) (SMsgDirection d) (CIDirection c d)
instance FromJSON ACIDirection where
parseJSON v = jsonACIDirection <$> J.parseJSON v
data JSONCIDirection data JSONCIDirection
= JCIDirectSnd = JCIDirectSnd
| JCIDirectRcv | JCIDirectRcv
| JCIGroupSnd | JCIGroupSnd
| JCIGroupRcv {groupMember :: GroupMember} | JCIGroupRcv {groupMember :: GroupMember}
deriving (Generic, Show) deriving (Show)
instance FromJSON JSONCIDirection where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "JCI"
instance ToJSON JSONCIDirection where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCI"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCI"
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (CIDirection c d) where
parseJSON v = (\(CCID _ x') -> checkDirection x') <$?> J.parseJSON v
instance ToJSON (CIDirection c d) where
toJSON = J.toJSON . jsonCIDirection
toEncoding = J.toEncoding . jsonCIDirection
jsonCIDirection :: CIDirection c d -> JSONCIDirection jsonCIDirection :: CIDirection c d -> JSONCIDirection
jsonCIDirection = \case jsonCIDirection = \case
@ -239,26 +197,12 @@ jsonACIDirection = \case
JCIGroupRcv m -> ACID SCTGroup SMDRcv $ CIGroupRcv m JCIGroupRcv m -> ACID SCTGroup SMDRcv $ CIGroupRcv m
data CIReactionCount = CIReactionCount {reaction :: MsgReaction, userReacted :: Bool, totalReacted :: Int} data CIReactionCount = CIReactionCount {reaction :: MsgReaction, userReacted :: Bool, totalReacted :: Int}
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON CIReactionCount where toEncoding = J.genericToEncoding J.defaultOptions
data CChatItem c = forall d. MsgDirectionI d => CChatItem (SMsgDirection d) (ChatItem c d) data CChatItem c = forall d. MsgDirectionI d => CChatItem (SMsgDirection d) (ChatItem c d)
deriving instance Show (CChatItem c) deriving instance Show (CChatItem c)
instance forall c. ChatTypeI c => FromJSON (CChatItem c) where
parseJSON v = J.withObject "CChatItem" parse v
where
parse o = do
CCID d (_ :: CIDirection c d) <- o .: "chatDir"
ci <- J.parseJSON @(ChatItem c d) v
pure $ CChatItem d ci
instance ChatTypeI c => ToJSON (CChatItem c) where
toJSON (CChatItem _ ci) = J.toJSON ci
toEncoding (CChatItem _ ci) = J.toEncoding ci
cchatItemId :: CChatItem c -> ChatItemId cchatItemId :: CChatItem c -> ChatItemId
cchatItemId (CChatItem _ ci) = chatItemId' ci cchatItemId (CChatItem _ ci) = chatItemId' ci
@ -325,51 +269,25 @@ data Chat c = Chat
chatItems :: [CChatItem c], chatItems :: [CChatItem c],
chatStats :: ChatStats chatStats :: ChatStats
} }
deriving (Show, Generic) deriving (Show)
instance ChatTypeI c => ToJSON (Chat c) where toEncoding = J.genericToEncoding J.defaultOptions
data AChat = forall c. ChatTypeI c => AChat (SChatType c) (Chat c) data AChat = forall c. ChatTypeI c => AChat (SChatType c) (Chat c)
deriving instance Show AChat deriving instance Show AChat
instance FromJSON AChat where
parseJSON = J.withObject "AChat" $ \o -> do
AChatInfo c chatInfo <- o .: "chatInfo"
chatItems <- o .: "chatItems"
chatStats <- o .: "chatStats"
pure $ AChat c Chat {chatInfo, chatItems, chatStats}
instance ToJSON AChat where
toJSON (AChat _ c) = J.toJSON c
toEncoding (AChat _ c) = J.toEncoding c
data ChatStats = ChatStats data ChatStats = ChatStats
{ unreadCount :: Int, { unreadCount :: Int,
minUnreadItemId :: ChatItemId, minUnreadItemId :: ChatItemId,
unreadChat :: Bool unreadChat :: Bool
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON ChatStats where toEncoding = J.genericToEncoding J.defaultOptions
-- | type to show a mix of messages from multiple chats -- | type to show a mix of messages from multiple chats
data AChatItem = forall c d. (ChatTypeI c, MsgDirectionI d) => AChatItem (SChatType c) (SMsgDirection d) (ChatInfo c) (ChatItem c d) data AChatItem = forall c d. (ChatTypeI c, MsgDirectionI d) => AChatItem (SChatType c) (SMsgDirection d) (ChatInfo c) (ChatItem c d)
deriving instance Show AChatItem deriving instance Show AChatItem
instance FromJSON AChatItem where
parseJSON = J.withObject "AChatItem" $ \o -> do
AChatInfo c chatInfo <- o .: "chatInfo"
CChatItem d chatItem <- o .: "chatItem"
pure $ AChatItem c d chatInfo chatItem
instance ToJSON AChatItem where
toJSON (AChatItem _ _ chat item) = J.toJSON $ JSONAnyChatItem chat item
toEncoding (AChatItem _ _ chat item) = J.toEncoding $ JSONAnyChatItem chat item
data JSONAnyChatItem c d = JSONAnyChatItem {chatInfo :: ChatInfo c, chatItem :: ChatItem c d} data JSONAnyChatItem c d = JSONAnyChatItem {chatInfo :: ChatInfo c, chatItem :: ChatItem c d}
deriving (Generic)
aChatItems :: AChat -> [AChatItem] aChatItems :: AChat -> [AChatItem]
aChatItems (AChat ct Chat {chatInfo, chatItems}) = map aChatItem chatItems aChatItems (AChat ct Chat {chatInfo, chatItems}) = map aChatItem chatItems
@ -387,10 +305,6 @@ updateFileStatus ci@ChatItem {file} status = case file of
Just f -> ci {file = Just (f :: CIFile d) {fileStatus = status}} Just f -> ci {file = Just (f :: CIFile d) {fileStatus = status}}
Nothing -> ci Nothing -> ci
instance (ChatTypeI c, MsgDirectionI d) => ToJSON (JSONAnyChatItem c d) where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
-- This type is not saved to DB, so all JSON encodings are platform-specific -- This type is not saved to DB, so all JSON encodings are platform-specific
data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta
{ itemId :: ChatItemId, { itemId :: ChatItemId,
@ -406,7 +320,7 @@ data CIMeta (c :: ChatType) (d :: MsgDirection) = CIMeta
createdAt :: UTCTime, createdAt :: UTCTime,
updatedAt :: UTCTime updatedAt :: UTCTime
} }
deriving (Show, Generic, FromJSON) deriving (Show)
mkCIMeta :: ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe SharedMsgId -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> UTCTime -> ChatItemTs -> UTCTime -> UTCTime -> CIMeta c d mkCIMeta :: ChatItemId -> CIContent d -> Text -> CIStatus d -> Maybe SharedMsgId -> Maybe (CIDeleted c) -> Bool -> Maybe CITimed -> Maybe Bool -> UTCTime -> ChatItemTs -> UTCTime -> UTCTime -> CIMeta c d
mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted itemEdited itemTimed itemLive currentTs itemTs createdAt updatedAt = mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted itemEdited itemTimed itemLive currentTs itemTs createdAt updatedAt =
@ -415,15 +329,11 @@ mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted item
_ -> False _ -> False
in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, itemTimed, itemLive, editable, createdAt, updatedAt} in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, itemTimed, itemLive, editable, createdAt, updatedAt}
instance ChatTypeI c => ToJSON (CIMeta c d) where toEncoding = J.genericToEncoding J.defaultOptions
data CITimed = CITimed data CITimed = CITimed
{ ttl :: Int, -- seconds { ttl :: Int, -- seconds
deleteAt :: Maybe UTCTime -- this is initially Nothing for received items, the timer starts when they are read deleteAt :: Maybe UTCTime -- this is initially Nothing for received items, the timer starts when they are read
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON CITimed where toEncoding = J.genericToEncoding J.defaultOptions
ttl' :: CITimed -> Int ttl' :: CITimed -> Int
ttl' CITimed {ttl} = ttl ttl' CITimed {ttl} = ttl
@ -457,14 +367,7 @@ data CIQuote (c :: ChatType) = CIQuote
content :: MsgContent, content :: MsgContent,
formattedText :: Maybe MarkdownList formattedText :: Maybe MarkdownList
} }
deriving (Show, Generic) deriving (Show)
instance ChatTypeI c => FromJSON (CIQuote c) where
parseJSON = J.genericParseJSON J.defaultOptions
instance ToJSON (CIQuote c) where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data CIReaction (c :: ChatType) (d :: MsgDirection) = CIReaction data CIReaction (c :: ChatType) (d :: MsgDirection) = CIReaction
{ chatDir :: CIDirection c d, { chatDir :: CIDirection c d,
@ -472,41 +375,15 @@ data CIReaction (c :: ChatType) (d :: MsgDirection) = CIReaction
sentAt :: UTCTime, sentAt :: UTCTime,
reaction :: MsgReaction reaction :: MsgReaction
} }
deriving (Show, Generic) deriving (Show)
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (CIReaction c d) where
parseJSON = J.genericParseJSON J.defaultOptions
instance ChatTypeI c => ToJSON (CIReaction c d) where
toEncoding = J.genericToEncoding J.defaultOptions
data AnyCIReaction = forall c d. ChatTypeI c => ACIR (SChatType c) (SMsgDirection d) (CIReaction c d) data AnyCIReaction = forall c d. ChatTypeI c => ACIR (SChatType c) (SMsgDirection d) (CIReaction c d)
instance FromJSON AnyCIReaction where
parseJSON v = J.withObject "AnyCIReaction" parse v
where
parse o = do
ACID c d (_ :: CIDirection c d) <- o .: "chatDir"
ACIR c d <$> J.parseJSON @(CIReaction c d) v
data ACIReaction = forall c d. ChatTypeI c => ACIReaction (SChatType c) (SMsgDirection d) (ChatInfo c) (CIReaction c d) data ACIReaction = forall c d. ChatTypeI c => ACIReaction (SChatType c) (SMsgDirection d) (ChatInfo c) (CIReaction c d)
deriving instance Show ACIReaction deriving instance Show ACIReaction
instance FromJSON ACIReaction where
parseJSON = J.withObject "ACIReaction" $ \o -> do
ACIR c d reaction <- o .: "chatReaction"
cInfo <- o .: "chatInfo"
pure $ ACIReaction c d cInfo reaction
instance ToJSON ACIReaction where
toJSON (ACIReaction _ _ cInfo reaction) = J.toJSON $ JSONCIReaction cInfo reaction
toEncoding (ACIReaction _ _ cInfo reaction) = J.toEncoding $ JSONCIReaction cInfo reaction
data JSONCIReaction c d = JSONCIReaction {chatInfo :: ChatInfo c, chatReaction :: CIReaction c d} data JSONCIReaction c d = JSONCIReaction {chatInfo :: ChatInfo c, chatReaction :: CIReaction c d}
deriving (Generic)
instance ChatTypeI c => ToJSON (JSONCIReaction c d) where toEncoding = J.genericToEncoding J.defaultOptions
data CIQDirection (c :: ChatType) where data CIQDirection (c :: ChatType) where
CIQDirectSnd :: CIQDirection 'CTDirect CIQDirectSnd :: CIQDirection 'CTDirect
@ -518,13 +395,6 @@ deriving instance Show (CIQDirection c)
data ACIQDirection = forall c. ChatTypeI c => ACIQDirection (SChatType c) (CIQDirection c) data ACIQDirection = forall c. ChatTypeI c => ACIQDirection (SChatType c) (CIQDirection c)
instance ChatTypeI c => FromJSON (CIQDirection c) where
parseJSON v = (\(ACIQDirection _ x) -> checkChatType x) . jsonACIQDirection <$?> J.parseJSON v
instance ToJSON (CIQDirection c) where
toJSON = J.toJSON . jsonCIQDirection
toEncoding = J.toEncoding . jsonCIQDirection
jsonCIQDirection :: CIQDirection c -> Maybe JSONCIDirection jsonCIQDirection :: CIQDirection c -> Maybe JSONCIDirection
jsonCIQDirection = \case jsonCIQDirection = \case
CIQDirectSnd -> Just JCIDirectSnd CIQDirectSnd -> Just JCIDirectSnd
@ -556,14 +426,7 @@ data CIFile (d :: MsgDirection) = CIFile
fileStatus :: CIFileStatus d, fileStatus :: CIFileStatus d,
fileProtocol :: FileProtocol fileProtocol :: FileProtocol
} }
deriving (Show, Generic) deriving (Show)
instance MsgDirectionI d => FromJSON (CIFile d) where
parseJSON = J.genericParseJSON J.defaultOptions
instance MsgDirectionI d => ToJSON (CIFile d) where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data FileProtocol = FPSMP | FPXFTP data FileProtocol = FPSMP | FPXFTP
deriving (Eq, Show, Ord) deriving (Eq, Show, Ord)
@ -621,17 +484,6 @@ ciFileEnded = \case
CIFSRcvError -> True CIFSRcvError -> True
CIFSInvalid {} -> True CIFSInvalid {} -> True
instance MsgDirectionI d => FromJSON (CIFileStatus d) where
parseJSON v = (\(AFS _ s) -> checkDirection s) . aciFileStatusJSON <$?> J.parseJSON v
instance ToJSON (CIFileStatus d) where
toJSON = J.toJSON . jsonCIFileStatus
toEncoding = J.toEncoding . jsonCIFileStatus
instance MsgDirectionI d => ToField (CIFileStatus d) where toField = toField . decodeLatin1 . strEncode
instance FromField ACIFileStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d) data ACIFileStatus = forall d. MsgDirectionI d => AFS (SMsgDirection d) (CIFileStatus d)
deriving instance Show ACIFileStatus deriving instance Show ACIFileStatus
@ -689,14 +541,6 @@ data JSONCIFileStatus
| JCIFSRcvCancelled | JCIFSRcvCancelled
| JCIFSRcvError | JCIFSRcvError
| JCIFSInvalid {text :: Text} | JCIFSInvalid {text :: Text}
deriving (Generic)
instance FromJSON JSONCIFileStatus where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "JCIFS"
instance ToJSON JSONCIFileStatus where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCIFS"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCIFS"
jsonCIFileStatus :: CIFileStatus d -> JSONCIFileStatus jsonCIFileStatus :: CIFileStatus d -> JSONCIFileStatus
jsonCIFileStatus = \case jsonCIFileStatus = \case
@ -758,19 +602,6 @@ deriving instance Eq (CIStatus d)
deriving instance Show (CIStatus d) deriving instance Show (CIStatus d)
instance MsgDirectionI d => FromJSON (CIStatus d) where
parseJSON v = (\(ACIStatus _ s) -> checkDirection s) . jsonACIStatus <$?> J.parseJSON v
instance ToJSON (CIStatus d) where
toJSON = J.toJSON . jsonCIStatus
toEncoding = J.toEncoding . jsonCIStatus
instance MsgDirectionI d => ToField (CIStatus d) where toField = toField . decodeLatin1 . strEncode
instance (Typeable d, MsgDirectionI d) => FromField (CIStatus d) where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
instance FromField ACIStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
data ACIStatus = forall d. MsgDirectionI d => ACIStatus (SMsgDirection d) (CIStatus d) data ACIStatus = forall d. MsgDirectionI d => ACIStatus (SMsgDirection d) (CIStatus d)
deriving instance Show ACIStatus deriving instance Show ACIStatus
@ -813,14 +644,7 @@ data JSONCIStatus
| JCISRcvNew | JCISRcvNew
| JCISRcvRead | JCISRcvRead
| JCISInvalid {text :: Text} | JCISInvalid {text :: Text}
deriving (Show, Generic) deriving (Show)
instance FromJSON JSONCIStatus where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "JCIS"
instance ToJSON JSONCIStatus where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCIS"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCIS"
jsonCIStatus :: CIStatus d -> JSONCIStatus jsonCIStatus :: CIStatus d -> JSONCIStatus
jsonCIStatus = \case jsonCIStatus = \case
@ -872,14 +696,7 @@ membersGroupItemStatus memStatusCounts
data SndCIStatusProgress data SndCIStatusProgress
= SSPPartial = SSPPartial
| SSPComplete | SSPComplete
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON SndCIStatusProgress where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "SSP"
instance ToJSON SndCIStatusProgress where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "SSP"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "SSP"
instance StrEncoding SndCIStatusProgress where instance StrEncoding SndCIStatusProgress where
strEncode = \case strEncode = \case
@ -929,13 +746,6 @@ instance ChatTypeI 'CTContactRequest where chatTypeI = SCTContactRequest
instance ChatTypeI 'CTContactConnection where chatTypeI = SCTContactConnection instance ChatTypeI 'CTContactConnection where chatTypeI = SCTContactConnection
instance ChatTypeI c => FromJSON (SChatType c) where
parseJSON v = (\(ACT t) -> checkChatType t) . aChatType <$?> J.parseJSON v
instance ToJSON (SChatType c) where
toJSON = J.toJSON . toChatType
toEncoding = J.toEncoding . toChatType
toChatType :: SChatType c -> ChatType toChatType :: SChatType c -> ChatType
toChatType = \case toChatType = \case
SCTDirect -> CTDirect SCTDirect -> CTDirect
@ -1007,9 +817,7 @@ data MsgMetaJSON = MsgMetaJSON
serverTs :: UTCTime, serverTs :: UTCTime,
sndId :: Int64 sndId :: Int64
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON MsgMetaJSON where toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
msgMetaToJson :: MsgMeta -> MsgMetaJSON msgMetaToJson :: MsgMeta -> MsgMetaJSON
msgMetaToJson MsgMeta {integrity, recipient = (rcvId, rcvTs), broker = (serverId, serverTs), sndMsgId = sndId} = msgMetaToJson MsgMeta {integrity, recipient = (rcvId, rcvTs), broker = (serverId, serverTs), sndMsgId = sndId} =
@ -1022,9 +830,6 @@ msgMetaToJson MsgMeta {integrity, recipient = (rcvId, rcvTs), broker = (serverId
sndId sndId
} }
msgMetaJson :: MsgMeta -> Text
msgMetaJson = decodeLatin1 . LB.toStrict . J.encode . msgMetaToJson
data MsgDeliveryStatus (d :: MsgDirection) where data MsgDeliveryStatus (d :: MsgDirection) where
MDSRcvAgent :: MsgDeliveryStatus 'MDRcv MDSRcvAgent :: MsgDeliveryStatus 'MDRcv
MDSRcvAcknowledged :: MsgDeliveryStatus 'MDRcv MDSRcvAcknowledged :: MsgDeliveryStatus 'MDRcv
@ -1081,25 +886,11 @@ deriving instance Show (CIDeleted c)
data ACIDeleted = forall c. ChatTypeI c => ACIDeleted (SChatType c) (CIDeleted c) data ACIDeleted = forall c. ChatTypeI c => ACIDeleted (SChatType c) (CIDeleted c)
instance ChatTypeI c => FromJSON (CIDeleted c) where
parseJSON v = (\(ACIDeleted _ x) -> checkChatType x) . jsonACIDeleted <$?> J.parseJSON v
instance ChatTypeI c => ToJSON (CIDeleted c) where
toJSON = J.toJSON . jsonCIDeleted
toEncoding = J.toEncoding . jsonCIDeleted
data JSONCIDeleted data JSONCIDeleted
= JCIDDeleted {deletedTs :: Maybe UTCTime, chatType :: ChatType} = JCIDDeleted {deletedTs :: Maybe UTCTime, chatType :: ChatType}
| JCIDBlocked {deletedTs :: Maybe UTCTime} | JCIDBlocked {deletedTs :: Maybe UTCTime}
| JCIDModerated {deletedTs :: Maybe UTCTime, byGroupMember :: GroupMember} | JCIDModerated {deletedTs :: Maybe UTCTime, byGroupMember :: GroupMember}
deriving (Show, Generic) deriving (Show)
instance FromJSON JSONCIDeleted where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "JCID"
instance ToJSON JSONCIDeleted where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "JCID"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "JCID"
jsonCIDeleted :: forall d. ChatTypeI d => CIDeleted d -> JSONCIDeleted jsonCIDeleted :: forall d. ChatTypeI d => CIDeleted d -> JSONCIDeleted
jsonCIDeleted = \case jsonCIDeleted = \case
@ -1123,9 +914,7 @@ data ChatItemInfo = ChatItemInfo
{ itemVersions :: [ChatItemVersion], { itemVersions :: [ChatItemVersion],
memberDeliveryStatuses :: Maybe [MemberDeliveryStatus] memberDeliveryStatuses :: Maybe [MemberDeliveryStatus]
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON ChatItemInfo where toEncoding = J.genericToEncoding J.defaultOptions
data ChatItemVersion = ChatItemVersion data ChatItemVersion = ChatItemVersion
{ chatItemVersionId :: Int64, { chatItemVersionId :: Int64,
@ -1134,9 +923,7 @@ data ChatItemVersion = ChatItemVersion
itemVersionTs :: UTCTime, itemVersionTs :: UTCTime,
createdAt :: UTCTime createdAt :: UTCTime
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON ChatItemVersion where toEncoding = J.genericToEncoding J.defaultOptions
mkItemVersion :: ChatItem c d -> Maybe ChatItemVersion mkItemVersion :: ChatItem c d -> Maybe ChatItemVersion
mkItemVersion ChatItem {content, meta} = version <$> ciMsgContent content mkItemVersion ChatItem {content, meta} = version <$> ciMsgContent content
@ -1155,9 +942,7 @@ data MemberDeliveryStatus = MemberDeliveryStatus
{ groupMemberId :: GroupMemberId, { groupMemberId :: GroupMemberId,
memberDeliveryStatus :: CIStatus 'MDSnd memberDeliveryStatus :: CIStatus 'MDSnd
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON MemberDeliveryStatus where toEncoding = J.genericToEncoding J.defaultOptions
data CIModeration = CIModeration data CIModeration = CIModeration
{ moderationId :: Int64, { moderationId :: Int64,
@ -1166,3 +951,187 @@ data CIModeration = CIModeration
moderatedAt :: UTCTime moderatedAt :: UTCTime
} }
deriving (Show) deriving (Show)
$(JQ.deriveJSON (enumJSON $ dropPrefix "CT") ''ChatType)
instance ChatTypeI c => FromJSON (SChatType c) where
parseJSON v = (\(ACT t) -> checkChatType t) . aChatType <$?> J.parseJSON v
instance ToJSON (SChatType c) where
toJSON = J.toJSON . toChatType
toEncoding = J.toEncoding . toChatType
$(JQ.deriveJSON defaultJSON ''ChatName)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "JCID") ''JSONCIDeleted)
instance ChatTypeI c => FromJSON (CIDeleted c) where
parseJSON v = (\(ACIDeleted _ x) -> checkChatType x) . jsonACIDeleted <$?> J.parseJSON v
instance ChatTypeI c => ToJSON (CIDeleted c) where
toJSON = J.toJSON . jsonCIDeleted
toEncoding = J.toEncoding . jsonCIDeleted
$(JQ.deriveJSON defaultJSON ''CITimed)
$(JQ.deriveJSON (enumJSON $ dropPrefix "SSP") ''SndCIStatusProgress)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "JCIS") ''JSONCIStatus)
instance MsgDirectionI d => FromJSON (CIStatus d) where
parseJSON v = (\(ACIStatus _ s) -> checkDirection s) . jsonACIStatus <$?> J.parseJSON v
instance ToJSON (CIStatus d) where
toJSON = J.toJSON . jsonCIStatus
toEncoding = J.toEncoding . jsonCIStatus
instance MsgDirectionI d => ToField (CIStatus d) where toField = toField . decodeLatin1 . strEncode
instance (Typeable d, MsgDirectionI d) => FromField (CIStatus d) where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
instance FromField ACIStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
$(JQ.deriveJSON defaultJSON ''MemberDeliveryStatus)
$(JQ.deriveJSON defaultJSON ''ChatItemVersion)
$(JQ.deriveJSON defaultJSON ''ChatItemInfo)
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (CIMeta c d) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''CIMeta)
instance ChatTypeI c => ToJSON (CIMeta c d) where
toJSON = $(JQ.mkToJSON defaultJSON ''CIMeta)
toEncoding = $(JQ.mkToEncoding defaultJSON ''CIMeta)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "JCIFS") ''JSONCIFileStatus)
instance MsgDirectionI d => FromJSON (CIFileStatus d) where
parseJSON v = (\(AFS _ s) -> checkDirection s) . aciFileStatusJSON <$?> J.parseJSON v
instance ToJSON (CIFileStatus d) where
toJSON = J.toJSON . jsonCIFileStatus
toEncoding = J.toEncoding . jsonCIFileStatus
instance MsgDirectionI d => ToField (CIFileStatus d) where toField = toField . decodeLatin1 . strEncode
instance FromField ACIFileStatus where fromField = fromTextField_ $ eitherToMaybe . strDecode . encodeUtf8
instance MsgDirectionI d => FromJSON (CIFile d) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''CIFile)
instance MsgDirectionI d => ToJSON (CIFile d) where
toJSON = $(JQ.mkToJSON defaultJSON ''CIFile)
toEncoding = $(JQ.mkToEncoding defaultJSON ''CIFile)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "JCI") ''JSONCIDirection)
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (CIDirection c d) where
parseJSON v = (\(CCID _ x') -> checkDirection x') <$?> J.parseJSON v
instance ToJSON (CIDirection c d) where
toJSON = J.toJSON . jsonCIDirection
toEncoding = J.toEncoding . jsonCIDirection
instance ChatTypeI c => FromJSON (CCIDirection c) where
parseJSON v = (\(ACID _ d x) -> checkChatType (CCID d x)) <$?> J.parseJSON v
instance FromJSON ACIDirection where
parseJSON v = jsonACIDirection <$> J.parseJSON v
instance ChatTypeI c => FromJSON (CIQDirection c) where
parseJSON v = (\(ACIQDirection _ x) -> checkChatType x) . jsonACIQDirection <$?> J.parseJSON v
instance ToJSON (CIQDirection c) where
toJSON = J.toJSON . jsonCIQDirection
toEncoding = J.toEncoding . jsonCIQDirection
instance ChatTypeI c => FromJSON (CIQuote c) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''CIQuote)
$(JQ.deriveToJSON defaultJSON ''CIQuote)
$(JQ.deriveJSON defaultJSON ''CIReactionCount)
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (ChatItem c d) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''ChatItem)
instance (ChatTypeI c, MsgDirectionI d) => ToJSON (ChatItem c d) where
toJSON = $(JQ.mkToJSON defaultJSON ''ChatItem)
toEncoding = $(JQ.mkToEncoding defaultJSON ''ChatItem)
instance (ChatTypeI c, MsgDirectionI d) => ToJSON (JSONAnyChatItem c d) where
toJSON = $(JQ.mkToJSON defaultJSON ''JSONAnyChatItem)
toEncoding = $(JQ.mkToEncoding defaultJSON ''JSONAnyChatItem)
instance FromJSON AChatItem where
parseJSON = J.withObject "AChatItem" $ \o -> do
AChatInfo c chatInfo <- o .: "chatInfo"
CChatItem d chatItem <- o .: "chatItem"
pure $ AChatItem c d chatInfo chatItem
instance ToJSON AChatItem where
toJSON (AChatItem _ _ chat item) = J.toJSON $ JSONAnyChatItem chat item
toEncoding (AChatItem _ _ chat item) = J.toEncoding $ JSONAnyChatItem chat item
instance forall c. ChatTypeI c => FromJSON (CChatItem c) where
parseJSON v = J.withObject "CChatItem" parse v
where
parse o = do
CCID d (_ :: CIDirection c d) <- o .: "chatDir"
ci <- J.parseJSON @(ChatItem c d) v
pure $ CChatItem d ci
instance ChatTypeI c => ToJSON (CChatItem c) where
toJSON (CChatItem _ ci) = J.toJSON ci
toEncoding (CChatItem _ ci) = J.toEncoding ci
$(JQ.deriveJSON defaultJSON ''ChatStats)
instance ChatTypeI c => ToJSON (Chat c) where
toJSON = $(JQ.mkToJSON defaultJSON ''Chat)
toEncoding = $(JQ.mkToEncoding defaultJSON ''Chat)
instance FromJSON AChat where
parseJSON = J.withObject "AChat" $ \o -> do
AChatInfo c chatInfo <- o .: "chatInfo"
chatItems <- o .: "chatItems"
chatStats <- o .: "chatStats"
pure $ AChat c Chat {chatInfo, chatItems, chatStats}
instance ToJSON AChat where
toJSON (AChat _ c) = J.toJSON c
toEncoding (AChat _ c) = J.toEncoding c
instance (ChatTypeI c, MsgDirectionI d) => FromJSON (CIReaction c d) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''CIReaction)
instance ChatTypeI c => ToJSON (CIReaction c d) where
toJSON = $(JQ.mkToJSON defaultJSON ''CIReaction)
toEncoding = $(JQ.mkToEncoding defaultJSON ''CIReaction)
instance FromJSON AnyCIReaction where
parseJSON v = J.withObject "AnyCIReaction" parse v
where
parse o = do
ACID c d (_ :: CIDirection c d) <- o .: "chatDir"
ACIR c d <$> J.parseJSON @(CIReaction c d) v
instance ChatTypeI c => ToJSON (JSONCIReaction c d) where
toJSON = $(JQ.mkToJSON defaultJSON ''JSONCIReaction)
toEncoding = $(JQ.mkToEncoding defaultJSON ''JSONCIReaction)
instance FromJSON ACIReaction where
parseJSON = J.withObject "ACIReaction" $ \o -> do
ACIR c d reaction <- o .: "chatReaction"
cInfo <- o .: "chatInfo"
pure $ ACIReaction c d cInfo reaction
instance ToJSON ACIReaction where
toJSON (ACIReaction _ _ cInfo reaction) = J.toJSON $ JSONCIReaction cInfo reaction
toEncoding (ACIReaction _ _ cInfo reaction) = J.toEncoding $ JSONCIReaction cInfo reaction
$(JQ.deriveJSON defaultJSON ''MsgMetaJSON)
msgMetaJson :: MsgMeta -> Text
msgMetaJson = decodeLatin1 . LB.toStrict . J.encode . msgMetaToJson

View file

@ -1,6 +1,5 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-} {-# LANGUAGE KindSignatures #-}
@ -14,9 +13,9 @@
module Simplex.Chat.Messages.CIContent where module Simplex.Chat.Messages.CIContent where
import Data.Aeson (FromJSON, ToJSON)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ import qualified Data.Aeson.TH as JQ
import Data.Aeson.Types as JT
import Data.Int (Int64) import Data.Int (Int64)
import Data.Text (Text) import Data.Text (Text)
import Data.Text.Encoding (decodeLatin1, encodeUtf8) import Data.Text.Encoding (decodeLatin1, encodeUtf8)
@ -24,25 +23,20 @@ import Data.Type.Equality
import Data.Word (Word32) import Data.Word (Word32)
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 Simplex.Chat.Messages.CIContent.Events
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Util import Simplex.Chat.Types.Util
import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..)) import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..))
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON)
import Simplex.Messaging.Util (safeDecodeUtf8, tshow, (<$?>)) import Simplex.Messaging.Util (safeDecodeUtf8, tshow, (<$?>))
data MsgDirection = MDRcv | MDSnd data MsgDirection = MDRcv | MDSnd
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON MsgDirection where $(JQ.deriveJSON (enumJSON $ dropPrefix "MD") ''MsgDirection)
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "MD"
instance ToJSON MsgDirection where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "MD"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "MD"
instance FromField AMsgDirection where fromField = fromIntField_ $ fmap fromMsgDirection . msgDirectionIntP instance FromField AMsgDirection where fromField = fromIntField_ $ fmap fromMsgDirection . msgDirectionIntP
@ -106,14 +100,9 @@ msgDirectionIntP = \case
_ -> Nothing _ -> Nothing
data CIDeleteMode = CIDMBroadcast | CIDMInternal data CIDeleteMode = CIDMBroadcast | CIDMInternal
deriving (Show, Generic) deriving (Show)
instance ToJSON CIDeleteMode where $(JQ.deriveJSON (enumJSON $ dropPrefix "CIDM") ''CIDeleteMode)
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CIDM"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CIDM"
instance FromJSON CIDeleteMode where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CIDM"
ciDeleteModeToText :: CIDeleteMode -> Text ciDeleteModeToText :: CIDeleteMode -> Text
ciDeleteModeToText = \case ciDeleteModeToText = \case
@ -163,14 +152,7 @@ ciMsgContent = \case
_ -> Nothing _ -> Nothing
data MsgDecryptError = MDERatchetHeader | MDETooManySkipped | MDERatchetEarlier | MDEOther data MsgDecryptError = MDERatchetHeader | MDETooManySkipped | MDERatchetEarlier | MDEOther
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance ToJSON MsgDecryptError where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "MDE"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "MDE"
instance FromJSON MsgDecryptError where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "MDE"
ciRequiresAttention :: forall d. MsgDirectionI d => CIContent d -> Bool ciRequiresAttention :: forall d. MsgDirectionI d => CIContent d -> Bool
ciRequiresAttention content = case msgDirection @d of ciRequiresAttention content = case msgDirection @d of
@ -204,135 +186,14 @@ ciRequiresAttention content = case msgDirection @d of
CIRcvModerated -> True CIRcvModerated -> True
CIInvalidJSON _ -> False CIInvalidJSON _ -> False
data RcvGroupEvent
= RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting
| RGEMemberConnected -- CRUserJoinedGroup, CRJoinedGroupMember, CRConnectedToGroupMember
| RGEMemberLeft -- CRLeftMember
| RGEMemberRole {groupMemberId :: GroupMemberId, profile :: Profile, role :: GroupMemberRole}
| RGEUserRole {role :: GroupMemberRole}
| RGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRDeletedMember
| RGEUserDeleted -- CRDeletedMemberUser
| RGEGroupDeleted -- CRGroupDeleted
| RGEGroupUpdated {groupProfile :: GroupProfile} -- CRGroupUpdated
-- RGEInvitedViaGroupLink chat items are not received - they're created when sending group invitations,
-- but being RcvGroupEvent allows them to be assigned to the respective member (and so enable "send direct message")
-- and be created as unread without adding / working around new status for sent items
| RGEInvitedViaGroupLink -- CRSentGroupInvitationViaLink
| RGEMemberCreatedContact -- CRNewMemberContactReceivedInv
deriving (Show, Generic)
instance FromJSON RcvGroupEvent where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RGE"
instance ToJSON RcvGroupEvent where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "RGE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RGE"
newtype DBRcvGroupEvent = RGE RcvGroupEvent
instance FromJSON DBRcvGroupEvent where
parseJSON v = RGE <$> J.genericParseJSON (singleFieldJSON $ dropPrefix "RGE") v
instance ToJSON DBRcvGroupEvent where
toJSON (RGE v) = J.genericToJSON (singleFieldJSON $ dropPrefix "RGE") v
toEncoding (RGE v) = J.genericToEncoding (singleFieldJSON $ dropPrefix "RGE") v
data SndGroupEvent
= SGEMemberRole {groupMemberId :: GroupMemberId, profile :: Profile, role :: GroupMemberRole}
| SGEUserRole {role :: GroupMemberRole}
| SGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRUserDeletedMember
| SGEUserLeft -- CRLeftMemberUser
| SGEGroupUpdated {groupProfile :: GroupProfile} -- CRGroupUpdated
deriving (Show, Generic)
instance FromJSON SndGroupEvent where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "SGE"
instance ToJSON SndGroupEvent where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "SGE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "SGE"
newtype DBSndGroupEvent = SGE SndGroupEvent
instance FromJSON DBSndGroupEvent where
parseJSON v = SGE <$> J.genericParseJSON (singleFieldJSON $ dropPrefix "SGE") v
instance ToJSON DBSndGroupEvent where
toJSON (SGE v) = J.genericToJSON (singleFieldJSON $ dropPrefix "SGE") v
toEncoding (SGE v) = J.genericToEncoding (singleFieldJSON $ dropPrefix "SGE") v
data RcvConnEvent
= RCESwitchQueue {phase :: SwitchPhase}
| RCERatchetSync {syncStatus :: RatchetSyncState}
| RCEVerificationCodeReset
deriving (Show, Generic)
data SndConnEvent
= SCESwitchQueue {phase :: SwitchPhase, member :: Maybe GroupMemberRef}
| SCERatchetSync {syncStatus :: RatchetSyncState, member :: Maybe GroupMemberRef}
deriving (Show, Generic)
instance FromJSON RcvConnEvent where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RCE"
instance ToJSON RcvConnEvent where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "RCE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RCE"
newtype DBRcvConnEvent = RCE RcvConnEvent
instance FromJSON DBRcvConnEvent where
parseJSON v = RCE <$> J.genericParseJSON (singleFieldJSON $ dropPrefix "RCE") v
instance ToJSON DBRcvConnEvent where
toJSON (RCE v) = J.genericToJSON (singleFieldJSON $ dropPrefix "RCE") v
toEncoding (RCE v) = J.genericToEncoding (singleFieldJSON $ dropPrefix "RCE") v
instance FromJSON SndConnEvent where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "SCE"
instance ToJSON SndConnEvent where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "SCE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "SCE"
newtype DBSndConnEvent = SCE SndConnEvent
instance FromJSON DBSndConnEvent where
parseJSON v = SCE <$> J.genericParseJSON (singleFieldJSON $ dropPrefix "SCE") v
instance ToJSON DBSndConnEvent where
toJSON (SCE v) = J.genericToJSON (singleFieldJSON $ dropPrefix "SCE") v
toEncoding (SCE v) = J.genericToEncoding (singleFieldJSON $ dropPrefix "SCE") v
data RcvDirectEvent =
-- RDEProfileChanged {...}
RDEContactDeleted
deriving (Show, Generic)
instance FromJSON RcvDirectEvent where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RDE"
instance ToJSON RcvDirectEvent where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "RDE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RDE"
newtype DBRcvDirectEvent = RDE RcvDirectEvent
instance FromJSON DBRcvDirectEvent where
parseJSON v = RDE <$> J.genericParseJSON (singleFieldJSON $ dropPrefix "RDE") v
instance ToJSON DBRcvDirectEvent where
toJSON (RDE v) = J.genericToJSON (singleFieldJSON $ dropPrefix "RDE") v
toEncoding (RDE v) = J.genericToEncoding (singleFieldJSON $ dropPrefix "RDE") v
newtype DBMsgErrorType = DBME MsgErrorType newtype DBMsgErrorType = DBME MsgErrorType
instance FromJSON DBMsgErrorType where instance FromJSON DBMsgErrorType where
parseJSON v = DBME <$> J.genericParseJSON (singleFieldJSON fstToLower) v parseJSON v = DBME <$> $(JQ.mkParseJSON (singleFieldJSON fstToLower) ''MsgErrorType) v
instance ToJSON DBMsgErrorType where instance ToJSON DBMsgErrorType where
toJSON (DBME v) = J.genericToJSON (singleFieldJSON fstToLower) v toJSON (DBME v) = $(JQ.mkToJSON (singleFieldJSON fstToLower) ''MsgErrorType) v
toEncoding (DBME v) = J.genericToEncoding (singleFieldJSON fstToLower) v toEncoding (DBME v) = $(JQ.mkToEncoding (singleFieldJSON fstToLower) ''MsgErrorType) v
data CIGroupInvitation = CIGroupInvitation data CIGroupInvitation = CIGroupInvitation
{ groupId :: GroupId, { groupId :: GroupId,
@ -341,25 +202,14 @@ data CIGroupInvitation = CIGroupInvitation
groupProfile :: GroupProfile, groupProfile :: GroupProfile,
status :: CIGroupInvitationStatus status :: CIGroupInvitationStatus
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON CIGroupInvitation where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data CIGroupInvitationStatus data CIGroupInvitationStatus
= CIGISPending = CIGISPending
| CIGISAccepted | CIGISAccepted
| CIGISRejected | CIGISRejected
| CIGISExpired | CIGISExpired
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON CIGroupInvitationStatus where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CIGIS"
instance ToJSON CIGroupInvitationStatus where
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CIGIS"
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CIGIS"
ciContentToText :: CIContent d -> Text ciContentToText :: CIContent d -> Text
ciContentToText = \case ciContentToText = \case
@ -685,6 +535,12 @@ ciCallInfoText status duration = case status of
CISCallEnded -> "ended " <> durationText duration CISCallEnded -> "ended " <> durationText duration
CISCallError -> "error" CISCallError -> "error"
$(JQ.deriveJSON (enumJSON $ dropPrefix "MDE") ''MsgDecryptError)
$(JQ.deriveJSON (enumJSON $ dropPrefix "CIGIS") ''CIGroupInvitationStatus)
$(JQ.deriveJSON defaultJSON ''CIGroupInvitation)
$(JQ.deriveJSON (enumJSON $ dropPrefix "CISCall") ''CICallStatus) $(JQ.deriveJSON (enumJSON $ dropPrefix "CISCall") ''CICallStatus)
-- platform specific -- platform specific

View file

@ -0,0 +1,116 @@
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE TemplateHaskell #-}
module Simplex.Chat.Messages.CIContent.Events where
import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson.TH as J
import Simplex.Chat.Types
import Simplex.Messaging.Agent.Protocol (RatchetSyncState (..), SwitchPhase (..))
import Simplex.Messaging.Parsers (dropPrefix, singleFieldJSON, sumTypeJSON)
data RcvGroupEvent
= RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting
| RGEMemberConnected -- CRUserJoinedGroup, CRJoinedGroupMember, CRConnectedToGroupMember
| RGEMemberLeft -- CRLeftMember
| RGEMemberRole {groupMemberId :: GroupMemberId, profile :: Profile, role :: GroupMemberRole}
| RGEUserRole {role :: GroupMemberRole}
| RGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRDeletedMember
| RGEUserDeleted -- CRDeletedMemberUser
| RGEGroupDeleted -- CRGroupDeleted
| RGEGroupUpdated {groupProfile :: GroupProfile} -- CRGroupUpdated
-- RGEInvitedViaGroupLink chat items are not received - they're created when sending group invitations,
-- but being RcvGroupEvent allows them to be assigned to the respective member (and so enable "send direct message")
-- and be created as unread without adding / working around new status for sent items
| RGEInvitedViaGroupLink -- CRSentGroupInvitationViaLink
| RGEMemberCreatedContact -- CRNewMemberContactReceivedInv
deriving (Show)
data SndGroupEvent
= SGEMemberRole {groupMemberId :: GroupMemberId, profile :: Profile, role :: GroupMemberRole}
| SGEUserRole {role :: GroupMemberRole}
| SGEMemberDeleted {groupMemberId :: GroupMemberId, profile :: Profile} -- CRUserDeletedMember
| SGEUserLeft -- CRLeftMemberUser
| SGEGroupUpdated {groupProfile :: GroupProfile} -- CRGroupUpdated
deriving (Show)
data RcvConnEvent
= RCESwitchQueue {phase :: SwitchPhase}
| RCERatchetSync {syncStatus :: RatchetSyncState}
| RCEVerificationCodeReset
deriving (Show)
data SndConnEvent
= SCESwitchQueue {phase :: SwitchPhase, member :: Maybe GroupMemberRef}
| SCERatchetSync {syncStatus :: RatchetSyncState, member :: Maybe GroupMemberRef}
deriving (Show)
data RcvDirectEvent =
-- RDEProfileChanged {...}
RDEContactDeleted
deriving (Show)
-- platform-specific JSON encoding (used in API)
$(J.deriveJSON (sumTypeJSON $ dropPrefix "RGE") ''RcvGroupEvent)
-- platform-independent JSON encoding (stored in DB)
newtype DBRcvGroupEvent = RGE RcvGroupEvent
instance FromJSON DBRcvGroupEvent where
parseJSON v = RGE <$> $(J.mkParseJSON (singleFieldJSON $ dropPrefix "RGE") ''RcvGroupEvent) v
instance ToJSON DBRcvGroupEvent where
toJSON (RGE v) = $(J.mkToJSON (singleFieldJSON $ dropPrefix "RGE") ''RcvGroupEvent) v
toEncoding (RGE v) = $(J.mkToEncoding (singleFieldJSON $ dropPrefix "RGE") ''RcvGroupEvent) v
-- platform-specific JSON encoding (used in API)
$(J.deriveJSON (sumTypeJSON $ dropPrefix "SGE") ''SndGroupEvent)
-- platform-independent JSON encoding (stored in DB)
newtype DBSndGroupEvent = SGE SndGroupEvent
instance FromJSON DBSndGroupEvent where
parseJSON v = SGE <$> $(J.mkParseJSON (singleFieldJSON $ dropPrefix "SGE") ''SndGroupEvent) v
instance ToJSON DBSndGroupEvent where
toJSON (SGE v) = $(J.mkToJSON (singleFieldJSON $ dropPrefix "SGE") ''SndGroupEvent) v
toEncoding (SGE v) = $(J.mkToEncoding (singleFieldJSON $ dropPrefix "SGE") ''SndGroupEvent) v
-- platform-specific JSON encoding (used in API)
$(J.deriveJSON (sumTypeJSON $ dropPrefix "RCE") ''RcvConnEvent)
-- platform-independent JSON encoding (stored in DB)
newtype DBRcvConnEvent = RCE RcvConnEvent
instance FromJSON DBRcvConnEvent where
parseJSON v = RCE <$> $(J.mkParseJSON (singleFieldJSON $ dropPrefix "RCE") ''RcvConnEvent) v
instance ToJSON DBRcvConnEvent where
toJSON (RCE v) = $(J.mkToJSON (singleFieldJSON $ dropPrefix "RCE") ''RcvConnEvent) v
toEncoding (RCE v) = $(J.mkToEncoding (singleFieldJSON $ dropPrefix "RCE") ''RcvConnEvent) v
-- platform-specific JSON encoding (used in API)
$(J.deriveJSON (sumTypeJSON $ dropPrefix "SCE") ''SndConnEvent)
-- platform-independent JSON encoding (stored in DB)
newtype DBSndConnEvent = SCE SndConnEvent
instance FromJSON DBSndConnEvent where
parseJSON v = SCE <$> $(J.mkParseJSON (singleFieldJSON $ dropPrefix "SCE") ''SndConnEvent) v
instance ToJSON DBSndConnEvent where
toJSON (SCE v) = $(J.mkToJSON (singleFieldJSON $ dropPrefix "SCE") ''SndConnEvent) v
toEncoding (SCE v) = $(J.mkToEncoding (singleFieldJSON $ dropPrefix "SCE") ''SndConnEvent) v
$(J.deriveJSON (sumTypeJSON $ dropPrefix "RDE") ''RcvDirectEvent)
-- platform-independent JSON encoding (stored in DB)
newtype DBRcvDirectEvent = RDE RcvDirectEvent
instance FromJSON DBRcvDirectEvent where
parseJSON v = RDE <$> $(J.mkParseJSON (singleFieldJSON $ dropPrefix "RDE") ''RcvDirectEvent) v
instance ToJSON DBRcvDirectEvent where
toJSON (RDE v) = $(J.mkToJSON (singleFieldJSON $ dropPrefix "RDE") ''RcvDirectEvent) v
toEncoding (RDE v) = $(J.mkToEncoding (singleFieldJSON $ dropPrefix "RDE") ''RcvDirectEvent) v

View file

@ -1,8 +1,8 @@
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fobject-code #-} {-# OPTIONS_GHC -fobject-code #-}
@ -13,8 +13,8 @@ import Control.Concurrent.STM
import Control.Exception (catch, SomeException) import Control.Exception (catch, SomeException)
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.Reader import Control.Monad.Reader
import Data.Aeson (ToJSON (..))
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import Data.Bifunctor (first) import Data.Bifunctor (first)
import qualified Data.ByteString.Base64.URL as U import qualified Data.ByteString.Base64.URL as U
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
@ -32,7 +32,6 @@ import Foreign.Ptr
import Foreign.StablePtr import Foreign.StablePtr
import Foreign.Storable (poke) import Foreign.Storable (poke)
import GHC.IO.Encoding (setLocaleEncoding, setFileSystemEncoding, setForeignEncoding) import GHC.IO.Encoding (setLocaleEncoding, setFileSystemEncoding, setForeignEncoding)
import GHC.Generics (Generic)
import Simplex.Chat import Simplex.Chat
import Simplex.Chat.Controller import Simplex.Chat.Controller
import Simplex.Chat.Markdown (ParsedMarkdown (..), parseMaybeMarkdownList) import Simplex.Chat.Markdown (ParsedMarkdown (..), parseMaybeMarkdownList)
@ -50,12 +49,26 @@ import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), Migrati
import Simplex.Messaging.Client (defaultNetworkConfig) import Simplex.Messaging.Client (defaultNetworkConfig)
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, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON)
import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), BasicAuth (..), CorrId (..), ProtoServerWithAuth (..), ProtocolServer (..)) import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), BasicAuth (..), CorrId (..), ProtoServerWithAuth (..), ProtocolServer (..))
import Simplex.Messaging.Util (catchAll, liftEitherWith, safeDecodeUtf8) import Simplex.Messaging.Util (catchAll, liftEitherWith, safeDecodeUtf8)
import System.IO (utf8) import System.IO (utf8)
import System.Timeout (timeout) import System.Timeout (timeout)
data DBMigrationResult
= DBMOk
| DBMInvalidConfirmation
| DBMErrorNotADatabase {dbFile :: String}
| DBMErrorMigration {dbFile :: String, migrationError :: MigrationError}
| DBMErrorSQL {dbFile :: String, migrationSQLError :: String}
deriving (Show)
$(JQ.deriveToJSON (sumTypeJSON $ dropPrefix "DBM") ''DBMigrationResult)
data APIResponse = APIResponse {corr :: Maybe CorrId, remoteHostId :: Maybe RemoteHostId, resp :: ChatResponse}
$(JQ.deriveToJSON defaultJSON ''APIResponse)
foreign export ccall "chat_migrate_init" cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString foreign export ccall "chat_migrate_init" cChatMigrateInit :: CString -> CString -> CString -> Ptr (StablePtr ChatController) -> IO CJSONString
foreign export ccall "chat_close_store" cChatCloseStore :: StablePtr ChatController -> IO CString foreign export ccall "chat_close_store" cChatCloseStore :: StablePtr ChatController -> IO CString
@ -189,18 +202,6 @@ defaultMobileConfig =
getActiveUser_ :: SQLiteStore -> IO (Maybe User) getActiveUser_ :: SQLiteStore -> IO (Maybe User)
getActiveUser_ st = find activeUser <$> withTransaction st getUsers getActiveUser_ st = find activeUser <$> withTransaction st getUsers
data DBMigrationResult
= DBMOk
| DBMInvalidConfirmation
| DBMErrorNotADatabase {dbFile :: String}
| DBMErrorMigration {dbFile :: String, migrationError :: MigrationError}
| DBMErrorSQL {dbFile :: String, migrationSQLError :: String}
deriving (Show, Generic)
instance ToJSON DBMigrationResult where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "DBM"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "DBM"
chatMigrateInit :: String -> String -> String -> IO (Either DBMigrationResult ChatController) chatMigrateInit :: String -> String -> String -> IO (Either DBMigrationResult ChatController)
chatMigrateInit dbFilePrefix dbKey confirm = runExceptT $ do chatMigrateInit dbFilePrefix dbKey confirm = runExceptT $ do
confirmMigrations <- liftEitherWith (const DBMInvalidConfirmation) $ strDecode $ B.pack confirm confirmMigrations <- liftEitherWith (const DBMInvalidConfirmation) $ strDecode $ B.pack confirm
@ -264,10 +265,3 @@ chatPasswordHash pwd salt = either (const "") passwordHash salt'
where where
salt' = U.decode salt salt' = U.decode salt
passwordHash = U.encode . C.sha512Hash . (pwd <>) passwordHash = U.encode . C.sha512Hash . (pwd <>)
data APIResponse = APIResponse {corr :: Maybe CorrId, remoteHostId :: Maybe RemoteHostId, resp :: ChatResponse}
deriving (Generic)
instance ToJSON APIResponse where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}

View file

@ -1,7 +1,7 @@
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TupleSections #-} {-# LANGUAGE TupleSections #-}
module Simplex.Chat.Mobile.File module Simplex.Chat.Mobile.File
@ -19,8 +19,8 @@ where
import Control.Monad import Control.Monad
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Aeson (ToJSON)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as LB import qualified Data.ByteString.Lazy as LB
@ -32,7 +32,6 @@ import Foreign.C
import Foreign.Marshal.Alloc (mallocBytes) import Foreign.Marshal.Alloc (mallocBytes)
import Foreign.Ptr import Foreign.Ptr
import Foreign.Storable (poke, pokeByteOff) import Foreign.Storable (poke, pokeByteOff)
import GHC.Generics (Generic)
import Simplex.Chat.Mobile.Shared import Simplex.Chat.Mobile.Shared
import Simplex.Chat.Util (chunkSize, encryptFile) import Simplex.Chat.Util (chunkSize, encryptFile)
import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..), CryptoFileHandle, FTCryptoError (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..), CryptoFileHandle, FTCryptoError (..))
@ -45,9 +44,8 @@ import UnliftIO (Handle, IOMode (..), withFile)
data WriteFileResult data WriteFileResult
= WFResult {cryptoArgs :: CryptoFileArgs} = WFResult {cryptoArgs :: CryptoFileArgs}
| WFError {writeError :: String} | WFError {writeError :: String}
deriving (Generic)
instance ToJSON WriteFileResult where toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "WF" $(JQ.deriveToJSON (sumTypeJSON $ dropPrefix "WF") ''WriteFileResult)
cChatWriteFile :: CString -> Ptr Word8 -> CInt -> IO CJSONString cChatWriteFile :: CString -> Ptr Word8 -> CInt -> IO CJSONString
cChatWriteFile cPath ptr len = do cChatWriteFile cPath ptr len = do
@ -66,9 +64,6 @@ chatWriteFile path s = do
data ReadFileResult data ReadFileResult
= RFResult {fileSize :: Int} = RFResult {fileSize :: Int}
| RFError {readError :: String} | RFError {readError :: String}
deriving (Generic)
instance ToJSON ReadFileResult where toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RF"
cChatReadFile :: CString -> CString -> CString -> IO (Ptr Word8) cChatReadFile :: CString -> CString -> CString -> IO (Ptr Word8)
cChatReadFile cPath cKey cNonce = do cChatReadFile cPath cKey cNonce = do
@ -141,3 +136,5 @@ chatDecryptFile fromPath keyStr nonceStr toPath = fromLeft "" <$> runCatchExcept
runCatchExceptT :: ExceptT String IO a -> IO (Either String a) runCatchExceptT :: ExceptT String IO a -> IO (Either String a)
runCatchExceptT action = runExceptT action `catchAll` (pure . Left . show) runCatchExceptT action = runExceptT action `catchAll` (pure . Left . show)
$(JQ.deriveToJSON (sumTypeJSON $ dropPrefix "RF") ''ReadFileResult)

View file

@ -1,6 +1,5 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-} {-# LANGUAGE KindSignatures #-}
@ -11,6 +10,7 @@
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE StrictData #-} {-# LANGUAGE StrictData #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-}
@ -23,6 +23,7 @@ import Data.Aeson (FromJSON, ToJSON, (.:), (.:?), (.=))
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.Encoding as JE
import qualified Data.Aeson.KeyMap as JM import qualified Data.Aeson.KeyMap as JM
import qualified Data.Aeson.TH as JQ
import qualified Data.Aeson.Types as JT import qualified Data.Aeson.Types as JT
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
@ -40,13 +41,12 @@ import Data.Typeable (Typeable)
import Data.Word (Word32) import Data.Word (Word32)
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 Simplex.Chat.Call import Simplex.Chat.Call
import Simplex.Chat.Types import Simplex.Chat.Types
import Simplex.Chat.Types.Util import Simplex.Chat.Types.Util
import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON)
import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>))
import Simplex.Messaging.Version hiding (version) import Simplex.Messaging.Version hiding (version)
@ -70,14 +70,9 @@ data ConnectionEntity
| SndFileConnection {entityConnection :: Connection, sndFileTransfer :: SndFileTransfer} | SndFileConnection {entityConnection :: Connection, sndFileTransfer :: SndFileTransfer}
| RcvFileConnection {entityConnection :: Connection, rcvFileTransfer :: RcvFileTransfer} | RcvFileConnection {entityConnection :: Connection, rcvFileTransfer :: RcvFileTransfer}
| UserContactConnection {entityConnection :: Connection, userContact :: UserContact} | UserContactConnection {entityConnection :: Connection, userContact :: UserContact}
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON ConnectionEntity where $(JQ.deriveJSON (sumTypeJSON fstToLower) ''ConnectionEntity)
parseJSON = J.genericParseJSON $ sumTypeJSON fstToLower
instance ToJSON ConnectionEntity where
toJSON = J.genericToJSON $ sumTypeJSON fstToLower
toEncoding = J.genericToEncoding $ sumTypeJSON fstToLower
updateEntityConnStatus :: ConnectionEntity -> ConnStatus -> ConnectionEntity updateEntityConnStatus :: ConnectionEntity -> ConnStatus -> ConnectionEntity
updateEntityConnStatus connEntity connStatus = case connEntity of updateEntityConnStatus connEntity connStatus = case connEntity of
@ -104,8 +99,6 @@ instance MsgEncodingI 'Binary where encoding = SBinary
instance MsgEncodingI 'Json where encoding = SJson instance MsgEncodingI 'Json where encoding = SJson
data ACMEventTag = forall e. MsgEncodingI e => ACMEventTag (SMsgEncoding e) (CMEventTag e)
instance TestEquality SMsgEncoding where instance TestEquality SMsgEncoding where
testEquality SBinary SBinary = Just Refl testEquality SBinary SBinary = Just Refl
testEquality SJson SJson = Just Refl testEquality SJson SJson = Just Refl
@ -127,7 +120,6 @@ data AppMessageJson = AppMessageJson
event :: Text, event :: Text,
params :: J.Object params :: J.Object
} }
deriving (Generic, FromJSON)
data AppMessageBinary = AppMessageBinary data AppMessageBinary = AppMessageBinary
{ msgId :: Maybe SharedMsgId, { msgId :: Maybe SharedMsgId,
@ -135,10 +127,6 @@ data AppMessageBinary = AppMessageBinary
body :: ByteString body :: ByteString
} }
instance ToJSON AppMessageJson where
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
instance StrEncoding AppMessageBinary where instance StrEncoding AppMessageBinary where
strEncode AppMessageBinary {tag, msgId, body} = smpEncode (tag, msgId', Tail body) strEncode AppMessageBinary {tag, msgId, body} = smpEncode (tag, msgId', Tail body)
where where
@ -167,20 +155,42 @@ instance ToJSON SharedMsgId where
toJSON = strToJSON toJSON = strToJSON
toEncoding = strToJEncoding toEncoding = strToJEncoding
$(JQ.deriveJSON defaultJSON ''AppMessageJson)
data MsgRef = MsgRef data MsgRef = MsgRef
{ msgId :: Maybe SharedMsgId, { msgId :: Maybe SharedMsgId,
sentAt :: UTCTime, sentAt :: UTCTime,
sent :: Bool, sent :: Bool,
memberId :: Maybe MemberId -- must be present in all group message references, both referencing sent and received memberId :: Maybe MemberId -- must be present in all group message references, both referencing sent and received
} }
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON MsgRef where $(JQ.deriveJSON defaultJSON ''MsgRef)
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
instance ToJSON MsgRef where data LinkPreview = LinkPreview {uri :: Text, title :: Text, description :: Text, image :: ImageData, content :: Maybe LinkContent}
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True} deriving (Eq, Show)
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data LinkContent = LCPage | LCImage | LCVideo {duration :: Maybe Int} | LCUnknown {tag :: Text, json :: J.Object}
deriving (Eq, Show)
$(pure [])
instance FromJSON LinkContent where
parseJSON v@(J.Object j) =
$(JQ.mkParseJSON (taggedObjectJSON $ dropPrefix "LC") ''LinkContent) v
<|> LCUnknown <$> j .: "type" <*> pure j
parseJSON invalid =
JT.prependFailure "bad LinkContent, " (JT.typeMismatch "Object" invalid)
instance ToJSON LinkContent where
toJSON = \case
LCUnknown _ j -> J.Object j
v -> $(JQ.mkToJSON (taggedObjectJSON $ dropPrefix "LC") ''LinkContent) v
toEncoding = \case
LCUnknown _ j -> JE.value $ J.Object j
v -> $(JQ.mkToEncoding (taggedObjectJSON $ dropPrefix "LC") ''LinkContent) v
$(JQ.deriveJSON defaultJSON ''LinkPreview)
data ChatMessage e = ChatMessage data ChatMessage e = ChatMessage
{ chatVRange :: VersionRange, { chatVRange :: VersionRange,
@ -191,19 +201,6 @@ data ChatMessage e = ChatMessage
data AChatMessage = forall e. MsgEncodingI e => ACMsg (SMsgEncoding e) (ChatMessage e) data AChatMessage = forall e. MsgEncodingI e => ACMsg (SMsgEncoding e) (ChatMessage e)
instance MsgEncodingI e => StrEncoding (ChatMessage e) where
strEncode msg = case chatToAppMessage msg of
AMJson m -> LB.toStrict $ J.encode m
AMBinary m -> strEncode m
strP = (\(ACMsg _ m) -> checkEncoding m) <$?> strP
instance StrEncoding AChatMessage where
strEncode (ACMsg _ m) = strEncode m
strP =
A.peekChar' >>= \case
'{' -> ACMsg SJson <$> ((appJsonToCM <=< J.eitherDecodeStrict') <$?> A.takeByteString)
_ -> ACMsg SBinary <$> (appBinaryToCM <$?> strP)
data ChatMsgEvent (e :: MsgEncoding) where data ChatMsgEvent (e :: MsgEncoding) where
XMsgNew :: MsgContainer -> ChatMsgEvent 'Json XMsgNew :: MsgContainer -> ChatMsgEvent 'Json
XMsgFileDescr :: {msgId :: SharedMsgId, fileDescr :: FileDescr} -> ChatMsgEvent 'Json XMsgFileDescr :: {msgId :: SharedMsgId, fileDescr :: FileDescr} -> ChatMsgEvent 'Json
@ -329,11 +326,7 @@ instance Encoding InlineFileChunk where
pure FileChunk {chunkNo = fromIntegral $ c2w c, chunkBytes} pure FileChunk {chunkNo = fromIntegral $ c2w c, chunkBytes}
data QuotedMsg = QuotedMsg {msgRef :: MsgRef, content :: MsgContent} data QuotedMsg = QuotedMsg {msgRef :: MsgRef, content :: MsgContent}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON QuotedMsg where
toEncoding = J.genericToEncoding J.defaultOptions
toJSON = J.genericToJSON J.defaultOptions
cmToQuotedMsg :: AChatMsgEvent -> Maybe QuotedMsg cmToQuotedMsg :: AChatMsgEvent -> Maybe QuotedMsg
cmToQuotedMsg = \case cmToQuotedMsg = \case
@ -386,34 +379,6 @@ isQuote = \case
MCQuote {} -> True MCQuote {} -> True
_ -> False _ -> False
data LinkPreview = LinkPreview {uri :: Text, title :: Text, description :: Text, image :: ImageData, content :: Maybe LinkContent}
deriving (Eq, Show, Generic)
data LinkContent = LCPage | LCImage | LCVideo {duration :: Maybe Int} | LCUnknown {tag :: Text, json :: J.Object}
deriving (Eq, Show, Generic)
instance FromJSON LinkPreview where
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
instance ToJSON LinkPreview where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
instance FromJSON LinkContent where
parseJSON v@(J.Object j) =
J.genericParseJSON (taggedObjectJSON $ dropPrefix "LC") v
<|> LCUnknown <$> j .: "type" <*> pure j
parseJSON invalid =
JT.prependFailure "bad LinkContent, " (JT.typeMismatch "Object" invalid)
instance ToJSON LinkContent where
toJSON = \case
LCUnknown _ j -> J.Object j
v -> J.genericToJSON (taggedObjectJSON $ dropPrefix "LC") v
toEncoding = \case
LCUnknown _ j -> JE.value $ J.Object j
v -> J.genericToEncoding (taggedObjectJSON $ dropPrefix "LC") v
data MsgContent data MsgContent
= MCText Text = MCText Text
| MCLink {text :: Text, preview :: LinkPreview} | MCLink {text :: Text, preview :: LinkPreview}
@ -466,6 +431,21 @@ msgContentTag = \case
data ExtMsgContent = ExtMsgContent {content :: MsgContent, file :: Maybe FileInvitation, ttl :: Maybe Int, live :: Maybe Bool} data ExtMsgContent = ExtMsgContent {content :: MsgContent, file :: Maybe FileInvitation, ttl :: Maybe Int, live :: Maybe Bool}
deriving (Eq, Show) deriving (Eq, Show)
$(JQ.deriveJSON defaultJSON ''QuotedMsg)
instance MsgEncodingI e => StrEncoding (ChatMessage e) where
strEncode msg = case chatToAppMessage msg of
AMJson m -> LB.toStrict $ J.encode m
AMBinary m -> strEncode m
strP = (\(ACMsg _ m) -> checkEncoding m) <$?> strP
instance StrEncoding AChatMessage where
strEncode (ACMsg _ m) = strEncode m
strP =
A.peekChar' >>= \case
'{' -> ACMsg SJson <$> ((appJsonToCM <=< J.eitherDecodeStrict') <$?> A.takeByteString)
_ -> ACMsg SBinary <$> (appBinaryToCM <$?> strP)
parseMsgContainer :: J.Object -> JT.Parser MsgContainer parseMsgContainer :: J.Object -> JT.Parser MsgContainer
parseMsgContainer v = parseMsgContainer v =
MCQuote <$> v .: "quote" <*> mc MCQuote <$> v .: "quote" <*> mc
@ -545,6 +525,8 @@ instance ToField MsgContent where
instance FromField MsgContent where instance FromField MsgContent where
fromField = fromTextField_ decodeJSON fromField = fromTextField_ decodeJSON
data ACMEventTag = forall e. MsgEncodingI e => ACMEventTag (SMsgEncoding e) (CMEventTag e)
data CMEventTag (e :: MsgEncoding) where data CMEventTag (e :: MsgEncoding) where
XMsgNew_ :: CMEventTag 'Json XMsgNew_ :: CMEventTag 'Json
XMsgFileDescr_ :: CMEventTag 'Json XMsgFileDescr_ :: CMEventTag 'Json

View file

@ -1,5 +1,4 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}

View file

@ -1,6 +1,5 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TemplateHaskell #-}
@ -12,7 +11,7 @@ import Data.Int (Int64)
import Data.Text (Text) import Data.Text (Text)
import qualified Simplex.Messaging.Crypto as C import qualified Simplex.Messaging.Crypto as C
import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client) import Simplex.Messaging.Transport.HTTP2.Client (HTTP2Client)
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, sumTypeJSON)
import UnliftIO import UnliftIO
data RemoteHostClient = RemoteHostClient data RemoteHostClient = RemoteHostClient
@ -116,10 +115,10 @@ $(J.deriveJSON (sumTypeJSON $ dropPrefix "RPE") ''RemoteProtocolError)
$(J.deriveJSON (enumJSON $ dropPrefix "PE") ''PlatformEncoding) $(J.deriveJSON (enumJSON $ dropPrefix "PE") ''PlatformEncoding)
$(J.deriveJSON J.defaultOptions ''RemoteCtrlOOB) $(J.deriveJSON defaultJSON ''RemoteCtrlOOB)
$(J.deriveJSON J.defaultOptions ''RemoteHostInfo) $(J.deriveJSON defaultJSON ''RemoteHostInfo)
$(J.deriveJSON J.defaultOptions {J.omitNothingFields = True} ''RemoteCtrl) $(J.deriveJSON defaultJSON ''RemoteCtrl)
$(J.deriveJSON J.defaultOptions {J.omitNothingFields = True} ''RemoteCtrlInfo) $(J.deriveJSON defaultJSON ''RemoteCtrlInfo)

View file

@ -1,10 +1,10 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
@ -61,8 +61,7 @@ where
import Control.Monad import Control.Monad
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson.TH as J
import qualified Data.Aeson as J
import Data.Functor (($>)) import Data.Functor (($>))
import Data.Int (Int64) import Data.Int (Int64)
import qualified Data.List.NonEmpty as L import qualified Data.List.NonEmpty as L
@ -73,7 +72,6 @@ import Data.Text.Encoding (decodeLatin1, encodeUtf8)
import Data.Time.Clock (UTCTime (..), getCurrentTime) import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (NamedParam (..), Only (..), (:.) (..)) import Database.SQLite.Simple (NamedParam (..), Only (..), (:.) (..))
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)
import GHC.Generics (Generic)
import Simplex.Chat.Call import Simplex.Chat.Call
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
@ -86,6 +84,7 @@ import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow)
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
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 (defaultJSON)
import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolTypeI (..), SubscriptionMode) import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolTypeI (..), SubscriptionMode)
import Simplex.Messaging.Transport.Client (TransportHost) import Simplex.Messaging.Transport.Client (TransportHost)
import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Util (safeDecodeUtf8)
@ -400,17 +399,17 @@ data UserContactLink = UserContactLink
{ connReqContact :: ConnReqContact, { connReqContact :: ConnReqContact,
autoAccept :: Maybe AutoAccept autoAccept :: Maybe AutoAccept
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON UserContactLink where toEncoding = J.genericToEncoding J.defaultOptions
data AutoAccept = AutoAccept data AutoAccept = AutoAccept
{ acceptIncognito :: IncognitoEnabled, { acceptIncognito :: IncognitoEnabled,
autoReply :: Maybe MsgContent autoReply :: Maybe MsgContent
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON AutoAccept where toEncoding = J.genericToEncoding J.defaultOptions $(J.deriveJSON defaultJSON ''AutoAccept)
$(J.deriveJSON defaultJSON ''UserContactLink)
toUserContactLink :: (ConnReqContact, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink toUserContactLink :: (ConnReqContact, Bool, IncognitoEnabled, Maybe MsgContent) -> UserContactLink
toUserContactLink (connReq, autoAccept, acceptIncognito, autoReply) = toUserContactLink (connReq, autoAccept, acceptIncognito, autoReply) =

View file

@ -1,11 +1,11 @@
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-} {-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeOperators #-} {-# LANGUAGE TypeOperators #-}
module Simplex.Chat.Store.Shared where module Simplex.Chat.Store.Shared where
@ -16,8 +16,7 @@ import Control.Monad
import Control.Monad.Except import Control.Monad.Except
import Control.Monad.IO.Class import Control.Monad.IO.Class
import Crypto.Random (ChaChaDRG, randomBytesGenerate) import Crypto.Random (ChaChaDRG, randomBytesGenerate)
import Data.Aeson (FromJSON, ToJSON) import qualified Data.Aeson.TH as J
import qualified Data.Aeson as J
import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Base64 as B64
import Data.ByteString.Char8 (ByteString) import Data.ByteString.Char8 (ByteString)
import Data.Int (Int64) import Data.Int (Int64)
@ -28,7 +27,6 @@ import Data.Time.Clock (UTCTime (..), getCurrentTime)
import Database.SQLite.Simple (NamedParam (..), Only (..), Query, SQLError, (:.) (..)) import Database.SQLite.Simple (NamedParam (..), Only (..), Query, SQLError, (:.) (..))
import qualified Database.SQLite.Simple as SQL import qualified Database.SQLite.Simple as SQL
import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.QQ (sql)
import GHC.Generics (Generic)
import Simplex.Chat.Messages import Simplex.Chat.Messages
import Simplex.Chat.Protocol import Simplex.Chat.Protocol
import Simplex.Chat.Remote.Types import Simplex.Chat.Remote.Types
@ -103,14 +101,9 @@ data StoreError
| SENoGroupSndStatus {itemId :: ChatItemId, groupMemberId :: GroupMemberId} | SENoGroupSndStatus {itemId :: ChatItemId, groupMemberId :: GroupMemberId}
| SERemoteHostNotFound {remoteHostId :: RemoteHostId} | SERemoteHostNotFound {remoteHostId :: RemoteHostId}
| SERemoteCtrlNotFound {remoteCtrlId :: RemoteCtrlId} | SERemoteCtrlNotFound {remoteCtrlId :: RemoteCtrlId}
deriving (Show, Exception, Generic) deriving (Show, Exception)
instance FromJSON StoreError where $(J.deriveJSON (sumTypeJSON $ dropPrefix "SE") ''StoreError)
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "SE"
instance ToJSON StoreError where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "SE"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "SE"
insertedRowId :: DB.Connection -> IO Int64 insertedRowId :: DB.Connection -> IO Int64
insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()" insertedRowId db = fromOnly . head <$> DB.query_ db "SELECT last_insert_rowid()"

View file

@ -2,7 +2,6 @@
{-# LANGUAGE ConstraintKinds #-} {-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
@ -45,14 +44,13 @@ import Database.SQLite.Simple.FromField (returnError, FromField(..))
import Database.SQLite.Simple.Internal (Field (..)) import Database.SQLite.Simple.Internal (Field (..))
import Database.SQLite.Simple.Ok import Database.SQLite.Simple.Ok
import Database.SQLite.Simple.ToField (ToField (..)) import Database.SQLite.Simple.ToField (ToField (..))
import GHC.Generics (Generic)
import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Preferences
import Simplex.Chat.Types.Util import Simplex.Chat.Types.Util
import Simplex.FileTransfer.Description (FileDigest) import Simplex.FileTransfer.Description (FileDigest)
import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId)
import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) import Simplex.Messaging.Crypto.File (CryptoFileArgs (..))
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON, enumJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, sumTypeJSON, taggedObjectJSON, enumJSON)
import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI)
import Simplex.Messaging.Util ((<$?>)) import Simplex.Messaging.Util ((<$?>))
import Simplex.Messaging.Version import Simplex.Messaging.Version
@ -264,9 +262,7 @@ data UserContact = UserContact
connReqContact :: ConnReqContact, connReqContact :: ConnReqContact,
groupId :: Maybe GroupId groupId :: Maybe GroupId
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON UserContact where toEncoding = J.genericToEncoding J.defaultOptions
userContactGroupId :: UserContact -> Maybe GroupId userContactGroupId :: UserContact -> Maybe GroupId
userContactGroupId UserContact {groupId} = groupId userContactGroupId UserContact {groupId} = groupId
@ -284,10 +280,7 @@ data UserContactRequest = UserContactRequest
updatedAt :: UTCTime, updatedAt :: UTCTime,
xContactId :: Maybe XContactId xContactId :: Maybe XContactId
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON UserContactRequest where
toEncoding = J.genericToEncoding J.defaultOptions
newtype XContactId = XContactId ByteString newtype XContactId = XContactId ByteString
deriving (Eq, Show) deriving (Eq, Show)
@ -341,9 +334,7 @@ optionalFullName displayName fullName
| otherwise = " (" <> fullName <> ")" | otherwise = " (" <> fullName <> ")"
data Group = Group {groupInfo :: GroupInfo, members :: [GroupMember]} data Group = Group {groupInfo :: GroupInfo, members :: [GroupMember]}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON Group where toEncoding = J.genericToEncoding J.defaultOptions
type GroupId = Int64 type GroupId = Int64
@ -359,9 +350,7 @@ data GroupInfo = GroupInfo
updatedAt :: UTCTime, updatedAt :: UTCTime,
chatTs :: Maybe UTCTime chatTs :: Maybe UTCTime
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupInfo where toEncoding = J.genericToEncoding J.defaultOptions
groupName' :: GroupInfo -> GroupName groupName' :: GroupInfo -> GroupName
groupName' GroupInfo {localDisplayName = g} = g groupName' GroupInfo {localDisplayName = g} = g
@ -369,9 +358,7 @@ groupName' GroupInfo {localDisplayName = g} = g
data GroupSummary = GroupSummary data GroupSummary = GroupSummary
{ currentMembers :: Int { currentMembers :: Int
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON GroupSummary where toEncoding = J.genericToEncoding J.defaultOptions
data ContactOrGroup = CGContact Contact | CGGroup Group data ContactOrGroup = CGContact Contact | CGGroup Group
@ -386,9 +373,7 @@ data ChatSettings = ChatSettings
sendRcpts :: Maybe Bool, sendRcpts :: Maybe Bool,
favorite :: Bool favorite :: Bool
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON ChatSettings where toEncoding = J.genericToEncoding J.defaultOptions
defaultChatSettings :: ChatSettings defaultChatSettings :: ChatSettings
defaultChatSettings = defaultChatSettings =
@ -402,18 +387,7 @@ chatHasNtfs :: ChatSettings -> Bool
chatHasNtfs ChatSettings {enableNtfs} = enableNtfs /= MFNone chatHasNtfs ChatSettings {enableNtfs} = enableNtfs /= MFNone
data MsgFilter = MFNone | MFAll | MFMentions data MsgFilter = MFNone | MFAll | MFMentions
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON MsgFilter where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "MF"
instance ToJSON MsgFilter where
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "MF"
toJSON = J.genericToJSON . enumJSON $ dropPrefix "MF"
instance FromField MsgFilter where fromField = fromIntField_ msgFilterIntP
instance ToField MsgFilter where toField = toField . msgFilterInt
msgFilterInt :: MsgFilter -> Int msgFilterInt :: MsgFilter -> Int
msgFilterInt = \case msgFilterInt = \case
@ -496,11 +470,7 @@ data Profile = Profile
-- - incognito -- - incognito
-- - local_alias -- - local_alias
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON Profile where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
-- check if profiles match ignoring preferences -- check if profiles match ignoring preferences
profilesMatch :: LocalProfile -> LocalProfile -> Bool profilesMatch :: LocalProfile -> LocalProfile -> Bool
@ -522,11 +492,7 @@ data LocalProfile = LocalProfile
preferences :: Maybe Preferences, preferences :: Maybe Preferences,
localAlias :: LocalAlias localAlias :: LocalAlias
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON LocalProfile where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
localProfileId :: LocalProfile -> ProfileId localProfileId :: LocalProfile -> ProfileId
localProfileId LocalProfile{profileId} = profileId localProfileId LocalProfile{profileId} = profileId
@ -546,11 +512,7 @@ data GroupProfile = GroupProfile
image :: Maybe ImageData, image :: Maybe ImageData,
groupPreferences :: Maybe GroupPreferences groupPreferences :: Maybe GroupPreferences
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupProfile where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
newtype ImageData = ImageData Text newtype ImageData = ImageData Text
deriving (Eq, Show) deriving (Eq, Show)
@ -567,14 +529,6 @@ instance ToField ImageData where toField (ImageData t) = toField t
instance FromField ImageData where fromField = fmap ImageData . fromField instance FromField ImageData where fromField = fmap ImageData . fromField
data CReqClientData = CRDataGroup {groupLinkId :: GroupLinkId} data CReqClientData = CRDataGroup {groupLinkId :: GroupLinkId}
deriving (Generic)
instance ToJSON CReqClientData where
toJSON = J.genericToJSON . taggedObjectJSON $ dropPrefix "CRData"
toEncoding = J.genericToEncoding . taggedObjectJSON $ dropPrefix "CRData"
instance FromJSON CReqClientData where
parseJSON = J.genericParseJSON . taggedObjectJSON $ dropPrefix "CRData"
newtype GroupLinkId = GroupLinkId {unGroupLinkId :: ByteString} -- used to identify invitation via group link newtype GroupLinkId = GroupLinkId {unGroupLinkId :: ByteString} -- used to identify invitation via group link
deriving (Eq, Show) deriving (Eq, Show)
@ -602,29 +556,19 @@ data GroupInvitation = GroupInvitation
groupProfile :: GroupProfile, groupProfile :: GroupProfile,
groupLinkId :: Maybe GroupLinkId groupLinkId :: Maybe GroupLinkId
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupInvitation where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data MemberIdRole = MemberIdRole data MemberIdRole = MemberIdRole
{ memberId :: MemberId, { memberId :: MemberId,
memberRole :: GroupMemberRole memberRole :: GroupMemberRole
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON MemberIdRole where toEncoding = J.genericToEncoding J.defaultOptions
data IntroInvitation = IntroInvitation data IntroInvitation = IntroInvitation
{ groupConnReq :: ConnReqInvitation, { groupConnReq :: ConnReqInvitation,
directConnReq :: Maybe ConnReqInvitation directConnReq :: Maybe ConnReqInvitation
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON IntroInvitation where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data MemberInfo = MemberInfo data MemberInfo = MemberInfo
{ memberId :: MemberId, { memberId :: MemberId,
@ -632,11 +576,7 @@ data MemberInfo = MemberInfo
v :: Maybe ChatVersionRange, v :: Maybe ChatVersionRange,
profile :: Profile profile :: Profile
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON MemberInfo where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
memberInfo :: GroupMember -> MemberInfo memberInfo :: GroupMember -> MemberInfo
memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} = memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} =
@ -675,16 +615,10 @@ data GroupMember = GroupMember
memberContactProfileId :: ProfileId, memberContactProfileId :: ProfileId,
activeConn :: Maybe Connection activeConn :: Maybe Connection
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupMember where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data GroupMemberRef = GroupMemberRef {groupMemberId :: Int64, profile :: Profile} data GroupMemberRef = GroupMemberRef {groupMemberId :: Int64, profile :: Profile}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupMemberRef where toEncoding = J.genericToEncoding J.defaultOptions
groupMemberRef :: GroupMember -> GroupMemberRef groupMemberRef :: GroupMember -> GroupMemberRef
groupMemberRef GroupMember {groupMemberId, memberProfile = p} = groupMemberRef GroupMember {groupMemberId, memberProfile = p} =
@ -744,14 +678,7 @@ instance ToJSON MemberId where
toEncoding = strToJEncoding toEncoding = strToJEncoding
data InvitedBy = IBContact {byContactId :: Int64} | IBUser | IBUnknown data InvitedBy = IBContact {byContactId :: Int64} | IBUser | IBUnknown
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON InvitedBy where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "IB"
instance ToJSON InvitedBy where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "IB"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "IB"
toInvitedBy :: Int64 -> Maybe Int64 -> InvitedBy toInvitedBy :: Int64 -> Maybe Int64 -> InvitedBy
toInvitedBy userCtId (Just ctId) toInvitedBy userCtId (Just ctId)
@ -803,9 +730,7 @@ instance ToJSON GroupMemberRole where
data GroupMemberSettings = GroupMemberSettings data GroupMemberSettings = GroupMemberSettings
{ showMessages :: Bool { showMessages :: Bool
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupMemberSettings where toEncoding = J.genericToEncoding J.defaultOptions
defaultMemberSettings :: GroupMemberSettings defaultMemberSettings :: GroupMemberSettings
defaultMemberSettings = GroupMemberSettings {showMessages = True} defaultMemberSettings = GroupMemberSettings {showMessages = True}
@ -986,9 +911,7 @@ data SndFileTransfer = SndFileTransfer
fileDescrId :: Maybe Int64, fileDescrId :: Maybe Int64,
fileInline :: Maybe InlineFileMode fileInline :: Maybe InlineFileMode
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON SndFileTransfer where toEncoding = J.genericToEncoding J.defaultOptions
sndFileTransferConnId :: SndFileTransfer -> ConnId sndFileTransferConnId :: SndFileTransfer -> ConnId
sndFileTransferConnId SndFileTransfer {agentConnId = AgentConnId acId} = acId sndFileTransferConnId SndFileTransfer {agentConnId = AgentConnId acId} = acId
@ -1003,24 +926,10 @@ data FileInvitation = FileInvitation
fileInline :: Maybe InlineFileMode, fileInline :: Maybe InlineFileMode,
fileDescr :: Maybe FileDescr fileDescr :: Maybe FileDescr
} }
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance ToJSON FileInvitation where
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
instance FromJSON FileInvitation where
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
data FileDescr = FileDescr {fileDescrText :: Text, fileDescrPartNo :: Int, fileDescrComplete :: Bool} data FileDescr = FileDescr {fileDescrText :: Text, fileDescrPartNo :: Int, fileDescrComplete :: Bool}
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance ToJSON FileDescr where
toEncoding = J.genericToEncoding J.defaultOptions
toJSON = J.genericToJSON J.defaultOptions
instance FromJSON FileDescr where
parseJSON = J.genericParseJSON J.defaultOptions
xftpFileInvitation :: FilePath -> Integer -> FileDescr -> FileInvitation xftpFileInvitation :: FilePath -> Integer -> FileDescr -> FileInvitation
xftpFileInvitation fileName fileSize fileDescr = xftpFileInvitation fileName fileSize fileDescr =
@ -1036,7 +945,7 @@ xftpFileInvitation fileName fileSize fileDescr =
data InlineFileMode data InlineFileMode
= IFMOffer -- file will be sent inline once accepted = IFMOffer -- file will be sent inline once accepted
| IFMSent -- file is sent inline without acceptance | IFMSent -- file is sent inline without acceptance
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance TextEncoding InlineFileMode where instance TextEncoding InlineFileMode where
textEncode = \case textEncode = \case
@ -1072,18 +981,14 @@ data RcvFileTransfer = RcvFileTransfer
-- SMP files are encrypted after all chunks are received -- SMP files are encrypted after all chunks are received
cryptoArgs :: Maybe CryptoFileArgs cryptoArgs :: Maybe CryptoFileArgs
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON RcvFileTransfer where toEncoding = J.genericToEncoding J.defaultOptions
data XFTPRcvFile = XFTPRcvFile data XFTPRcvFile = XFTPRcvFile
{ rcvFileDescription :: RcvFileDescr, { rcvFileDescription :: RcvFileDescr,
agentRcvFileId :: Maybe AgentRcvFileId, agentRcvFileId :: Maybe AgentRcvFileId,
agentRcvFileDeleted :: Bool agentRcvFileDeleted :: Bool
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON XFTPRcvFile where toEncoding = J.genericToEncoding J.defaultOptions
data RcvFileDescr = RcvFileDescr data RcvFileDescr = RcvFileDescr
{ fileDescrId :: Int64, { fileDescrId :: Int64,
@ -1091,9 +996,7 @@ data RcvFileDescr = RcvFileDescr
fileDescrPartNo :: Int, fileDescrPartNo :: Int,
fileDescrComplete :: Bool fileDescrComplete :: Bool
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON RcvFileDescr where toEncoding = J.genericToEncoding J.defaultOptions
data RcvFileStatus data RcvFileStatus
= RFSNew = RFSNew
@ -1101,14 +1004,7 @@ data RcvFileStatus
| RFSConnected RcvFileInfo | RFSConnected RcvFileInfo
| RFSComplete RcvFileInfo | RFSComplete RcvFileInfo
| RFSCancelled (Maybe RcvFileInfo) | RFSCancelled (Maybe RcvFileInfo)
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromJSON RcvFileStatus where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RFS"
instance ToJSON RcvFileStatus where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "RFS"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "RFS"
rcvFileComplete :: RcvFileStatus -> Bool rcvFileComplete :: RcvFileStatus -> Bool
rcvFileComplete = \case rcvFileComplete = \case
@ -1123,9 +1019,7 @@ data RcvFileInfo = RcvFileInfo
connId :: Maybe Int64, connId :: Maybe Int64,
agentConnId :: Maybe AgentConnId agentConnId :: Maybe AgentConnId
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON RcvFileInfo where toEncoding = J.genericToEncoding J.defaultOptions
liveRcvFileTransferInfo :: RcvFileTransfer -> Maybe RcvFileInfo liveRcvFileTransferInfo :: RcvFileTransfer -> Maybe RcvFileInfo
liveRcvFileTransferInfo RcvFileTransfer {fileStatus} = case fileStatus of liveRcvFileTransferInfo RcvFileTransfer {fileStatus} = case fileStatus of
@ -1226,14 +1120,7 @@ data FileTransfer
sndFileTransfers :: [SndFileTransfer] sndFileTransfers :: [SndFileTransfer]
} }
| FTRcv {rcvFileTransfer :: RcvFileTransfer} | FTRcv {rcvFileTransfer :: RcvFileTransfer}
deriving (Show, Generic) deriving (Show)
instance FromJSON FileTransfer where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "FT"
instance ToJSON FileTransfer where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "FT"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "FT"
data FileTransferMeta = FileTransferMeta data FileTransferMeta = FileTransferMeta
{ fileId :: FileTransferId, { fileId :: FileTransferId,
@ -1245,9 +1132,7 @@ data FileTransferMeta = FileTransferMeta
chunkSize :: Integer, chunkSize :: Integer,
cancelled :: Bool cancelled :: Bool
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON FileTransferMeta where toEncoding = J.genericToEncoding J.defaultOptions
data XFTPSndFile = XFTPSndFile data XFTPSndFile = XFTPSndFile
{ agentSndFileId :: AgentSndFileId, { agentSndFileId :: AgentSndFileId,
@ -1255,9 +1140,7 @@ data XFTPSndFile = XFTPSndFile
agentSndFileDeleted :: Bool, agentSndFileDeleted :: Bool,
cryptoArgs :: Maybe CryptoFileArgs cryptoArgs :: Maybe CryptoFileArgs
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON XFTPSndFile where toEncoding = J.genericToEncoding J.defaultOptions
fileTransferCancelled :: FileTransfer -> Bool fileTransferCancelled :: FileTransfer -> Bool
fileTransferCancelled (FTSnd FileTransferMeta {cancelled} _) = cancelled fileTransferCancelled (FTSnd FileTransferMeta {cancelled} _) = cancelled
@ -1318,7 +1201,7 @@ data Connection = Connection
authErrCounter :: Int, authErrCounter :: Int,
createdAt :: UTCTime createdAt :: UTCTime
} }
deriving (Eq, Show, Generic) deriving (Eq, Show)
connReady :: Connection -> Bool connReady :: Connection -> Bool
connReady Connection {connStatus} = connStatus == ConnReady || connStatus == ConnSndReady connReady Connection {connStatus} = connStatus == ConnReady || connStatus == ConnSndReady
@ -1330,9 +1213,7 @@ connDisabled :: Connection -> Bool
connDisabled Connection {authErrCounter} = authErrCounter >= authErrDisableCount connDisabled Connection {authErrCounter} = authErrCounter >= authErrDisableCount
data SecurityCode = SecurityCode {securityCode :: Text, verifiedAt :: UTCTime} data SecurityCode = SecurityCode {securityCode :: Text, verifiedAt :: UTCTime}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON SecurityCode where toEncoding = J.genericToEncoding J.defaultOptions
verificationCode :: ByteString -> Text verificationCode :: ByteString -> Text
verificationCode = T.pack . unwords . chunks 5 . show . os2ip verificationCode = T.pack . unwords . chunks 5 . show . os2ip
@ -1351,13 +1232,6 @@ aConnId Connection {agentConnId = AgentConnId cId} = cId
connIncognito :: Connection -> Bool connIncognito :: Connection -> Bool
connIncognito Connection {customUserProfileId} = isJust customUserProfileId connIncognito Connection {customUserProfileId} = isJust customUserProfileId
instance FromJSON Connection where
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
instance ToJSON Connection where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data PendingContactConnection = PendingContactConnection data PendingContactConnection = PendingContactConnection
{ pccConnId :: Int64, { pccConnId :: Int64,
pccAgentConnId :: AgentConnId, pccAgentConnId :: AgentConnId,
@ -1371,13 +1245,11 @@ data PendingContactConnection = PendingContactConnection
createdAt :: UTCTime, createdAt :: UTCTime,
updatedAt :: UTCTime updatedAt :: UTCTime
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
aConnId' :: PendingContactConnection -> ConnId aConnId' :: PendingContactConnection -> ConnId
aConnId' PendingContactConnection {pccAgentConnId = AgentConnId cId} = cId aConnId' PendingContactConnection {pccAgentConnId = AgentConnId cId} = cId
instance ToJSON PendingContactConnection where toEncoding = J.genericToEncoding J.defaultOptions
data ConnStatus data ConnStatus
= -- | connection is created by initiating party with agent NEW command (createConnection) = -- | connection is created by initiating party with agent NEW command (createConnection)
ConnNew ConnNew
@ -1512,7 +1384,7 @@ data NetworkStatus
| NSConnected | NSConnected
| NSDisconnected | NSDisconnected
| NSError {connectionError :: String} | NSError {connectionError :: String}
deriving (Eq, Ord, Show, Generic) deriving (Eq, Ord, Show)
netStatusStr :: NetworkStatus -> String netStatusStr :: NetworkStatus -> String
netStatusStr = \case netStatusStr = \case
@ -1521,20 +1393,11 @@ netStatusStr = \case
NSDisconnected -> "disconnected" NSDisconnected -> "disconnected"
NSError e -> "error: " <> e NSError e -> "error: " <> e
instance FromJSON NetworkStatus where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "NS"
instance ToJSON NetworkStatus where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "NS"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "NS"
data ConnNetworkStatus = ConnNetworkStatus data ConnNetworkStatus = ConnNetworkStatus
{ agentConnId :: AgentConnId, { agentConnId :: AgentConnId,
networkStatus :: NetworkStatus networkStatus :: NetworkStatus
} }
deriving (Show, Generic, FromJSON) deriving (Show)
instance ToJSON ConnNetworkStatus where toEncoding = J.genericToEncoding J.defaultOptions
type CommandId = Int64 type CommandId = Int64
@ -1548,7 +1411,7 @@ data CommandStatus
= CSCreated = CSCreated
| CSCompleted -- unused - was replaced with deleteCommand | CSCompleted -- unused - was replaced with deleteCommand
| CSError -- internal command error, e.g. not matching connection id or unexpected response, not related to agent message ERR | CSError -- internal command error, e.g. not matching connection id or unexpected response, not related to agent message ERR
deriving (Show, Generic) deriving (Show)
instance FromField CommandStatus where fromField = fromTextField_ textDecode instance FromField CommandStatus where fromField = fromTextField_ textDecode
@ -1575,7 +1438,7 @@ data CommandFunction
| CFAcceptContact | CFAcceptContact
| CFAckMessage | CFAckMessage
| CFDeleteConn -- not used | CFDeleteConn -- not used
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromField CommandFunction where fromField = fromTextField_ textDecode instance FromField CommandFunction where fromField = fromTextField_ textDecode
@ -1641,14 +1504,7 @@ data ServerCfg p = ServerCfg
tested :: Maybe Bool, tested :: Maybe Bool,
enabled :: Bool enabled :: Bool
} }
deriving (Show, Generic) deriving (Show)
instance ProtocolTypeI p => ToJSON (ServerCfg p) where
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
instance ProtocolTypeI p => FromJSON (ServerCfg p) where
parseJSON = J.genericParseJSON J.defaultOptions {J.omitNothingFields = True}
newtype ChatVersionRange = ChatVersionRange {fromChatVRange :: VersionRange} deriving (Eq, Show) newtype ChatVersionRange = ChatVersionRange {fromChatVRange :: VersionRange} deriving (Eq, Show)
@ -1674,14 +1530,95 @@ instance ToJSON JVersionRange where
toJSON (JVersionRange (VersionRange minV maxV)) = J.object ["minVersion" .= minV, "maxVersion" .= maxV] toJSON (JVersionRange (VersionRange minV maxV)) = J.object ["minVersion" .= minV, "maxVersion" .= maxV]
toEncoding (JVersionRange (VersionRange minV maxV)) = J.pairs $ "minVersion" .= minV <> "maxVersion" .= maxV toEncoding (JVersionRange (VersionRange minV maxV)) = J.pairs $ "minVersion" .= minV <> "maxVersion" .= maxV
$(JQ.deriveJSON defOpts ''UserPwdHash) $(JQ.deriveJSON defaultJSON ''UserContact)
$(JQ.deriveJSON defOpts ''User) $(JQ.deriveJSON defaultJSON ''Profile)
$(JQ.deriveJSON defOpts ''NewUser) $(JQ.deriveJSON defaultJSON ''LocalProfile)
$(JQ.deriveJSON defOpts ''UserInfo) $(JQ.deriveJSON defaultJSON ''UserContactRequest)
$(JQ.deriveJSON defOpts ''Contact) $(JQ.deriveJSON defaultJSON ''GroupProfile)
$(JQ.deriveJSON defOpts ''ContactRef) $(JQ.deriveJSON (sumTypeJSON $ dropPrefix "IB") ''InvitedBy)
$(JQ.deriveJSON defaultJSON ''GroupMemberSettings)
$(JQ.deriveJSON defaultJSON ''SecurityCode)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "NS") ''NetworkStatus)
$(JQ.deriveJSON defaultJSON ''ConnNetworkStatus)
$(JQ.deriveJSON defaultJSON ''Connection)
$(JQ.deriveJSON defaultJSON ''PendingContactConnection)
$(JQ.deriveJSON defaultJSON ''GroupMember)
$(JQ.deriveJSON (enumJSON $ dropPrefix "MF") ''MsgFilter)
$(JQ.deriveJSON defaultJSON ''ChatSettings)
$(JQ.deriveJSON defaultJSON ''GroupInfo)
$(JQ.deriveJSON defaultJSON ''Group)
$(JQ.deriveJSON defaultJSON ''GroupSummary)
instance FromField MsgFilter where fromField = fromIntField_ msgFilterIntP
instance ToField MsgFilter where toField = toField . msgFilterInt
$(JQ.deriveJSON (taggedObjectJSON $ dropPrefix "CRData") ''CReqClientData)
$(JQ.deriveJSON defaultJSON ''MemberIdRole)
$(JQ.deriveJSON defaultJSON ''GroupInvitation)
$(JQ.deriveJSON defaultJSON ''IntroInvitation)
$(JQ.deriveJSON defaultJSON ''MemberInfo)
$(JQ.deriveJSON defaultJSON ''GroupMemberRef)
$(JQ.deriveJSON defaultJSON ''FileDescr)
$(JQ.deriveJSON defaultJSON ''FileInvitation)
$(JQ.deriveJSON defaultJSON ''SndFileTransfer)
$(JQ.deriveJSON defaultJSON ''RcvFileDescr)
$(JQ.deriveJSON defaultJSON ''XFTPRcvFile)
$(JQ.deriveJSON defaultJSON ''RcvFileInfo)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "RFS") ''RcvFileStatus)
$(JQ.deriveJSON defaultJSON ''RcvFileTransfer)
$(JQ.deriveJSON defaultJSON ''XFTPSndFile)
$(JQ.deriveJSON defaultJSON ''FileTransferMeta)
$(JQ.deriveJSON (sumTypeJSON $ dropPrefix "FT") ''FileTransfer)
$(JQ.deriveJSON defaultJSON ''UserPwdHash)
$(JQ.deriveJSON defaultJSON ''User)
$(JQ.deriveJSON defaultJSON ''NewUser)
$(JQ.deriveJSON defaultJSON ''UserInfo)
$(JQ.deriveJSON defaultJSON ''Contact)
$(JQ.deriveJSON defaultJSON ''ContactRef)
instance ProtocolTypeI p => ToJSON (ServerCfg p) where
toEncoding = $(JQ.mkToEncoding defaultJSON ''ServerCfg)
toJSON = $(JQ.mkToJSON defaultJSON ''ServerCfg)
instance ProtocolTypeI p => FromJSON (ServerCfg p) where
parseJSON = $(JQ.mkParseJSON defaultJSON ''ServerCfg)

View file

@ -1,6 +1,5 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveAnyClass #-} {-# LANGUAGE DeriveAnyClass #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-} {-# LANGUAGE FlexibleInstances #-}
@ -12,6 +11,7 @@
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE StandaloneDeriving #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilyDependencies #-} {-# LANGUAGE TypeFamilyDependencies #-}
@ -24,7 +24,7 @@ module Simplex.Chat.Types.Preferences where
import Control.Applicative ((<|>)) import Control.Applicative ((<|>))
import Data.Aeson (FromJSON (..), ToJSON (..)) import Data.Aeson (FromJSON (..), ToJSON (..))
import qualified Data.Aeson as J import qualified Data.Aeson.TH as J
import qualified Data.Attoparsec.ByteString.Char8 as A import qualified Data.Attoparsec.ByteString.Char8 as A
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import Data.Maybe (fromMaybe, isJust) import Data.Maybe (fromMaybe, isJust)
@ -32,11 +32,10 @@ import Data.Text (Text)
import qualified Data.Text as T import qualified Data.Text as T
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.Records.Compat import GHC.Records.Compat
import Simplex.Chat.Types.Util import Simplex.Chat.Types.Util
import Simplex.Messaging.Encoding.String import Simplex.Messaging.Encoding.String
import Simplex.Messaging.Parsers (dropPrefix, enumJSON, fromTextField_, sumTypeJSON) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON)
import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>))
data ChatFeature data ChatFeature
@ -45,7 +44,7 @@ data ChatFeature
| CFReactions | CFReactions
| CFVoice | CFVoice
| CFCalls | CFCalls
deriving (Show, Generic) deriving (Show)
data SChatFeature (f :: ChatFeature) where data SChatFeature (f :: ChatFeature) where
SCFTimedMessages :: SChatFeature 'CFTimedMessages SCFTimedMessages :: SChatFeature 'CFTimedMessages
@ -71,13 +70,6 @@ chatFeatureNameText = \case
chatFeatureNameText' :: SChatFeature f -> Text chatFeatureNameText' :: SChatFeature f -> Text
chatFeatureNameText' = chatFeatureNameText . chatFeature chatFeatureNameText' = chatFeatureNameText . chatFeature
instance ToJSON ChatFeature where
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "CF"
toJSON = J.genericToJSON . enumJSON $ dropPrefix "CF"
instance FromJSON ChatFeature where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "CF"
allChatFeatures :: [AChatFeature] allChatFeatures :: [AChatFeature]
allChatFeatures = allChatFeatures =
[ ACF SCFTimedMessages, [ ACF SCFTimedMessages,
@ -149,17 +141,7 @@ data Preferences = Preferences
voice :: Maybe VoicePreference, voice :: Maybe VoicePreference,
calls :: Maybe CallsPreference calls :: Maybe CallsPreference
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON Preferences where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
instance ToField Preferences where
toField = toField . encodeJSON
instance FromField Preferences where
fromField = fromTextField_ decodeJSON
data GroupFeature data GroupFeature
= GFTimedMessages = GFTimedMessages
@ -168,7 +150,7 @@ data GroupFeature
| GFReactions | GFReactions
| GFVoice | GFVoice
| GFFiles | GFFiles
deriving (Show, Generic) deriving (Show)
data SGroupFeature (f :: GroupFeature) where data SGroupFeature (f :: GroupFeature) where
SGFTimedMessages :: SGroupFeature 'GFTimedMessages SGFTimedMessages :: SGroupFeature 'GFTimedMessages
@ -200,13 +182,6 @@ groupFeatureAllowed' :: GroupFeatureI f => SGroupFeature f -> FullGroupPreferenc
groupFeatureAllowed' feature prefs = groupFeatureAllowed' feature prefs =
getField @"enable" (getGroupPreference feature prefs) == FEOn getField @"enable" (getGroupPreference feature prefs) == FEOn
instance ToJSON GroupFeature where
toEncoding = J.genericToEncoding . enumJSON $ dropPrefix "GF"
toJSON = J.genericToJSON . enumJSON $ dropPrefix "GF"
instance FromJSON GroupFeature where
parseJSON = J.genericParseJSON . enumJSON $ dropPrefix "GF"
allGroupFeatures :: [AGroupFeature] allGroupFeatures :: [AGroupFeature]
allGroupFeatures = allGroupFeatures =
[ AGF SGFTimedMessages, [ AGF SGFTimedMessages,
@ -263,17 +238,7 @@ data GroupPreferences = GroupPreferences
voice :: Maybe VoiceGroupPreference, voice :: Maybe VoiceGroupPreference,
files :: Maybe FilesGroupPreference files :: Maybe FilesGroupPreference
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupPreferences where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
instance ToField GroupPreferences where
toField = toField . encodeJSON
instance FromField GroupPreferences where
fromField = fromTextField_ decodeJSON
setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences setGroupPreference :: forall f. GroupFeatureI f => SGroupFeature f -> GroupFeatureEnabled -> Maybe GroupPreferences -> GroupPreferences
setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs setGroupPreference f enable prefs_ = setGroupPreference_ f pref prefs
@ -312,9 +277,7 @@ data FullPreferences = FullPreferences
voice :: VoicePreference, voice :: VoicePreference,
calls :: CallsPreference calls :: CallsPreference
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON FullPreferences where toEncoding = J.genericToEncoding J.defaultOptions
-- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing -- full collection of group preferences defined in the app - it is used to ensure we include all preferences and to simplify processing
-- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here. -- if some of the preferences are not defined in GroupPreferences, defaults from defaultGroupPrefs are used here.
@ -326,9 +289,7 @@ data FullGroupPreferences = FullGroupPreferences
voice :: VoiceGroupPreference, voice :: VoiceGroupPreference,
files :: FilesGroupPreference files :: FilesGroupPreference
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON FullGroupPreferences where toEncoding = J.genericToEncoding J.defaultOptions
-- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences -- merged preferences of user for a given contact - they differentiate between specific preferences for the contact and global user preferences
data ContactUserPreferences = ContactUserPreferences data ContactUserPreferences = ContactUserPreferences
@ -338,30 +299,17 @@ data ContactUserPreferences = ContactUserPreferences
voice :: ContactUserPreference VoicePreference, voice :: ContactUserPreference VoicePreference,
calls :: ContactUserPreference CallsPreference calls :: ContactUserPreference CallsPreference
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
data ContactUserPreference p = ContactUserPreference data ContactUserPreference p = ContactUserPreference
{ enabled :: PrefEnabled, { enabled :: PrefEnabled,
userPreference :: ContactUserPref p, userPreference :: ContactUserPref p,
contactPreference :: p contactPreference :: p
} }
deriving (Eq, Show, Generic) deriving (Eq, Show)
data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p} data ContactUserPref p = CUPContact {preference :: p} | CUPUser {preference :: p}
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance ToJSON ContactUserPreferences where toEncoding = J.genericToEncoding J.defaultOptions
instance FromJSON p => FromJSON (ContactUserPreference p) where parseJSON = J.genericParseJSON J.defaultOptions
instance ToJSON p => ToJSON (ContactUserPreference p) where toEncoding = J.genericToEncoding J.defaultOptions
instance FromJSON p => FromJSON (ContactUserPref p) where
parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "CUP"
instance ToJSON p => ToJSON (ContactUserPref p) where
toJSON = J.genericToJSON . sumTypeJSON $ dropPrefix "CUP"
toEncoding = J.genericToEncoding . sumTypeJSON $ dropPrefix "CUP"
toChatPrefs :: FullPreferences -> Preferences toChatPrefs :: FullPreferences -> Preferences
toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} = toChatPrefs FullPreferences {timedMessages, fullDelete, reactions, voice, calls} =
@ -404,31 +352,19 @@ data TimedMessagesPreference = TimedMessagesPreference
{ allow :: FeatureAllowed, { allow :: FeatureAllowed,
ttl :: Maybe Int ttl :: Maybe Int
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON TimedMessagesPreference where
toJSON = J.genericToJSON J.defaultOptions {J.omitNothingFields = True}
toEncoding = J.genericToEncoding J.defaultOptions {J.omitNothingFields = True}
data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed} data FullDeletePreference = FullDeletePreference {allow :: FeatureAllowed}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON FullDeletePreference where toEncoding = J.genericToEncoding J.defaultOptions
data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed} data ReactionsPreference = ReactionsPreference {allow :: FeatureAllowed}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON ReactionsPreference where toEncoding = J.genericToEncoding J.defaultOptions
data VoicePreference = VoicePreference {allow :: FeatureAllowed} data VoicePreference = VoicePreference {allow :: FeatureAllowed}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON VoicePreference where toEncoding = J.genericToEncoding J.defaultOptions
data CallsPreference = CallsPreference {allow :: FeatureAllowed} data CallsPreference = CallsPreference {allow :: FeatureAllowed}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON CallsPreference where toEncoding = J.genericToEncoding J.defaultOptions
class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where class (Eq (FeaturePreference f), HasField "allow" (FeaturePreference f) FeatureAllowed) => FeatureI f where
type FeaturePreference (f :: ChatFeature) = p | p -> f type FeaturePreference (f :: ChatFeature) = p | p -> f
@ -477,47 +413,33 @@ instance FeatureI 'CFCalls where
data GroupPreference = GroupPreference data GroupPreference = GroupPreference
{enable :: GroupFeatureEnabled} {enable :: GroupFeatureEnabled}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
data TimedMessagesGroupPreference = TimedMessagesGroupPreference data TimedMessagesGroupPreference = TimedMessagesGroupPreference
{ enable :: GroupFeatureEnabled, { enable :: GroupFeatureEnabled,
ttl :: Maybe Int ttl :: Maybe Int
} }
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
data DirectMessagesGroupPreference = DirectMessagesGroupPreference data DirectMessagesGroupPreference = DirectMessagesGroupPreference
{enable :: GroupFeatureEnabled} {enable :: GroupFeatureEnabled}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
data FullDeleteGroupPreference = FullDeleteGroupPreference data FullDeleteGroupPreference = FullDeleteGroupPreference
{enable :: GroupFeatureEnabled} {enable :: GroupFeatureEnabled}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
data ReactionsGroupPreference = ReactionsGroupPreference data ReactionsGroupPreference = ReactionsGroupPreference
{enable :: GroupFeatureEnabled} {enable :: GroupFeatureEnabled}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
data VoiceGroupPreference = VoiceGroupPreference data VoiceGroupPreference = VoiceGroupPreference
{enable :: GroupFeatureEnabled} {enable :: GroupFeatureEnabled}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
data FilesGroupPreference = FilesGroupPreference data FilesGroupPreference = FilesGroupPreference
{enable :: GroupFeatureEnabled} {enable :: GroupFeatureEnabled}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON GroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
instance ToJSON TimedMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
instance ToJSON DirectMessagesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
instance ToJSON ReactionsGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
instance ToJSON FullDeleteGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
instance ToJSON VoiceGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
instance ToJSON FilesGroupPreference where toEncoding = J.genericToEncoding J.defaultOptions
class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where class (Eq (GroupFeaturePreference f), HasField "enable" (GroupFeaturePreference f) GroupFeatureEnabled) => GroupFeatureI f where
type GroupFeaturePreference (f :: GroupFeature) = p | p -> f type GroupFeaturePreference (f :: GroupFeature) = p | p -> f
@ -619,7 +541,7 @@ data FeatureAllowed
= FAAlways -- allow unconditionally = FAAlways -- allow unconditionally
| FAYes -- allow, if peer allows it | FAYes -- allow, if peer allows it
| FANo -- do not allow | FANo -- do not allow
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode instance FromField FeatureAllowed where fromField = fromBlobField_ strDecode
@ -645,7 +567,7 @@ instance ToJSON FeatureAllowed where
toEncoding = strToJEncoding toEncoding = strToJEncoding
data GroupFeatureEnabled = FEOn | FEOff data GroupFeatureEnabled = FEOn | FEOff
deriving (Eq, Show, Generic) deriving (Eq, Show)
instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode instance FromField GroupFeatureEnabled where fromField = fromBlobField_ strDecode
@ -718,11 +640,7 @@ toGroupPreferences groupPreferences =
pref f = Just $ getGroupPreference f groupPreferences pref f = Just $ getGroupPreference f groupPreferences
data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool} data PrefEnabled = PrefEnabled {forUser :: Bool, forContact :: Bool}
deriving (Eq, Show, Generic, FromJSON) deriving (Eq, Show)
instance ToJSON PrefEnabled where
toJSON = J.genericToJSON J.defaultOptions
toEncoding = J.genericToEncoding J.defaultOptions
prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled prefEnabled :: FeatureI f => Bool -> FeaturePreference f -> FeaturePreference f -> PrefEnabled
prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of prefEnabled asymmetric user contact = case (getField @"allow" user, getField @"allow" contact) of
@ -784,3 +702,69 @@ getContactUserPreference f ps = case f of
SCFReactions -> ps.reactions SCFReactions -> ps.reactions
SCFVoice -> ps.voice SCFVoice -> ps.voice
SCFCalls -> ps.calls SCFCalls -> ps.calls
$(J.deriveJSON (enumJSON $ dropPrefix "CF") ''ChatFeature)
$(J.deriveJSON (enumJSON $ dropPrefix "GF") ''GroupFeature)
$(J.deriveJSON defaultJSON ''TimedMessagesPreference)
$(J.deriveJSON defaultJSON ''FullDeletePreference)
$(J.deriveJSON defaultJSON ''ReactionsPreference)
$(J.deriveJSON defaultJSON ''VoicePreference)
$(J.deriveJSON defaultJSON ''CallsPreference)
$(J.deriveJSON defaultJSON ''Preferences)
instance ToField Preferences where
toField = toField . encodeJSON
instance FromField Preferences where
fromField = fromTextField_ decodeJSON
$(J.deriveJSON defaultJSON ''GroupPreference)
$(J.deriveJSON defaultJSON ''TimedMessagesGroupPreference)
$(J.deriveJSON defaultJSON ''DirectMessagesGroupPreference)
$(J.deriveJSON defaultJSON ''ReactionsGroupPreference)
$(J.deriveJSON defaultJSON ''FullDeleteGroupPreference)
$(J.deriveJSON defaultJSON ''VoiceGroupPreference)
$(J.deriveJSON defaultJSON ''FilesGroupPreference)
$(J.deriveJSON defaultJSON ''GroupPreferences)
instance ToField GroupPreferences where
toField = toField . encodeJSON
instance FromField GroupPreferences where
fromField = fromTextField_ decodeJSON
$(J.deriveJSON defaultJSON ''FullPreferences)
$(J.deriveJSON defaultJSON ''FullGroupPreferences)
$(J.deriveJSON defaultJSON ''PrefEnabled)
instance FromJSON p => FromJSON (ContactUserPref p) where
parseJSON = $(J.mkParseJSON (sumTypeJSON $ dropPrefix "CUP") ''ContactUserPref)
instance ToJSON p => ToJSON (ContactUserPref p) where
toJSON = $(J.mkToJSON (sumTypeJSON $ dropPrefix "CUP") ''ContactUserPref)
toEncoding = $(J.mkToEncoding (sumTypeJSON $ dropPrefix "CUP") ''ContactUserPref)
instance FromJSON p => FromJSON (ContactUserPreference p) where
parseJSON = $(J.mkParseJSON defaultJSON ''ContactUserPreference)
instance ToJSON p => ToJSON (ContactUserPreference p) where
toJSON = $(J.mkToJSON defaultJSON ''ContactUserPreference)
toEncoding = $(J.mkToEncoding defaultJSON ''ContactUserPreference)
$(J.deriveJSON defaultJSON ''ContactUserPreferences)

View file

@ -28,6 +28,3 @@ fromBlobField_ p = \case
Right k -> Ok k Right k -> Ok k
Left e -> returnError ConversionFailed f ("could not parse field: " ++ e) Left e -> returnError ConversionFailed f ("could not parse field: " ++ e)
f -> returnError ConversionFailed f "expecting SQLBlob column type" f -> returnError ConversionFailed f "expecting SQLBlob column type"
defOpts :: J.Options
defOpts = J.defaultOptions {J.omitNothingFields = True}

View file

@ -1,5 +1,4 @@
{-# LANGUAGE DataKinds #-} {-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE GADTs #-} {-# LANGUAGE GADTs #-}
{-# LANGUAGE LambdaCase #-} {-# LANGUAGE LambdaCase #-}
@ -7,12 +6,13 @@
{-# LANGUAGE OverloadedRecordDot #-} {-# LANGUAGE OverloadedRecordDot #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeApplications #-} {-# LANGUAGE TypeApplications #-}
module Simplex.Chat.View where module Simplex.Chat.View where
import Data.Aeson (ToJSON)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Char8 as B
import qualified Data.ByteString.Lazy.Char8 as LB import qualified Data.ByteString.Lazy.Char8 as LB
import Data.Char (isSpace, toUpper) import Data.Char (isSpace, toUpper)
@ -31,7 +31,6 @@ import Data.Time (LocalTime (..), TimeOfDay (..), TimeZone (..), utcToLocalTime)
import Data.Time.Calendar (addDays) import Data.Time.Calendar (addDays)
import Data.Time.Clock (UTCTime) import Data.Time.Clock (UTCTime)
import Data.Time.Format (defaultTimeLocale, formatTime) import Data.Time.Format (defaultTimeLocale, formatTime)
import GHC.Generics (Generic)
import qualified Network.HTTP.Types as Q import qualified Network.HTTP.Types as Q
import Numeric (showFFloat) import Numeric (showFFloat)
import Simplex.Chat (defaultChatConfig, maxImageSize) import Simplex.Chat (defaultChatConfig, maxImageSize)
@ -66,6 +65,13 @@ import System.Console.ANSI.Types
type CurrentTime = UTCTime type CurrentTime = UTCTime
data WCallCommand
= WCCallStart {media :: CallMedia, aesKey :: Maybe String, useWorker :: Bool}
| WCCallOffer {offer :: Text, iceCandidates :: Text, media :: CallMedia, aesKey :: Maybe String, useWorker :: Bool}
| WCCallAnswer {answer :: Text, iceCandidates :: Text}
$(JQ.deriveToJSON (taggedObjectJSON $ dropPrefix "WCCall") ''WCallCommand)
serializeChatResponse :: (Maybe RemoteHostId, Maybe User) -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> String serializeChatResponse :: (Maybe RemoteHostId, Maybe User) -> CurrentTime -> TimeZone -> Maybe RemoteHostId -> ChatResponse -> String
serializeChatResponse user_ ts tz remoteHost_ = unlines . map unStyle . responseToView user_ defaultChatConfig False ts tz remoteHost_ serializeChatResponse user_ ts tz remoteHost_ = unlines . map unStyle . responseToView user_ defaultChatConfig False ts tz remoteHost_
@ -1633,16 +1639,6 @@ supporedBrowsers callType
| encryptedCall callType = " (only Chrome and Safari support e2e encryption for WebRTC, Safari may require enabling WebRTC insertable streams)" | encryptedCall callType = " (only Chrome and Safari support e2e encryption for WebRTC, Safari may require enabling WebRTC insertable streams)"
| otherwise = "" | otherwise = ""
data WCallCommand
= WCCallStart {media :: CallMedia, aesKey :: Maybe String, useWorker :: Bool}
| WCCallOffer {offer :: Text, iceCandidates :: Text, media :: CallMedia, aesKey :: Maybe String, useWorker :: Bool}
| WCCallAnswer {answer :: Text, iceCandidates :: Text}
deriving (Generic)
instance ToJSON WCallCommand where
toEncoding = J.genericToEncoding . taggedObjectJSON $ dropPrefix "WCCall"
toJSON = J.genericToJSON . taggedObjectJSON $ dropPrefix "WCCall"
viewVersionInfo :: ChatLogLevel -> CoreVersionInfo -> [StyledString] viewVersionInfo :: ChatLogLevel -> CoreVersionInfo -> [StyledString]
viewVersionInfo logLevel CoreVersionInfo {version, simplexmqVersion, simplexmqCommit} = viewVersionInfo logLevel CoreVersionInfo {version, simplexmqVersion, simplexmqCommit} =
map plain $ map plain $

View file

@ -49,7 +49,7 @@ extra-deps:
# - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561 # - simplexmq-1.0.0@sha256:34b2004728ae396e3ae449cd090ba7410781e2b3cefc59259915f4ca5daa9ea8,8561
# - ../simplexmq # - ../simplexmq
- github: simplex-chat/simplexmq - github: simplex-chat/simplexmq
commit: d920a2504b6d4653748da7d297cb13cd0a0f1f48 commit: 511d793b927b1e2f12999e0829718671b3a8f0cb
- github: kazu-yamamoto/http2 - github: kazu-yamamoto/http2
commit: 804fa283f067bd3fd89b8c5f8d25b3047813a517 commit: 804fa283f067bd3fd89b8c5f8d25b3047813a517
# - ../direct-sqlcipher # - ../direct-sqlcipher

View file

@ -1,6 +1,7 @@
{-# LANGUAGE CPP #-} {-# LANGUAGE CPP #-}
{-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TemplateHaskell #-}
{-# OPTIONS_GHC -fno-warn-orphans #-} {-# OPTIONS_GHC -fno-warn-orphans #-}
@ -9,8 +10,9 @@ module MobileTests where
import ChatTests.Utils import ChatTests.Utils
import Control.Monad.Except import Control.Monad.Except
import Crypto.Random (getRandomBytes) import Crypto.Random (getRandomBytes)
import Data.Aeson (FromJSON (..)) import Data.Aeson (FromJSON)
import qualified Data.Aeson as J import qualified Data.Aeson as J
import qualified Data.Aeson.TH as JQ
import Data.ByteString (ByteString) import Data.ByteString (ByteString)
import qualified Data.ByteString as B import qualified Data.ByteString as B
import qualified Data.ByteString.Char8 as BS import qualified Data.ByteString.Char8 as BS
@ -256,9 +258,11 @@ testMediaCApi _ = do
(f cKeyStr ptr cLen >>= peekCAString) `shouldReturn` "" (f cKeyStr ptr cLen >>= peekCAString) `shouldReturn` ""
getByteString ptr cLen getByteString ptr cLen
instance FromJSON WriteFileResult where parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "WF" instance FromJSON WriteFileResult where
parseJSON = $(JQ.mkParseJSON (sumTypeJSON $ dropPrefix "WF") ''WriteFileResult)
instance FromJSON ReadFileResult where parseJSON = J.genericParseJSON . sumTypeJSON $ dropPrefix "RF" instance FromJSON ReadFileResult where
parseJSON = $(JQ.mkParseJSON (sumTypeJSON $ dropPrefix "RF") ''ReadFileResult)
testFileCApi :: FilePath -> FilePath -> IO () testFileCApi :: FilePath -> FilePath -> IO ()
testFileCApi fileName tmp = do testFileCApi fileName tmp = do