1+ {-# LANGUAGE DeriveGeneric #-}
12{-# LANGUAGE GeneralizedNewtypeDeriving #-}
23{-# LANGUAGE LambdaCase #-}
34{-# LANGUAGE NumericUnderscores #-}
78-- | This module contains low-level HTTP utility
89module Network.Matrix.Internal where
910
11+ import GHC.Generics (Generic )
1012import Control.Concurrent (threadDelay )
1113import Control.Exception (Exception , throw , throwIO )
1214import Control.Monad (mzero , unless , void )
1315import Control.Monad.Catch (Handler (Handler ), MonadMask )
1416import Control.Monad.IO.Class (MonadIO , liftIO )
1517import Control.Retry (RetryStatus (.. ))
1618import qualified Control.Retry as Retry
17- import Data.Aeson (FromJSON (.. ), FromJSONKey (.. ), Value (Object ), eitherDecode , encode , object , withObject , (.:) , (.:? ) , (.=) )
19+ import Data.Aeson (FromJSON (.. ), FromJSONKey (.. ), Value (Object ), eitherDecode , encode , object , (.:) , (.= ) , defaultOptions , Options ( .. ), camelTo2 , genericParseJSON )
1820import Data.ByteString.Lazy (ByteString , toStrict )
1921import Data.Hashable (Hashable )
2022import Data.Maybe (catMaybes , fromMaybe )
@@ -27,27 +29,26 @@ import Network.HTTP.Types (Status (..))
2729import Network.HTTP.Types.Status (statusIsSuccessful )
2830import System.Environment (getEnv )
2931import System.IO (stderr )
32+ import Data.Char (isUpper )
3033
3134newtype MatrixToken = MatrixToken Text
3235newtype Username = Username { username :: Text }
3336newtype DeviceId = DeviceId { deviceId :: Text }
3437newtype InitialDeviceDisplayName = InitialDeviceDisplayName { initialDeviceDisplayName :: Text }
3538data LoginSecret = Password Text | Token Text
3639
40+ -- https://spec.matrix.org/v1.17/client-server-api/#post_matrixclientv3login
3741data LoginResponse = LoginResponse
38- { lrUserId :: Text
39- , lrAccessToken :: Text
40- , lrHomeServer :: Maybe Text
42+ { lrAccessToken :: Text
4143 , lrDeviceId :: Text
42- }
44+ , lrExpiresInMs :: Maybe Int -- Added in v1.3
45+ , lrHomeServer :: Maybe Text
46+ , lrRefreshToken :: Maybe Text -- Added in v1.3
47+ , lrUserId :: Text
48+ } deriving (Generic , Show )
4349
4450instance FromJSON LoginResponse where
45- parseJSON = withObject " LoginResponse" $ \ v -> do
46- userId' <- v .: " user_id"
47- accessToken' <- v .: " access_token"
48- homeServer' <- v .:? " home_server"
49- deviceId' <- v .: " device_id"
50- pure $ LoginResponse userId' accessToken' homeServer' deviceId'
51+ parseJSON = genericParseJSON aesonOptions
5152
5253getTokenFromEnv ::
5354 -- | The envirnoment variable name
@@ -85,9 +86,10 @@ mkRequest' baseUrl (MatrixToken token) auth path = do
8586 authHeaders =
8687 [(" Authorization" , " Bearer " <> encodeUtf8 token) | auth]
8788
88- mkLoginRequest' :: Text -> Maybe DeviceId -> Maybe InitialDeviceDisplayName -> Username -> LoginSecret -> IO HTTP. Request
89- mkLoginRequest' baseUrl did idn (Username name) secret' = do
90- let path = " /_matrix/client/r0/login"
89+ -- https://spec.matrix.org/v1.17/client-server-api/#post_matrixclientv3login
90+ mkLoginRequest' :: Text -> Maybe DeviceId -> Maybe InitialDeviceDisplayName -> Bool -> Username -> LoginSecret -> IO HTTP. Request
91+ mkLoginRequest' baseUrl did idn enableRefreshTokens (Username name) secret' = do
92+ let path = " /_matrix/client/v3/login"
9193 initRequest <- HTTP. parseUrlThrow (unpack $ baseUrl <> path)
9294
9395 let (secretKey, secret, secretType) = case secret' of
@@ -100,6 +102,7 @@ mkLoginRequest' baseUrl did idn (Username name) secret' = do
100102 object $
101103 [ " identifier" .= object [" type" .= (" m.id.user" :: Text ), " user" .= name]
102104 , secretKey .= secret
105+ , " refresh_token" .= enableRefreshTokens -- Added in v1.3
103106 , " type" .= (secretType :: Text )
104107 ]
105108 <> catMaybes
@@ -111,7 +114,7 @@ mkLoginRequest' baseUrl did idn (Username name) secret' = do
111114
112115mkLogoutRequest' :: Text -> MatrixToken -> IO HTTP. Request
113116mkLogoutRequest' baseUrl (MatrixToken token) = do
114- let path = " /_matrix/client/r0 /logout"
117+ let path = " /_matrix/client/v3 /logout"
115118 initRequest <- HTTP. parseUrlThrow (unpack $ baseUrl <> path)
116119 let headers = [(" Authorization" , encodeUtf8 $ " Bearer " <> token)]
117120 pure $ initRequest{HTTP. method = " POST" , HTTP. requestHeaders = headers}
@@ -145,20 +148,15 @@ data MatrixError = MatrixError
145148 , meError :: Text
146149 , meRetryAfterMS :: Maybe Int
147150 }
148- deriving (Show , Eq )
151+ deriving (Generic , Show , Eq )
152+
153+ instance FromJSON MatrixError where
154+ parseJSON = genericParseJSON aesonOptions
149155
150156data MatrixException = MatrixRateLimit deriving (Show )
151157
152158instance Exception MatrixException
153159
154- instance FromJSON MatrixError where
155- parseJSON (Object v) =
156- MatrixError
157- <$> v .: " errcode"
158- <*> v .: " error"
159- <*> v .:? " retry_after_ms"
160- parseJSON _ = mzero
161-
162160-- | 'MatrixIO' is a convenient type alias for server response
163161type MatrixIO a = MatrixM IO a
164162
@@ -211,3 +209,18 @@ retryWithLog limit logRetry action =
211209
212210retry :: (MonadIO m , MonadMask m ) => MatrixM m a -> MatrixM m a
213211retry = retryWithLog 7 (liftIO . hPutStrLn stderr)
212+
213+ -------------------------------------------------------------------------------
214+ -- Utils
215+
216+ aesonOptions :: Options
217+ aesonOptions = defaultOptions
218+ { fieldLabelModifier = camelTo2 ' _' . dropPrefix
219+ , omitNothingFields = True
220+ }
221+ where
222+ -- drops lower case prefix
223+ dropPrefix :: String -> String
224+ dropPrefix [] = []
225+ dropPrefix (x: xs) | isUpper x = x : xs
226+ | otherwise = dropPrefix xs
0 commit comments