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     , _ch_sendChunk    = \_ -> pure ()  -- Signal doesn't support streaming
   53     , _ch_readSecret   = ioError (userError "Vault management requires the CLI interface")
   54     , _ch_prompt       = \promptText -> do
   55         sendSignalMessage sc (OutgoingMessage promptText)
   56         _im_content <$> receiveEnvelope sc
   57     , _ch_promptSecret = \_ ->
   58         ioError (userError "Vault management requires the CLI interface")
   59     }
   60 
   61 -- | Block until a Signal envelope arrives in the queue.
   62 receiveEnvelope :: SignalChannel -> IO IncomingMessage
   63 receiveEnvelope sc = do
   64   envelope <- atomically $ readTQueue (_sch_inbox sc)
   65   let sender = _se_source envelope
   66       content = maybe "" _sdm_message (_se_dataMessage envelope)
   67   pure IncomingMessage
   68     { _im_userId  = UserId sender
   69     , _im_content = content
   70     }
   71 
   72 -- | Send a message via Signal. In a full implementation this would
   73 -- invoke signal-cli. For now, logs the outgoing message.
   74 sendSignalMessage :: SignalChannel -> OutgoingMessage -> IO ()
   75 sendSignalMessage sc msg =
   76   _lh_logInfo (_sch_log sc) $ "Signal send: " <> _om_content msg
   77 
   78 -- | Send an error via Signal.
   79 sendSignalError :: SignalChannel -> PublicError -> IO ()
   80 sendSignalError sc err =
   81   _lh_logWarn (_sch_log sc) $ "Signal error: " <> T.pack (show err)
   82 
   83 -- | A Signal envelope (simplified JSON-RPC format from signal-cli).
   84 data SignalEnvelope = SignalEnvelope
   85   { _se_source      :: Text
   86   , _se_timestamp   :: Int
   87   , _se_dataMessage :: Maybe SignalDataMessage
   88   }
   89   deriving stock (Show, Eq)
   90 
   91 -- | A Signal data message.
   92 data SignalDataMessage = SignalDataMessage
   93   { _sdm_message   :: Text
   94   , _sdm_timestamp :: Int
   95   }
   96   deriving stock (Show, Eq)
   97 
   98 instance FromJSON SignalEnvelope where
   99   parseJSON = withObject "SignalEnvelope" $ \o ->
  100     SignalEnvelope <$> o .: "source" <*> o .: "timestamp" <*> o .:? "dataMessage"
  101 
  102 instance FromJSON SignalDataMessage where
  103   parseJSON = withObject "SignalDataMessage" $ \o ->
  104     SignalDataMessage <$> o .: "message" <*> o .: "timestamp"
  105 
  106 -- | Parse a JSON value as a Signal envelope.
  107 parseSignalEnvelope :: Value -> Either String SignalEnvelope
  108 parseSignalEnvelope v = case fromJSON v of
  109   Error err -> Left err
  110   Success e -> Right e