never executed always true always false
    1 module PureClaw.Channels.Signal
    2   ( -- * Signal channel
    3     SignalChannel (..)
    4   , SignalConfig (..)
    5   , mkSignalChannel
    6     -- * Message parsing
    7   , parseSignalEnvelope
    8   , SignalEnvelope (..)
    9   , SignalDataMessage (..)
   10   ) where
   11 
   12 import Control.Concurrent.STM
   13 import Data.Aeson
   14 import Data.Text (Text)
   15 import Data.Text qualified as T
   16 
   17 import PureClaw.Channels.Class
   18 import PureClaw.Core.Errors
   19 import PureClaw.Core.Types
   20 import PureClaw.Handles.Channel
   21 import PureClaw.Handles.Log
   22 
   23 -- | Configuration for Signal channel.
   24 newtype SignalConfig = SignalConfig
   25   { _sc_account :: Text
   26   }
   27   deriving stock (Show, Eq)
   28 
   29 -- | A Signal channel backed by a message queue. Messages from signal-cli
   30 -- are pushed into the queue and the agent loop pulls them out via 'receive'.
   31 data SignalChannel = SignalChannel
   32   { _sch_config :: SignalConfig
   33   , _sch_inbox  :: TQueue SignalEnvelope
   34   , _sch_log    :: LogHandle
   35   }
   36 
   37 -- | Create a Signal channel with an empty inbox.
   38 mkSignalChannel :: SignalConfig -> LogHandle -> IO SignalChannel
   39 mkSignalChannel config lh = do
   40   inbox <- newTQueueIO
   41   pure SignalChannel
   42     { _sch_config = config
   43     , _sch_inbox  = inbox
   44     , _sch_log    = lh
   45     }
   46 
   47 instance Channel SignalChannel where
   48   toHandle sc = ChannelHandle
   49     { _ch_receive   = receiveEnvelope sc
   50     , _ch_send      = sendSignalMessage sc
   51     , _ch_sendError = sendSignalError sc
   52     }
   53 
   54 -- | Block until a Signal envelope arrives in the queue.
   55 receiveEnvelope :: SignalChannel -> IO IncomingMessage
   56 receiveEnvelope sc = do
   57   envelope <- atomically $ readTQueue (_sch_inbox sc)
   58   let sender = _se_source envelope
   59       content = maybe "" _sdm_message (_se_dataMessage envelope)
   60   pure IncomingMessage
   61     { _im_userId  = UserId sender
   62     , _im_content = content
   63     }
   64 
   65 -- | Send a message via Signal. In a full implementation this would
   66 -- invoke signal-cli. For now, logs the outgoing message.
   67 sendSignalMessage :: SignalChannel -> OutgoingMessage -> IO ()
   68 sendSignalMessage sc msg =
   69   _lh_logInfo (_sch_log sc) $ "Signal send: " <> _om_content msg
   70 
   71 -- | Send an error via Signal.
   72 sendSignalError :: SignalChannel -> PublicError -> IO ()
   73 sendSignalError sc err =
   74   _lh_logWarn (_sch_log sc) $ "Signal error: " <> T.pack (show err)
   75 
   76 -- | A Signal envelope (simplified JSON-RPC format from signal-cli).
   77 data SignalEnvelope = SignalEnvelope
   78   { _se_source      :: Text
   79   , _se_timestamp   :: Int
   80   , _se_dataMessage :: Maybe SignalDataMessage
   81   }
   82   deriving stock (Show, Eq)
   83 
   84 -- | A Signal data message.
   85 data SignalDataMessage = SignalDataMessage
   86   { _sdm_message   :: Text
   87   , _sdm_timestamp :: Int
   88   }
   89   deriving stock (Show, Eq)
   90 
   91 instance FromJSON SignalEnvelope where
   92   parseJSON = withObject "SignalEnvelope" $ \o ->
   93     SignalEnvelope <$> o .: "source" <*> o .: "timestamp" <*> o .:? "dataMessage"
   94 
   95 instance FromJSON SignalDataMessage where
   96   parseJSON = withObject "SignalDataMessage" $ \o ->
   97     SignalDataMessage <$> o .: "message" <*> o .: "timestamp"
   98 
   99 -- | Parse a JSON value as a Signal envelope.
  100 parseSignalEnvelope :: Value -> Either String SignalEnvelope
  101 parseSignalEnvelope v = case fromJSON v of
  102   Error err -> Left err
  103   Success e -> Right e