Compare commits
9 Commits
becd3bcdb1
...
e6257d8e38
Author | SHA1 | Date |
---|---|---|
Bram | e6257d8e38 | |
Bram | 7a75bffbfb | |
Bram | 4f08dd1176 | |
Bram | 50b10c64ca | |
Bram | 3b0b3264de | |
Bram | c84bb2a1ef | |
Bram | 6e89371845 | |
Bram | 77387ab492 | |
Bram | 83043e73f4 |
|
@ -0,0 +1,163 @@
|
|||
module Internal.Api.BaseUrl.Api exposing (..)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Base URL
|
||||
|
||||
This module looks for the right homeserver address.
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Api.Chain as C
|
||||
import Internal.Api.Request as R
|
||||
import Internal.Config.Leaks as L
|
||||
import Internal.Config.Log exposing (log)
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Context as Context
|
||||
import Internal.Values.Envelope as E
|
||||
import Internal.Values.Vault as V
|
||||
|
||||
|
||||
baseUrl : BaseUrlInput -> C.TaskChain R.Error (E.EnvelopeUpdate V.VaultUpdate) ph { ph | baseUrl : () }
|
||||
baseUrl data =
|
||||
R.toChain
|
||||
{ logHttp =
|
||||
\r ->
|
||||
( E.HttpRequest r
|
||||
, String.concat
|
||||
-- TODO: Move this to Internal.Config.Text module
|
||||
[ "Matrix HTTP: "
|
||||
, r.method
|
||||
, " "
|
||||
, r.url
|
||||
]
|
||||
|> log.info
|
||||
|> List.singleton
|
||||
)
|
||||
, coder = coder
|
||||
, request =
|
||||
\context ->
|
||||
{ attributes = []
|
||||
, baseUrl = data.url
|
||||
, context = context
|
||||
, method = "GET"
|
||||
, path = [ ".well-known", "matrix", "client" ]
|
||||
}
|
||||
, toContextChange = \info -> Context.setBaseUrl info.homeserver.baseUrl
|
||||
, toUpdate =
|
||||
\info ->
|
||||
( E.SetBaseUrl info.homeserver.baseUrl
|
||||
, String.concat
|
||||
[ "Found baseURL of "
|
||||
, data.url
|
||||
, " at address "
|
||||
, info.homeserver.baseUrl
|
||||
]
|
||||
|> log.debug
|
||||
|> List.singleton
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
type alias BaseUrlInput =
|
||||
{ url : String }
|
||||
|
||||
|
||||
type alias DiscoveryInformation =
|
||||
{ homeserver : HomeserverInformation
|
||||
, identityServer : Maybe IdentityServerInformation
|
||||
}
|
||||
|
||||
|
||||
type alias HomeserverInformation =
|
||||
{ baseUrl : String }
|
||||
|
||||
|
||||
type alias IdentityServerInformation =
|
||||
{ baseUrl : String }
|
||||
|
||||
|
||||
coder : Json.Coder DiscoveryInformation
|
||||
coder =
|
||||
Json.object2
|
||||
{ name = "Discovery Information"
|
||||
, description =
|
||||
[ "Gets discovery information about the domain. The file may include additional keys, which MUST follow the Java package naming convention, e.g. com.example.myapp.property. This ensures property names are suitably namespaced for each application and reduces the risk of clashes."
|
||||
, "Note that this endpoint is not necessarily handled by the homeserver, but by another webserver, to be used for discovering the homeserver URL."
|
||||
, "https://spec.matrix.org/v1.10/client-server-api/#getwell-knownmatrixclient"
|
||||
]
|
||||
, init = DiscoveryInformation
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "m.homeserver"
|
||||
, toField = .homeserver
|
||||
, coder =
|
||||
Json.object2
|
||||
{ name = "Homeserver Information"
|
||||
, description =
|
||||
[ "Used by clients to discover homeserver information."
|
||||
]
|
||||
, init = \a _ -> { baseUrl = a }
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "base_url"
|
||||
, toField = .baseUrl
|
||||
, description =
|
||||
[ "The base URL for the homeserver for client-server connections."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = L.field
|
||||
, toField = always Nothing
|
||||
, description =
|
||||
[ "The Elm SDK always expects objects to have at least two fields."
|
||||
, "Otherwise, what's the point of hiding the value in an object?"
|
||||
, "For this reason, this empty placeholder key will always be ignored."
|
||||
]
|
||||
, coder = Json.value
|
||||
}
|
||||
)
|
||||
, description =
|
||||
[ "Used by clients to discover homeserver information."
|
||||
]
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "m.identity_server"
|
||||
, toField = .identityServer
|
||||
, coder =
|
||||
Json.object2
|
||||
{ name = "Homeserver Information"
|
||||
, description =
|
||||
[ "Used by clients to discover homeserver information."
|
||||
]
|
||||
, init = \a _ -> { baseUrl = a }
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "base_url"
|
||||
, toField = .baseUrl
|
||||
, description =
|
||||
[ "The base URL for the homeserver for client-server connections."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = L.field
|
||||
, toField = always Nothing
|
||||
, description =
|
||||
[ "The Elm SDK always expects objects to have at least two fields."
|
||||
, "Otherwise, what's the point of hiding the value in an object?"
|
||||
, "For this reason, this empty placeholder key will always be ignored."
|
||||
]
|
||||
, coder = Json.value
|
||||
}
|
||||
)
|
||||
, description =
|
||||
[ "Used by clients to discover identity server information."
|
||||
]
|
||||
}
|
||||
)
|
|
@ -1,7 +1,7 @@
|
|||
module Internal.Api.Chain exposing
|
||||
( TaskChain, CompleteChain
|
||||
, IdemChain, toTask
|
||||
, fail, succeed, andThen
|
||||
, fail, succeed, andThen, catchWith
|
||||
)
|
||||
|
||||
{-|
|
||||
|
@ -27,7 +27,7 @@ avoid leaking values passing through the API in unexpected ways.
|
|||
|
||||
## Operations
|
||||
|
||||
@docs fail, succeed, andThen
|
||||
@docs fail, succeed, andThen, catchWith
|
||||
|
||||
-}
|
||||
|
||||
|
@ -127,7 +127,7 @@ andThen f2 f1 =
|
|||
|
||||
{-| When an error has occurred, "fix" it with an artificial task chain result.
|
||||
-}
|
||||
catchWith : (err -> TaskChainPiece u a b) -> TaskChain err u a b -> TaskChain err u a b
|
||||
catchWith : (err -> TaskChainPiece u a b) -> TaskChain err u a b -> TaskChain err2 u a b
|
||||
catchWith onErr f =
|
||||
onError (\e -> succeed <| onErr e) f
|
||||
|
||||
|
@ -173,7 +173,7 @@ onError onErr f =
|
|||
|> Task.onError
|
||||
(\old ->
|
||||
{ contextChange = identity
|
||||
, logs = old.logs
|
||||
, logs = old.logs -- TODO: Log caught errors
|
||||
, messages = old.messages
|
||||
}
|
||||
|> succeed
|
||||
|
|
|
@ -0,0 +1,924 @@
|
|||
module Internal.Api.LoginWithUsernameAndPassword.Api exposing (Phantom, loginWithUsernameAndPassword)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Login
|
||||
|
||||
This module allows the user to log in using a username and password.
|
||||
|
||||
@docs Phantom, loginWithUsernameAndPassword
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Api.Api as A
|
||||
import Internal.Api.Request as R
|
||||
import Internal.Config.Leaks as L
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Context as Context
|
||||
import Internal.Values.Envelope as E
|
||||
import Internal.Values.User as User exposing (User)
|
||||
import Internal.Values.Vault as V
|
||||
import Json.Encode as E
|
||||
|
||||
|
||||
loginWithUsernameAndPassword : LoginWithUsernameAndPasswordInput -> A.TaskChain (Phantom a) (Phantom { a | accessToken : () })
|
||||
loginWithUsernameAndPassword =
|
||||
A.startWithVersion "r0.0.0" loginWithUsernameAndPasswordV1
|
||||
|> A.sameForVersion "r0.0.1"
|
||||
|> A.sameForVersion "r0.1.0"
|
||||
|> A.sameForVersion "r0.2.0"
|
||||
|> A.forVersion "r0.3.0" loginWithUsernameAndPasswordV2
|
||||
|> A.forVersion "r0.4.0" loginWithUsernameAndPasswordV3
|
||||
|> A.forVersion "r0.5.0" loginWithUsernameAndPasswordV4
|
||||
|> A.sameForVersion "r0.6.0"
|
||||
|> A.sameForVersion "r0.6.1"
|
||||
|> A.forVersion "v1.1" loginWithUsernameAndPasswordV5
|
||||
|> A.sameForVersion "v1.2"
|
||||
|> A.forVersion "v1.3" loginWithUsernameAndPasswordV6
|
||||
|> A.forVersion "v1.4" loginWithUsernameAndPasswordV7
|
||||
|> A.sameForVersion "v1.5"
|
||||
|> A.sameForVersion "v1.6"
|
||||
|> A.sameForVersion "v1.7"
|
||||
|> A.sameForVersion "v1.8"
|
||||
|> A.sameForVersion "v1.9"
|
||||
|> A.sameForVersion "v1.10"
|
||||
|> A.versionChain
|
||||
|
||||
|
||||
type alias Phantom a =
|
||||
{ a | baseUrl : (), versions : () }
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordInput =
|
||||
{ deviceId : Maybe String
|
||||
, enableRefreshToken : Maybe Bool
|
||||
, initialDeviceDisplayName : Maybe String
|
||||
, password : String
|
||||
, username : String
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordInputV1 a =
|
||||
{ a
|
||||
| password : String
|
||||
, username : String
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordInputV2 a =
|
||||
{ a
|
||||
| deviceId : Maybe String
|
||||
, initialDeviceDisplayName : Maybe String
|
||||
, password : String
|
||||
, username : String
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordInputV3 a =
|
||||
{ a
|
||||
| deviceId : Maybe String
|
||||
, enableRefreshToken : Maybe Bool
|
||||
, initialDeviceDisplayName : Maybe String
|
||||
, password : String
|
||||
, username : String
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordOutputV1 =
|
||||
{ accessToken : String -- Even though it is not required, we do not want it to be omitted.
|
||||
, homeserver : String
|
||||
, refreshToken : Maybe String
|
||||
, user : Maybe User
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordOutputV2 =
|
||||
{ accessToken : String -- Even though it is not required, we do not want it to be omitted.
|
||||
, deviceId : Maybe String
|
||||
, homeserver : String
|
||||
, user : Maybe User
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordOutputV3 =
|
||||
{ accessToken : String -- Even though it is not required, we do not want it to be omitted.
|
||||
, deviceId : Maybe String
|
||||
, homeserver : Maybe String
|
||||
, user : Maybe User
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordOutputV4 =
|
||||
{ accessToken : String -- Even though it is not required, we do not want it to be omitted.
|
||||
, deviceId : Maybe String
|
||||
, homeserver : Maybe String
|
||||
, user : Maybe User
|
||||
, wellKnown : Maybe DiscoveryInformationV1
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordOutputV5 =
|
||||
{ accessToken : String -- Even though it is not required, we do not want it to be omitted.
|
||||
, deviceId : Maybe String
|
||||
, expiresInMs : Maybe Int
|
||||
, homeserver : Maybe String
|
||||
, refreshToken : Maybe String
|
||||
, user : Maybe User
|
||||
, wellKnown : Maybe DiscoveryInformationV1
|
||||
}
|
||||
|
||||
|
||||
type alias LoginWithUsernameAndPasswordOutputV6 =
|
||||
{ accessToken : String
|
||||
, deviceId : String
|
||||
, expiresInMs : Maybe Int
|
||||
, homeserver : Maybe String
|
||||
, refreshToken : Maybe String
|
||||
, user : User
|
||||
, wellKnown : Maybe DiscoveryInformationV1
|
||||
}
|
||||
|
||||
|
||||
type alias DiscoveryInformationV1 =
|
||||
{ homeserver : HomeserverInformation
|
||||
, identityServer : Maybe IdentityServerInformation
|
||||
}
|
||||
|
||||
|
||||
type alias HomeserverInformation =
|
||||
{ baseUrl : String }
|
||||
|
||||
|
||||
type alias IdentityServerInformation =
|
||||
{ baseUrl : String }
|
||||
|
||||
|
||||
type alias PhantomV1 a =
|
||||
{ a | baseUrl : (), now : () }
|
||||
|
||||
|
||||
loginWithUsernameAndPasswordV1 : LoginWithUsernameAndPasswordInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 { a | accessToken : () })
|
||||
loginWithUsernameAndPasswordV1 { username, password } context =
|
||||
A.request
|
||||
{ attributes =
|
||||
[ R.bodyString "password" password
|
||||
, R.bodyString "type" "m.login.password"
|
||||
, R.bodyString "user" username
|
||||
, R.onStatusCode 400 "M_UNKNOWN"
|
||||
, R.onStatusCode 403 "M_FORBIDDEN"
|
||||
]
|
||||
, coder = coderV1
|
||||
, method = "POST"
|
||||
, path = [ "_matrix", "client", "r0", "login" ]
|
||||
, contextChange =
|
||||
\out -> Context.setAccessToken out.accessToken
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More
|
||||
[ E.SetAccessToken
|
||||
{ created = Context.getNow context
|
||||
, expiryMs = Nothing
|
||||
, lastUsed = Context.getNow context
|
||||
, refresh = out.refreshToken
|
||||
, value = out.accessToken
|
||||
}
|
||||
, out.user
|
||||
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|
||||
|> E.Optional
|
||||
]
|
||||
, []
|
||||
)
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
loginWithUsernameAndPasswordV2 : LoginWithUsernameAndPasswordInputV2 i -> A.TaskChain (PhantomV1 a) (PhantomV1 { a | accessToken : () })
|
||||
loginWithUsernameAndPasswordV2 { deviceId, initialDeviceDisplayName, username, password } context =
|
||||
A.request
|
||||
{ attributes =
|
||||
[ R.bodyOpString "device_id" deviceId
|
||||
, R.bodyOpString "initial_device_display_name" initialDeviceDisplayName
|
||||
, R.bodyString "password" password
|
||||
, R.bodyString "type" "m.login.password"
|
||||
, R.bodyString "user" username
|
||||
, R.onStatusCode 400 "M_UNKNOWN"
|
||||
, R.onStatusCode 403 "M_FORBIDDEN"
|
||||
, R.onStatusCode 429 "string" -- Yup. That's what it says.
|
||||
]
|
||||
, coder = coderV2
|
||||
, method = "POST"
|
||||
, path = [ "_matrix", "client", "r0", "login" ]
|
||||
, contextChange =
|
||||
\out -> Context.setAccessToken out.accessToken
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More
|
||||
[ E.SetAccessToken
|
||||
{ created = Context.getNow context
|
||||
, expiryMs = Nothing
|
||||
, lastUsed = Context.getNow context
|
||||
, refresh = Nothing
|
||||
, value = out.accessToken
|
||||
}
|
||||
, out.user
|
||||
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|
||||
|> E.Optional
|
||||
, out.deviceId
|
||||
|> Maybe.map E.SetDeviceId
|
||||
|> E.Optional
|
||||
]
|
||||
, []
|
||||
)
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
loginWithUsernameAndPasswordV3 : LoginWithUsernameAndPasswordInputV2 i -> A.TaskChain (PhantomV1 a) (PhantomV1 { a | accessToken : () })
|
||||
loginWithUsernameAndPasswordV3 { deviceId, initialDeviceDisplayName, username, password } context =
|
||||
A.request
|
||||
{ attributes =
|
||||
[ R.bodyOpString "address" Nothing
|
||||
, R.bodyOpString "device_id" deviceId
|
||||
, R.bodyValue "identifier"
|
||||
(E.object
|
||||
[ ( "type", E.string "m.id.user" )
|
||||
, ( "user", E.string username )
|
||||
]
|
||||
)
|
||||
, R.bodyOpString "initial_device_display_name" initialDeviceDisplayName
|
||||
, R.bodyString "password" password
|
||||
, R.bodyString "type" "m.login.password"
|
||||
, R.bodyString "user" username
|
||||
, R.onStatusCode 400 "M_UNKNOWN"
|
||||
, R.onStatusCode 403 "M_FORBIDDEN"
|
||||
, R.onStatusCode 429 "M_LIMIT_EXCEEDED"
|
||||
]
|
||||
, coder = coderV3
|
||||
, method = "POST"
|
||||
, path = [ "_matrix", "client", "r0", "login" ]
|
||||
, contextChange =
|
||||
\out -> Context.setAccessToken out.accessToken
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More
|
||||
[ E.SetAccessToken
|
||||
{ created = Context.getNow context
|
||||
, expiryMs = Nothing
|
||||
, lastUsed = Context.getNow context
|
||||
, refresh = Nothing
|
||||
, value = out.accessToken
|
||||
}
|
||||
, out.user
|
||||
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|
||||
|> E.Optional
|
||||
, out.deviceId
|
||||
|> Maybe.map E.SetDeviceId
|
||||
|> E.Optional
|
||||
]
|
||||
, []
|
||||
)
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
loginWithUsernameAndPasswordV4 : LoginWithUsernameAndPasswordInputV2 i -> A.TaskChain (PhantomV1 a) (PhantomV1 { a | accessToken : () })
|
||||
loginWithUsernameAndPasswordV4 { deviceId, initialDeviceDisplayName, username, password } context =
|
||||
A.request
|
||||
{ attributes =
|
||||
[ R.bodyOpString "address" Nothing
|
||||
, R.bodyOpString "device_id" deviceId
|
||||
, R.bodyValue "identifier"
|
||||
(E.object
|
||||
[ ( "type", E.string "m.id.user" )
|
||||
, ( "user", E.string username )
|
||||
]
|
||||
)
|
||||
, R.bodyOpString "initial_device_display_name" initialDeviceDisplayName
|
||||
, R.bodyString "password" password
|
||||
, R.bodyString "type" "m.login.password"
|
||||
, R.bodyString "user" username
|
||||
, R.onStatusCode 400 "M_UNKNOWN"
|
||||
, R.onStatusCode 403 "M_FORBIDDEN"
|
||||
, R.onStatusCode 429 "M_LIMIT_EXCEEDED"
|
||||
]
|
||||
, coder = coderV4
|
||||
, method = "POST"
|
||||
, path = [ "_matrix", "client", "r0", "login" ]
|
||||
, contextChange =
|
||||
\out -> Context.setAccessToken out.accessToken
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More
|
||||
[ E.SetAccessToken
|
||||
{ created = Context.getNow context
|
||||
, expiryMs = Nothing
|
||||
, lastUsed = Context.getNow context
|
||||
, refresh = Nothing
|
||||
, value = out.accessToken
|
||||
}
|
||||
, out.user
|
||||
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|
||||
|> E.Optional
|
||||
, out.wellKnown
|
||||
|> Maybe.map (.homeserver >> .baseUrl)
|
||||
|> Maybe.map E.SetBaseUrl
|
||||
|> E.Optional
|
||||
, out.deviceId
|
||||
|> Maybe.map E.SetDeviceId
|
||||
|> E.Optional
|
||||
]
|
||||
, []
|
||||
)
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
loginWithUsernameAndPasswordV5 : LoginWithUsernameAndPasswordInputV2 i -> A.TaskChain (PhantomV1 a) (PhantomV1 { a | accessToken : () })
|
||||
loginWithUsernameAndPasswordV5 { deviceId, initialDeviceDisplayName, username, password } context =
|
||||
A.request
|
||||
{ attributes =
|
||||
[ R.bodyOpString "address" Nothing
|
||||
, R.bodyOpString "device_id" deviceId
|
||||
, R.bodyValue "identifier"
|
||||
(E.object
|
||||
[ ( "type", E.string "m.id.user" )
|
||||
, ( "user", E.string username )
|
||||
]
|
||||
)
|
||||
, R.bodyOpString "initial_device_display_name" initialDeviceDisplayName
|
||||
, R.bodyString "password" password
|
||||
, R.bodyString "type" "m.login.password"
|
||||
, R.bodyString "user" username
|
||||
, R.onStatusCode 400 "M_UNKNOWN"
|
||||
, R.onStatusCode 403 "M_FORBIDDEN"
|
||||
, R.onStatusCode 429 "M_LIMIT_EXCEEDED"
|
||||
]
|
||||
, coder = coderV4
|
||||
, method = "POST"
|
||||
, path = [ "_matrix", "client", "v3", "login" ]
|
||||
, contextChange =
|
||||
\out -> Context.setAccessToken out.accessToken
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More
|
||||
[ E.SetAccessToken
|
||||
{ created = Context.getNow context
|
||||
, expiryMs = Nothing
|
||||
, lastUsed = Context.getNow context
|
||||
, refresh = Nothing
|
||||
, value = out.accessToken
|
||||
}
|
||||
, out.user
|
||||
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|
||||
|> E.Optional
|
||||
, out.wellKnown
|
||||
|> Maybe.map (.homeserver >> .baseUrl)
|
||||
|> Maybe.map E.SetBaseUrl
|
||||
|> E.Optional
|
||||
, out.deviceId
|
||||
|> Maybe.map E.SetDeviceId
|
||||
|> E.Optional
|
||||
]
|
||||
, []
|
||||
)
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
loginWithUsernameAndPasswordV6 : LoginWithUsernameAndPasswordInputV3 i -> A.TaskChain (PhantomV1 a) (PhantomV1 { a | accessToken : () })
|
||||
loginWithUsernameAndPasswordV6 { deviceId, enableRefreshToken, initialDeviceDisplayName, username, password } context =
|
||||
A.request
|
||||
{ attributes =
|
||||
[ R.bodyOpString "address" Nothing
|
||||
, R.bodyOpString "device_id" deviceId
|
||||
, R.bodyValue "identifier"
|
||||
(E.object
|
||||
[ ( "type", E.string "m.id.user" )
|
||||
, ( "user", E.string username )
|
||||
]
|
||||
)
|
||||
, R.bodyOpString "initial_device_display_name" initialDeviceDisplayName
|
||||
, R.bodyString "password" password
|
||||
, R.bodyOpBool "refresh_token" enableRefreshToken
|
||||
, R.bodyString "type" "m.login.password"
|
||||
, R.bodyString "user" username
|
||||
, R.onStatusCode 400 "M_UNKNOWN"
|
||||
, R.onStatusCode 403 "M_FORBIDDEN"
|
||||
, R.onStatusCode 429 "M_LIMIT_EXCEEDED"
|
||||
]
|
||||
, coder = coderV5
|
||||
, method = "POST"
|
||||
, path = [ "_matrix", "client", "v3", "login" ]
|
||||
, contextChange =
|
||||
\out -> Context.setAccessToken out.accessToken
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More
|
||||
[ E.SetAccessToken
|
||||
{ created = Context.getNow context
|
||||
, expiryMs = out.expiresInMs
|
||||
, lastUsed = Context.getNow context
|
||||
, refresh = out.refreshToken
|
||||
, value = out.accessToken
|
||||
}
|
||||
, out.user
|
||||
|> Maybe.map (V.SetUser >> E.ContentUpdate)
|
||||
|> E.Optional
|
||||
, out.wellKnown
|
||||
|> Maybe.map (.homeserver >> .baseUrl)
|
||||
|> Maybe.map E.SetBaseUrl
|
||||
|> E.Optional
|
||||
, out.deviceId
|
||||
|> Maybe.map E.SetDeviceId
|
||||
|> E.Optional
|
||||
]
|
||||
, []
|
||||
)
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
loginWithUsernameAndPasswordV7 : LoginWithUsernameAndPasswordInputV3 i -> A.TaskChain (PhantomV1 a) (PhantomV1 { a | accessToken : () })
|
||||
loginWithUsernameAndPasswordV7 { deviceId, enableRefreshToken, initialDeviceDisplayName, username, password } context =
|
||||
A.request
|
||||
{ attributes =
|
||||
[ R.bodyOpString "address" Nothing
|
||||
, R.bodyOpString "device_id" deviceId
|
||||
, R.bodyValue "identifier"
|
||||
(E.object
|
||||
[ ( "type", E.string "m.id.user" )
|
||||
, ( "user", E.string username )
|
||||
]
|
||||
)
|
||||
, R.bodyOpString "initial_device_display_name" initialDeviceDisplayName
|
||||
, R.bodyString "password" password
|
||||
, R.bodyOpBool "refresh_token" enableRefreshToken
|
||||
, R.bodyString "type" "m.login.password"
|
||||
, R.bodyString "user" username
|
||||
, R.onStatusCode 400 "M_UNKNOWN"
|
||||
, R.onStatusCode 403 "M_FORBIDDEN"
|
||||
, R.onStatusCode 429 "M_LIMIT_EXCEEDED"
|
||||
]
|
||||
, coder = coderV6
|
||||
, method = "POST"
|
||||
, path = [ "_matrix", "client", "v3", "login" ]
|
||||
, contextChange =
|
||||
\out -> Context.setAccessToken out.accessToken
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More
|
||||
[ E.SetAccessToken
|
||||
{ created = Context.getNow context
|
||||
, expiryMs = out.expiresInMs
|
||||
, lastUsed = Context.getNow context
|
||||
, refresh = out.refreshToken
|
||||
, value = out.accessToken
|
||||
}
|
||||
, E.ContentUpdate (V.SetUser out.user)
|
||||
, out.wellKnown
|
||||
|> Maybe.map (.homeserver >> .baseUrl)
|
||||
|> Maybe.map E.SetBaseUrl
|
||||
|> E.Optional
|
||||
, E.SetDeviceId out.deviceId
|
||||
]
|
||||
, []
|
||||
)
|
||||
}
|
||||
context
|
||||
|
||||
|
||||
coderV1 : Json.Coder LoginWithUsernameAndPasswordOutputV1
|
||||
coderV1 =
|
||||
Json.object4
|
||||
{ name = "Login Response"
|
||||
, description =
|
||||
[ "Authenticates the user by password, and issues an access token they can use to authorize themself in subsequent requests."
|
||||
, "https://spec.matrix.org/legacy/r0.0.0/client_server.html#post-matrix-client-r0-login"
|
||||
]
|
||||
, init = LoginWithUsernameAndPasswordOutputV1
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "access_token"
|
||||
, toField = .accessToken
|
||||
, description =
|
||||
[ "An access token for the account. This access token can then be used to authorize other requests. The access token may expire at some point, and if so, it SHOULD come with a refresh_token. There is no specific error message to indicate that a request has failed because an access token has expired; instead, if a client has reason to believe its access token is valid, and it receives an auth error, they should attempt to refresh for a new token on failure, and retry the request with the new token."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "home_server"
|
||||
, toField = .homeserver
|
||||
, description =
|
||||
[ "The hostname of the homeserver on which the account has been registered."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "refresh_token"
|
||||
, toField = .refreshToken
|
||||
, description =
|
||||
[ "A refresh_token may be exchanged for a new access_token using the /tokenrefresh API endpoint."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "user_id"
|
||||
, toField = .user
|
||||
, description =
|
||||
[ "The fully-qualified Matrix ID that has been registered."
|
||||
]
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
coderV2 : Json.Coder LoginWithUsernameAndPasswordOutputV2
|
||||
coderV2 =
|
||||
Json.object4
|
||||
{ name = "Login Response"
|
||||
, description =
|
||||
[ "Authenticates the user by password, and issues an access token they can use to authorize themself in subsequent requests."
|
||||
, "https://spec.matrix.org/legacy/client_server/r0.3.0.html#post-matrix-client-r0-login"
|
||||
]
|
||||
, init = LoginWithUsernameAndPasswordOutputV2
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "access_token"
|
||||
, toField = .accessToken
|
||||
, description =
|
||||
[ "An access token for the account. This access token can then be used to authorize other requests."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "device_id"
|
||||
, toField = .deviceId
|
||||
, description =
|
||||
[ "ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "home_server"
|
||||
, toField = .homeserver
|
||||
, description =
|
||||
[ "The hostname of the homeserver on which the account has been registered."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "user_id"
|
||||
, toField = .user
|
||||
, description =
|
||||
[ "The fully-qualified Matrix ID that has been registered."
|
||||
]
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
coderV3 : Json.Coder LoginWithUsernameAndPasswordOutputV3
|
||||
coderV3 =
|
||||
Json.object4
|
||||
{ name = "Login Response"
|
||||
, description =
|
||||
[ "Authenticates the user by password, and issues an access token they can use to authorize themself in subsequent requests."
|
||||
, "https://spec.matrix.org/legacy/client_server/r0.3.0.html#post-matrix-client-r0-login"
|
||||
]
|
||||
, init = LoginWithUsernameAndPasswordOutputV3
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "access_token"
|
||||
, toField = .accessToken
|
||||
, description =
|
||||
[ "An access token for the account. This access token can then be used to authorize other requests."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "device_id"
|
||||
, toField = .deviceId
|
||||
, description =
|
||||
[ "ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "home_server"
|
||||
, toField = .homeserver
|
||||
, description =
|
||||
[ "The hostname of the homeserver on which the account has been registered."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "user_id"
|
||||
, toField = .user
|
||||
, description =
|
||||
[ "The fully-qualified Matrix ID that has been registered."
|
||||
]
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
coderV4 : Json.Coder LoginWithUsernameAndPasswordOutputV4
|
||||
coderV4 =
|
||||
Json.object5
|
||||
{ name = "Login Response"
|
||||
, description =
|
||||
[ "Authenticates the user by password, and issues an access token they can use to authorize themself in subsequent requests."
|
||||
, "https://spec.matrix.org/legacy/client_server/r0.5.0.html#post-matrix-client-r0-login"
|
||||
]
|
||||
, init = LoginWithUsernameAndPasswordOutputV4
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "access_token"
|
||||
, toField = .accessToken
|
||||
, description =
|
||||
[ "An access token for the account. This access token can then be used to authorize other requests."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "device_id"
|
||||
, toField = .deviceId
|
||||
, description =
|
||||
[ "ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "home_server"
|
||||
, toField = .homeserver
|
||||
, description =
|
||||
[ "The hostname of the homeserver on which the account has been registered."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "user_id"
|
||||
, toField = .user
|
||||
, description =
|
||||
[ "The fully-qualified Matrix ID that has been registered."
|
||||
]
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "well_known"
|
||||
, toField = .wellKnown
|
||||
, description =
|
||||
[ "Optional client configuration provided by the server. If present, clients SHOULD use the provided object to reconfigure themselves, optionally validating the URLs within. This object takes the same form as the one returned from .well-known autodiscovery."
|
||||
]
|
||||
, coder = disoveryInformationCoderV1
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
coderV5 : Json.Coder LoginWithUsernameAndPasswordOutputV5
|
||||
coderV5 =
|
||||
Json.object7
|
||||
{ name = "Login Response"
|
||||
, description =
|
||||
[ "Authenticates the user by password, and issues an access token they can use to authorize themself in subsequent requests."
|
||||
, "https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3login"
|
||||
]
|
||||
, init = LoginWithUsernameAndPasswordOutputV5
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "access_token"
|
||||
, toField = .accessToken
|
||||
, description =
|
||||
[ "An access token for the account. This access token can then be used to authorize other requests."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "device_id"
|
||||
, toField = .deviceId
|
||||
, description =
|
||||
[ "ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "expires_in_ms"
|
||||
, toField = .expiresInMs
|
||||
, description =
|
||||
[ "The lifetime of the access token, in milliseconds. Once the access token has expired a new access token can be obtained by using the provided refresh token. If no refresh token is provided, the client will need to re-log in to obtain a new access token. If not given, the client can assume that the access token will not expire. "
|
||||
]
|
||||
, coder = Json.int
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "home_server"
|
||||
, toField = .homeserver
|
||||
, description =
|
||||
[ "The hostname of the homeserver on which the account has been registered."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "refresh_token"
|
||||
, toField = .refreshToken
|
||||
, description =
|
||||
[ "A refresh token for the account. This token can be used to obtain a new access token when it expires by calling the /refresh endpoint."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "user_id"
|
||||
, toField = .user
|
||||
, description =
|
||||
[ "The fully-qualified Matrix ID that has been registered."
|
||||
]
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "well_known"
|
||||
, toField = .wellKnown
|
||||
, description =
|
||||
[ "Optional client configuration provided by the server. If present, clients SHOULD use the provided object to reconfigure themselves, optionally validating the URLs within. This object takes the same form as the one returned from .well-known autodiscovery."
|
||||
]
|
||||
, coder = disoveryInformationCoderV1
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
coderV6 : Json.Coder LoginWithUsernameAndPasswordOutputV6
|
||||
coderV6 =
|
||||
Json.object7
|
||||
{ name = "Login Response"
|
||||
, description =
|
||||
[ "Authenticates the user by password, and issues an access token they can use to authorize themself in subsequent requests."
|
||||
, "https://spec.matrix.org/v1.3/client-server-api/#post_matrixclientv3login"
|
||||
]
|
||||
, init = LoginWithUsernameAndPasswordOutputV6
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "access_token"
|
||||
, toField = .accessToken
|
||||
, description =
|
||||
[ "An access token for the account. This access token can then be used to authorize other requests."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "device_id"
|
||||
, toField = .deviceId
|
||||
, description =
|
||||
[ "ID of the logged-in device. Will be the same as the corresponding parameter in the request, if one was specified."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "expires_in_ms"
|
||||
, toField = .expiresInMs
|
||||
, description =
|
||||
[ "The lifetime of the access token, in milliseconds. Once the access token has expired a new access token can be obtained by using the provided refresh token. If no refresh token is provided, the client will need to re-log in to obtain a new access token. If not given, the client can assume that the access token will not expire. "
|
||||
]
|
||||
, coder = Json.int
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "home_server"
|
||||
, toField = .homeserver
|
||||
, description =
|
||||
[ "The hostname of the homeserver on which the account has been registered."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "refresh_token"
|
||||
, toField = .refreshToken
|
||||
, description =
|
||||
[ "A refresh token for the account. This token can be used to obtain a new access token when it expires by calling the /refresh endpoint."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "user_id"
|
||||
, toField = .user
|
||||
, description =
|
||||
[ "The fully-qualified Matrix ID that has been registered."
|
||||
]
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "well_known"
|
||||
, toField = .wellKnown
|
||||
, description =
|
||||
[ "Optional client configuration provided by the server. If present, clients SHOULD use the provided object to reconfigure themselves, optionally validating the URLs within. This object takes the same form as the one returned from .well-known autodiscovery."
|
||||
]
|
||||
, coder = disoveryInformationCoderV1
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
disoveryInformationCoderV1 : Json.Coder DiscoveryInformationV1
|
||||
disoveryInformationCoderV1 =
|
||||
Json.object2
|
||||
{ name = "Discovery Information"
|
||||
, description =
|
||||
[ "Gets discovery information about the domain. The file may include additional keys, which MUST follow the Java package naming convention, e.g. com.example.myapp.property. This ensures property names are suitably namespaced for each application and reduces the risk of clashes."
|
||||
, "Note that this endpoint is not necessarily handled by the homeserver, but by another webserver, to be used for discovering the homeserver URL."
|
||||
, "https://spec.matrix.org/v1.10/client-server-api/#getwell-knownmatrixclient"
|
||||
]
|
||||
, init = DiscoveryInformationV1
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "m.homeserver"
|
||||
, toField = .homeserver
|
||||
, coder =
|
||||
Json.object2
|
||||
{ name = "Homeserver Information"
|
||||
, description =
|
||||
[ "Used by clients to discover homeserver information."
|
||||
]
|
||||
, init = \a _ -> { baseUrl = a }
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "base_url"
|
||||
, toField = .baseUrl
|
||||
, description =
|
||||
[ "The base URL for the homeserver for client-server connections."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = L.field
|
||||
, toField = always Nothing
|
||||
, description =
|
||||
[ "The Elm SDK always expects objects to have at least two fields."
|
||||
, "Otherwise, what's the point of hiding the value in an object?"
|
||||
, "For this reason, this empty placeholder key will always be ignored."
|
||||
]
|
||||
, coder = Json.value
|
||||
}
|
||||
)
|
||||
, description =
|
||||
[ "Used by clients to discover homeserver information."
|
||||
]
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "m.identity_server"
|
||||
, toField = .identityServer
|
||||
, coder =
|
||||
Json.object2
|
||||
{ name = "Homeserver Information"
|
||||
, description =
|
||||
[ "Used by clients to discover homeserver information."
|
||||
]
|
||||
, init = \a _ -> { baseUrl = a }
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "base_url"
|
||||
, toField = .baseUrl
|
||||
, description =
|
||||
[ "The base URL for the homeserver for client-server connections."
|
||||
]
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = L.field
|
||||
, toField = always Nothing
|
||||
, description =
|
||||
[ "The Elm SDK always expects objects to have at least two fields."
|
||||
, "Otherwise, what's the point of hiding the value in an object?"
|
||||
, "For this reason, this empty placeholder key will always be ignored."
|
||||
]
|
||||
, coder = Json.value
|
||||
}
|
||||
)
|
||||
, description =
|
||||
[ "Used by clients to discover identity server information."
|
||||
]
|
||||
}
|
||||
)
|
|
@ -0,0 +1,57 @@
|
|||
module Internal.Api.Main exposing
|
||||
( Msg
|
||||
, sendMessageEvent
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Main API module
|
||||
|
||||
This module is used as reference for getting
|
||||
|
||||
|
||||
## VaultUpdate
|
||||
|
||||
@docs Msg
|
||||
|
||||
|
||||
## Actions
|
||||
|
||||
@docs sendMessageEvent
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Api.Task as ITask exposing (Backpack)
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Context as Context
|
||||
import Internal.Values.Envelope as E
|
||||
|
||||
|
||||
type alias Msg =
|
||||
Backpack
|
||||
|
||||
|
||||
{-| Send a message event.
|
||||
-}
|
||||
sendMessageEvent :
|
||||
E.Envelope a
|
||||
->
|
||||
{ content : Json.Value
|
||||
, eventType : String
|
||||
, roomId : String
|
||||
, toMsg : Msg -> msg
|
||||
, transactionId : String
|
||||
}
|
||||
-> Cmd msg
|
||||
sendMessageEvent env data =
|
||||
ITask.run
|
||||
data.toMsg
|
||||
(ITask.sendMessageEvent
|
||||
{ content = data.content
|
||||
, eventType = data.eventType
|
||||
, roomId = data.roomId
|
||||
, transactionId = data.transactionId
|
||||
}
|
||||
)
|
||||
(Context.apiFormat env.context)
|
|
@ -0,0 +1,37 @@
|
|||
module Internal.Api.Now.Api exposing (getNow)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Now
|
||||
|
||||
Get the current time.
|
||||
|
||||
@docs getNow
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Api.Api as A
|
||||
import Internal.Config.Log exposing (log)
|
||||
import Internal.Values.Context as Context
|
||||
import Internal.Values.Envelope as E
|
||||
import Task
|
||||
import Time
|
||||
|
||||
|
||||
getNow : A.TaskChain a { a | now : () }
|
||||
getNow _ =
|
||||
Task.map
|
||||
(\now ->
|
||||
{ messages = [ E.SetNow now ]
|
||||
, logs =
|
||||
[ "Identified current time at Unix time "
|
||||
, now |> Time.posixToMillis |> String.fromInt
|
||||
]
|
||||
|> String.concat
|
||||
|> log.debug
|
||||
|> List.singleton
|
||||
, contextChange = Context.setNow now
|
||||
}
|
||||
)
|
||||
Time.now
|
|
@ -1,7 +1,7 @@
|
|||
module Internal.Api.Request exposing
|
||||
( ApiCall, ApiPlan, Attribute, callAPI, withAttributes, toChain
|
||||
, Request, Error(..)
|
||||
, accessToken, withTransactionId, timeout, onStatusCode
|
||||
, accessToken, timeout, onStatusCode
|
||||
, fullBody, bodyBool, bodyInt, bodyString, bodyValue, bodyOpBool, bodyOpInt, bodyOpString, bodyOpValue
|
||||
, queryBool, queryInt, queryString, queryOpBool, queryOpInt, queryOpString
|
||||
)
|
||||
|
@ -28,7 +28,7 @@ Sometimes, APIs might fail. As a result, you may receive an error.
|
|||
|
||||
### General attributes
|
||||
|
||||
@docs accessToken, withTransactionId, timeout, onStatusCode
|
||||
@docs accessToken, timeout, onStatusCode
|
||||
|
||||
|
||||
### Body
|
||||
|
@ -89,7 +89,6 @@ type ContextAttr
|
|||
| Header Http.Header
|
||||
| NoAttr
|
||||
| QueryParam UrlBuilder.QueryParameter
|
||||
| ReplaceInUrl String String
|
||||
| StatusCodeResponse Int ( Error, List Log )
|
||||
| Timeout Float
|
||||
|
||||
|
@ -98,6 +97,8 @@ type ContextAttr
|
|||
-}
|
||||
type Error
|
||||
= InternetException Http.Error
|
||||
| MissingUsername
|
||||
| MissingPassword
|
||||
| NoSupportedVersion
|
||||
| ServerReturnsBadJSON String
|
||||
| ServerReturnsError String Json.Value
|
||||
|
@ -374,27 +375,7 @@ getUrl : ApiCall a -> String
|
|||
getUrl { attributes, baseUrl, path } =
|
||||
UrlBuilder.crossOrigin
|
||||
baseUrl
|
||||
(path
|
||||
|> List.map
|
||||
(\p ->
|
||||
List.foldl
|
||||
(\attr cp ->
|
||||
case attr of
|
||||
ReplaceInUrl from to ->
|
||||
if from == cp then
|
||||
to
|
||||
|
||||
else
|
||||
cp
|
||||
|
||||
_ ->
|
||||
cp
|
||||
)
|
||||
p
|
||||
attributes
|
||||
)
|
||||
|> List.map Url.percentEncode
|
||||
)
|
||||
(List.map Url.percentEncode path)
|
||||
(getQueryParams attributes)
|
||||
|
||||
|
||||
|
@ -613,10 +594,3 @@ withAttributes attrs f context =
|
|||
|> List.append data.attributes
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| Attribute that requires a transaction id to be present.
|
||||
-}
|
||||
withTransactionId : Attribute { a | transaction : () }
|
||||
withTransactionId =
|
||||
Context.getTransaction >> ReplaceInUrl "txnId"
|
||||
|
|
|
@ -0,0 +1,198 @@
|
|||
module Internal.Api.SendMessageEvent.Api exposing (..)
|
||||
|
||||
{-|
|
||||
|
||||
|
||||
# Send message event
|
||||
|
||||
This module helps send message events to rooms on the Matrix API.
|
||||
|
||||
@docs Phantom
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Api.Api as A
|
||||
import Internal.Api.Request as R
|
||||
import Internal.Config.Leaks as L
|
||||
import Internal.Config.Log exposing (log)
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Envelope as E
|
||||
|
||||
|
||||
sendMessageEvent : SendMessageEventInput -> A.TaskChain (Phantom a) (Phantom a)
|
||||
sendMessageEvent =
|
||||
A.startWithVersion "r0.0.0" sendMessageEventV1
|
||||
|> A.sameForVersion "r0.0.1"
|
||||
|> A.sameForVersion "r0.1.0"
|
||||
|> A.sameForVersion "r0.2.0"
|
||||
|> A.sameForVersion "r0.3.0"
|
||||
|> A.sameForVersion "r0.4.0"
|
||||
|> A.sameForVersion "r0.5.0"
|
||||
|> A.sameForVersion "r0.6.0"
|
||||
|> A.forVersion "r0.6.1" sendMessageEventV2
|
||||
|> A.forVersion "v1.1" sendMessageEventV3
|
||||
|> A.sameForVersion "v1.2"
|
||||
|> A.sameForVersion "v1.3"
|
||||
|> A.sameForVersion "v1.4"
|
||||
|> A.sameForVersion "v1.5"
|
||||
|> A.sameForVersion "v1.6"
|
||||
|> A.sameForVersion "v1.7"
|
||||
|> A.sameForVersion "v1.8"
|
||||
|> A.sameForVersion "v1.9"
|
||||
|> A.sameForVersion "v1.10"
|
||||
|> A.sameForVersion "v1.11"
|
||||
|> A.versionChain
|
||||
|
||||
|
||||
type alias Phantom a =
|
||||
a
|
||||
|
||||
|
||||
type alias PhantomV1 a =
|
||||
{ a | accessToken : (), baseUrl : () }
|
||||
|
||||
|
||||
type alias SendMessageEventInput =
|
||||
{ content : Json.Value
|
||||
, eventType : String
|
||||
, roomId : String
|
||||
, transactionId : String
|
||||
}
|
||||
|
||||
|
||||
type alias SendMessageEventInputV1 a =
|
||||
{ a
|
||||
| content : Json.Value
|
||||
, eventType : String
|
||||
, roomId : String
|
||||
, transactionId : String
|
||||
}
|
||||
|
||||
|
||||
type alias SendMessageEventOutputV1 =
|
||||
{ eventId : Maybe String }
|
||||
|
||||
|
||||
type alias SendMessageEventOutputV2 =
|
||||
{ eventId : String }
|
||||
|
||||
|
||||
sendMessageEventV1 : SendMessageEventInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
|
||||
sendMessageEventV1 { content, eventType, roomId, transactionId } =
|
||||
A.request
|
||||
{ attributes = [ R.fullBody content ]
|
||||
, coder = coderV1
|
||||
, contextChange = always identity
|
||||
, method = "PUT"
|
||||
, path = [ "_matrix", "client", "r0", "rooms", roomId, "send", eventType, transactionId ]
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More []
|
||||
, out.eventId
|
||||
|> Maybe.map ((++) ", received event id ")
|
||||
|> Maybe.withDefault ""
|
||||
|> (++) "Sent event"
|
||||
|> log.debug
|
||||
|> List.singleton
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
sendMessageEventV2 : SendMessageEventInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
|
||||
sendMessageEventV2 { content, eventType, roomId, transactionId } =
|
||||
A.request
|
||||
{ attributes = [ R.fullBody content ]
|
||||
, coder = coderV2
|
||||
, contextChange = always identity
|
||||
, method = "PUT"
|
||||
, path = [ "_matrix", "client", "r0", "rooms", roomId, "send", eventType, transactionId ]
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More []
|
||||
, out.eventId
|
||||
|> (++) "Sent event, received event id "
|
||||
|> log.debug
|
||||
|> List.singleton
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
sendMessageEventV3 : SendMessageEventInputV1 i -> A.TaskChain (PhantomV1 a) (PhantomV1 a)
|
||||
sendMessageEventV3 { content, eventType, roomId, transactionId } =
|
||||
A.request
|
||||
{ attributes = [ R.fullBody content ]
|
||||
, coder = coderV2
|
||||
, contextChange = always identity
|
||||
, method = "PUT"
|
||||
, path = [ "_matrix", "client", "v3", "rooms", roomId, "send", eventType, transactionId ]
|
||||
, toUpdate =
|
||||
\out ->
|
||||
( E.More []
|
||||
, out.eventId
|
||||
|> (++) "Sent event, received event id "
|
||||
|> log.debug
|
||||
|> List.singleton
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
coderV1 : Json.Coder SendMessageEventOutputV1
|
||||
coderV1 =
|
||||
Json.object2
|
||||
{ name = "EventResponse"
|
||||
, description =
|
||||
[ "This endpoint is used to send a message event to a room. Message events allow access to historical events and pagination, making them suited for \"once-off\" activity in a room."
|
||||
, "The body of the request should be the content object of the event; the fields in this object will vary depending on the type of event."
|
||||
, "https://spec.matrix.org/legacy/r0.0.0/client_server.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid"
|
||||
]
|
||||
, init = always SendMessageEventOutputV1
|
||||
}
|
||||
(Json.field.optional.value
|
||||
{ fieldName = L.field
|
||||
, toField = always Nothing
|
||||
, description =
|
||||
[ "The Elm SDK always expects objects to have at least two fields."
|
||||
, "Otherwise, what's the point of hiding the value in an object?"
|
||||
, "For this reason, this empty placeholder key will always be ignored."
|
||||
]
|
||||
, coder = Json.value
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "event_id"
|
||||
, toField = .eventId
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
coderV2 : Json.Coder SendMessageEventOutputV2
|
||||
coderV2 =
|
||||
Json.object2
|
||||
{ name = "EventResponse"
|
||||
, description =
|
||||
[ "This endpoint is used to send a message event to a room. Message events allow access to historical events and pagination, making them suited for \"once-off\" activity in a room."
|
||||
, "The body of the request should be the content object of the event; the fields in this object will vary depending on the type of event."
|
||||
, "https://spec.matrix.org/legacy/r0.0.0/client_server.html#put-matrix-client-r0-rooms-roomid-send-eventtype-txnid"
|
||||
]
|
||||
, init = always SendMessageEventOutputV2
|
||||
}
|
||||
(Json.field.optional.value
|
||||
{ fieldName = L.field
|
||||
, toField = always Nothing
|
||||
, description =
|
||||
[ "The Elm SDK always expects objects to have at least two fields."
|
||||
, "Otherwise, what's the point of hiding the value in an object?"
|
||||
, "For this reason, this empty placeholder key will always be ignored."
|
||||
]
|
||||
, coder = Json.value
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "event_id"
|
||||
, toField = .eventId
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
|
@ -1,4 +1,7 @@
|
|||
module Internal.Api.Task exposing (Task, run)
|
||||
module Internal.Api.Task exposing
|
||||
( Task, run, Backpack
|
||||
, sendMessageEvent
|
||||
)
|
||||
|
||||
{-|
|
||||
|
||||
|
@ -15,14 +18,25 @@ up-to-date.
|
|||
|
||||
## Use
|
||||
|
||||
@docs Task, run
|
||||
@docs Task, run, Backpack
|
||||
|
||||
|
||||
## Tasks
|
||||
|
||||
@docs sendMessageEvent
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Api.BaseUrl.Api
|
||||
import Internal.Api.Chain as C
|
||||
import Internal.Api.LoginWithUsernameAndPassword.Api
|
||||
import Internal.Api.Now.Api
|
||||
import Internal.Api.Request as Request
|
||||
import Internal.Config.Log exposing (Log)
|
||||
import Internal.Values.Context exposing (APIContext)
|
||||
import Internal.Api.SendMessageEvent.Api
|
||||
import Internal.Api.Versions.Api
|
||||
import Internal.Config.Log exposing (Log, log)
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Context as Context exposing (APIContext)
|
||||
import Internal.Values.Envelope exposing (EnvelopeUpdate(..))
|
||||
import Internal.Values.Room exposing (RoomUpdate(..))
|
||||
import Internal.Values.Vault exposing (VaultUpdate(..))
|
||||
|
@ -49,6 +63,128 @@ type alias UFTask a b =
|
|||
C.TaskChain Request.Error (EnvelopeUpdate VaultUpdate) a b
|
||||
|
||||
|
||||
{-| Get an access token to talk to the Matrix API
|
||||
-}
|
||||
getAccessToken : UFTask { a | now : () } { a | accessToken : (), now : () }
|
||||
getAccessToken c =
|
||||
case Context.fromApiFormat c of
|
||||
context ->
|
||||
case ( Context.mostPopularToken context, context.username, context.password ) of
|
||||
( Just a, _, _ ) ->
|
||||
C.succeed
|
||||
{ messages = []
|
||||
, logs = [ log.debug "Using cached access token from Vault" ]
|
||||
, contextChange = Context.setAccessToken a
|
||||
}
|
||||
c
|
||||
|
||||
( Nothing, Just u, Just p ) ->
|
||||
Internal.Api.LoginWithUsernameAndPassword.Api.loginWithUsernameAndPassword
|
||||
{ deviceId = Context.fromApiFormat c |> .deviceId
|
||||
, enableRefreshToken = Just True -- TODO: Turn this into a setting
|
||||
, initialDeviceDisplayName = Nothing -- TODO: Turn this into a setting
|
||||
, password = p
|
||||
, username = u
|
||||
}
|
||||
c
|
||||
|
||||
( Nothing, Nothing, _ ) ->
|
||||
C.fail Request.MissingUsername c
|
||||
|
||||
( Nothing, Just _, Nothing ) ->
|
||||
C.fail Request.MissingPassword c
|
||||
|
||||
|
||||
{-| Get the base URL where the Matrix API can be accessed
|
||||
-}
|
||||
getBaseUrl : UFTask a { a | baseUrl : () }
|
||||
getBaseUrl c =
|
||||
case Context.fromApiFormat c |> .baseUrl of
|
||||
Just b ->
|
||||
C.succeed
|
||||
{ messages = []
|
||||
, logs = [ log.debug "Using cached baseURL from Vault" ]
|
||||
, contextChange = Context.setBaseUrl b
|
||||
}
|
||||
c
|
||||
|
||||
Nothing ->
|
||||
Internal.Api.BaseUrl.Api.baseUrl
|
||||
{ url = Context.fromApiFormat c |> .serverName }
|
||||
c
|
||||
|
||||
|
||||
{-| Get the current timestamp
|
||||
-}
|
||||
getNow : UFTask { a | baseUrl : () } { a | baseUrl : (), now : () }
|
||||
getNow =
|
||||
Internal.Api.Now.Api.getNow
|
||||
|
||||
|
||||
{-| Get the versions that are potentially supported by the Matrix API
|
||||
-}
|
||||
getVersions : UFTask { a | baseUrl : () } { a | baseUrl : (), versions : () }
|
||||
getVersions c =
|
||||
case Context.fromApiFormat c |> .versions of
|
||||
Just v ->
|
||||
C.succeed
|
||||
{ messages = []
|
||||
, logs = [ log.debug "Using cached versions from Vault" ]
|
||||
, contextChange = Context.setVersions v
|
||||
}
|
||||
c
|
||||
|
||||
Nothing ->
|
||||
Internal.Api.Versions.Api.versions c
|
||||
|
||||
|
||||
finishTask : UFTask {} b -> Task
|
||||
finishTask uftask =
|
||||
uftask
|
||||
|> C.andThen
|
||||
(C.succeed
|
||||
{ messages = []
|
||||
, logs = []
|
||||
, contextChange = Context.reset
|
||||
}
|
||||
)
|
||||
|> C.catchWith
|
||||
(\_ ->
|
||||
{ messages = [] -- TODO: Maybe categorize errors?
|
||||
, logs = [ log.warn "Encountered unhandled error" ]
|
||||
, contextChange = Context.reset
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| Establish a Task Chain context where the base URL and supported list of
|
||||
versions are known.
|
||||
-}
|
||||
makeVB : UFTask a { a | baseUrl : (), versions : () }
|
||||
makeVB =
|
||||
C.andThen getVersions getBaseUrl
|
||||
|
||||
|
||||
{-| Establish a Task Chain context where the base URL and supported list of
|
||||
versions are known, and where an access token is available to make an
|
||||
authenticated API call.
|
||||
-}
|
||||
makeVBA : UFTask a { a | accessToken : (), baseUrl : (), now : (), versions : () }
|
||||
makeVBA =
|
||||
makeVB
|
||||
|> C.andThen getNow
|
||||
|> C.andThen getAccessToken
|
||||
|
||||
|
||||
{-| Send a message event to a room.
|
||||
-}
|
||||
sendMessageEvent : { content : Json.Value, eventType : String, roomId : String, transactionId : String } -> Task
|
||||
sendMessageEvent input =
|
||||
makeVBA
|
||||
|> C.andThen (Internal.Api.SendMessageEvent.Api.sendMessageEvent input)
|
||||
|> finishTask
|
||||
|
||||
|
||||
{-| Transform a completed task into a Cmd.
|
||||
-}
|
||||
run : (Backpack -> msg) -> Task -> APIContext {} -> Cmd msg
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
module Internal.Tools.Timestamp exposing
|
||||
( Timestamp
|
||||
, add, toMs
|
||||
, coder, encode, decoder
|
||||
)
|
||||
|
||||
|
@ -12,6 +13,11 @@ elm/time. This module offers ways to work with the timestamp in meaningful ways.
|
|||
@docs Timestamp
|
||||
|
||||
|
||||
## Calculate
|
||||
|
||||
@docs add, toMs
|
||||
|
||||
|
||||
## JSON coders
|
||||
|
||||
@docs coder, encode, decoder
|
||||
|
@ -28,6 +34,15 @@ type alias Timestamp =
|
|||
Time.Posix
|
||||
|
||||
|
||||
{-| Add a given number of miliseconds to a given Timestamp.
|
||||
-}
|
||||
add : Int -> Timestamp -> Timestamp
|
||||
add m =
|
||||
Time.posixToMillis
|
||||
>> (+) m
|
||||
>> Time.millisToPosix
|
||||
|
||||
|
||||
{-| Create a Json coder
|
||||
-}
|
||||
coder : Json.Coder Timestamp
|
||||
|
@ -55,3 +70,10 @@ encode =
|
|||
decoder : Json.Decoder Timestamp
|
||||
decoder =
|
||||
Json.decode coder
|
||||
|
||||
|
||||
{-| Turn a Timestamp into a number of miliseconds
|
||||
-}
|
||||
toMs : Timestamp -> Int
|
||||
toMs =
|
||||
Time.posixToMillis
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
module Internal.Values.Context exposing
|
||||
( Context, init, coder, encode, decoder
|
||||
( Context, AccessToken, init, coder, encode, decoder, mostPopularToken
|
||||
, APIContext, apiFormat, fromApiFormat
|
||||
, setAccessToken, getAccessToken
|
||||
, setBaseUrl, getBaseUrl
|
||||
, setNow, getNow
|
||||
, setTransaction, getTransaction
|
||||
, Versions, setVersions, getVersions
|
||||
, reset
|
||||
)
|
||||
|
||||
{-| The Context is the set of variables that the user (mostly) cannot control.
|
||||
|
@ -14,7 +16,7 @@ the Matrix API.
|
|||
|
||||
## Context
|
||||
|
||||
@docs Context, init, coder, encode, decoder
|
||||
@docs Context, AccessToken, init, coder, encode, decoder, mostPopularToken
|
||||
|
||||
|
||||
## APIContext
|
||||
|
@ -38,6 +40,11 @@ information that can be inserted.
|
|||
@docs setBaseUrl, getBaseUrl
|
||||
|
||||
|
||||
### Timestamp
|
||||
|
||||
@docs setNow, getNow
|
||||
|
||||
|
||||
### Transaction id
|
||||
|
||||
@docs setTransaction, getTransaction
|
||||
|
@ -47,21 +54,43 @@ information that can be inserted.
|
|||
|
||||
@docs Versions, setVersions, getVersions
|
||||
|
||||
|
||||
### Reset
|
||||
|
||||
@docs reset
|
||||
|
||||
-}
|
||||
|
||||
import Internal.Config.Leaks as L
|
||||
import Internal.Config.Text as Text
|
||||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Tools.Timestamp as Timestamp exposing (Timestamp)
|
||||
import Json.Encode as E
|
||||
import Set exposing (Set)
|
||||
import Time
|
||||
|
||||
|
||||
{-| The Access Token is a combination of access tokens, values and refresh
|
||||
tokens that contain and summarizes all properties of a known access token.
|
||||
-}
|
||||
type alias AccessToken =
|
||||
{ created : Timestamp
|
||||
, expiryMs : Maybe Int
|
||||
, lastUsed : Timestamp
|
||||
, refresh : Maybe String
|
||||
, value : String
|
||||
}
|
||||
|
||||
|
||||
{-| The Context type stores all the information in the Vault. This data type is
|
||||
static and hence can be passed on easily.
|
||||
-}
|
||||
type alias Context =
|
||||
{ accessToken : Maybe String
|
||||
{ accessTokens : Hashdict AccessToken
|
||||
, baseUrl : Maybe String
|
||||
, deviceId : Maybe String
|
||||
, now : Maybe Timestamp
|
||||
, password : Maybe String
|
||||
, refreshToken : Maybe String
|
||||
, serverName : String
|
||||
|
@ -80,6 +109,7 @@ type APIContext ph
|
|||
{ accessToken : String
|
||||
, baseUrl : String
|
||||
, context : Context
|
||||
, now : Timestamp
|
||||
, transaction : String
|
||||
, versions : Versions
|
||||
}
|
||||
|
@ -94,9 +124,11 @@ type alias Versions =
|
|||
apiFormat : Context -> APIContext {}
|
||||
apiFormat context =
|
||||
APIContext
|
||||
{ accessToken = context.accessToken |> Maybe.withDefault L.accessToken
|
||||
{ accessToken =
|
||||
mostPopularToken context |> Maybe.withDefault L.accessToken
|
||||
, baseUrl = context.baseUrl |> Maybe.withDefault L.baseUrl
|
||||
, context = context
|
||||
, now = context.now |> Maybe.withDefault (Time.millisToPosix 0)
|
||||
, transaction = context.transaction |> Maybe.withDefault L.transaction
|
||||
, versions = context.versions |> Maybe.withDefault L.versions
|
||||
}
|
||||
|
@ -114,16 +146,16 @@ fromApiFormat (APIContext c) =
|
|||
-}
|
||||
coder : Json.Coder Context
|
||||
coder =
|
||||
Json.object8
|
||||
Json.object10
|
||||
{ name = Text.docs.context.name
|
||||
, description = Text.docs.context.description
|
||||
, init = Context
|
||||
}
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "accessToken"
|
||||
, toField = .accessToken
|
||||
(Json.field.required
|
||||
{ fieldName = "accessTokens"
|
||||
, toField = .accessTokens
|
||||
, description = Text.fields.context.accessToken
|
||||
, coder = Json.string
|
||||
, coder = Hashdict.coder .value coderAccessToken
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
|
@ -133,6 +165,20 @@ coder =
|
|||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "deviceId"
|
||||
, toField = .deviceId
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "now"
|
||||
, toField = .now
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Timestamp.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "password"
|
||||
, toField = .password
|
||||
|
@ -177,6 +223,52 @@ coder =
|
|||
)
|
||||
|
||||
|
||||
{-| JSON coder for an Access Token.
|
||||
-}
|
||||
coderAccessToken : Json.Coder AccessToken
|
||||
coderAccessToken =
|
||||
Json.object5
|
||||
{ name = Debug.todo "Needs docs"
|
||||
, description = Debug.todo "Needs docs"
|
||||
, init = AccessToken
|
||||
}
|
||||
(Json.field.required
|
||||
{ fieldName = "created"
|
||||
, toField = .created
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Timestamp.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "expiryMs"
|
||||
, toField = .expiryMs
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Json.int
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "lastUsed"
|
||||
, toField = .lastUsed
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Timestamp.coder
|
||||
}
|
||||
)
|
||||
(Json.field.optional.value
|
||||
{ fieldName = "refresh"
|
||||
, toField = .refresh
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "value"
|
||||
, toField = .value
|
||||
, description = Debug.todo "Needs docs"
|
||||
, coder = Json.string
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| Decode a Context type from a JSON value.
|
||||
-}
|
||||
decoder : Json.Decoder Context
|
||||
|
@ -195,8 +287,10 @@ encode =
|
|||
-}
|
||||
init : String -> Context
|
||||
init sn =
|
||||
{ accessToken = Nothing
|
||||
{ accessTokens = Hashdict.empty .value
|
||||
, baseUrl = Nothing
|
||||
, deviceId = Nothing
|
||||
, now = Nothing
|
||||
, refreshToken = Nothing
|
||||
, password = Nothing
|
||||
, serverName = sn
|
||||
|
@ -206,6 +300,36 @@ init sn =
|
|||
}
|
||||
|
||||
|
||||
{-| Get the most popular access token available, if any.
|
||||
-}
|
||||
mostPopularToken : Context -> Maybe String
|
||||
mostPopularToken c =
|
||||
c.accessTokens
|
||||
|> Hashdict.values
|
||||
|> List.sortBy
|
||||
(\token ->
|
||||
case token.expiryMs of
|
||||
Nothing ->
|
||||
( 0, Timestamp.toMs token.created )
|
||||
|
||||
Just e ->
|
||||
( 1
|
||||
, token.created
|
||||
|> Timestamp.add e
|
||||
|> Timestamp.toMs
|
||||
)
|
||||
)
|
||||
|> List.head
|
||||
|> Maybe.map .value
|
||||
|
||||
|
||||
{-| Reset the phantom type of the Context, effectively forgetting all values.
|
||||
-}
|
||||
reset : APIContext a -> APIContext {}
|
||||
reset (APIContext c) =
|
||||
APIContext c
|
||||
|
||||
|
||||
{-| Get an inserted access token.
|
||||
-}
|
||||
getAccessToken : APIContext { a | accessToken : () } -> String
|
||||
|
@ -234,6 +358,20 @@ setBaseUrl value (APIContext c) =
|
|||
APIContext { c | baseUrl = value }
|
||||
|
||||
|
||||
{-| Get an inserted timestamp.
|
||||
-}
|
||||
getNow : APIContext { a | now : () } -> Timestamp
|
||||
getNow (APIContext c) =
|
||||
c.now
|
||||
|
||||
|
||||
{-| Insert a Timestamp into the APIContext.
|
||||
-}
|
||||
setNow : Timestamp -> APIContext a -> APIContext { a | now : () }
|
||||
setNow t (APIContext c) =
|
||||
APIContext { c | now = t }
|
||||
|
||||
|
||||
{-| Get an inserted transaction id.
|
||||
-}
|
||||
getTransaction : APIContext { a | transaction : () } -> String
|
||||
|
|
|
@ -51,8 +51,10 @@ settings that can be adjusted manually.
|
|||
import Internal.Api.Request as Request
|
||||
import Internal.Config.Log exposing (Log)
|
||||
import Internal.Config.Text as Text
|
||||
import Internal.Tools.Hashdict as Hashdict
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Context as Context exposing (Context, Versions)
|
||||
import Internal.Tools.Timestamp exposing (Timestamp)
|
||||
import Internal.Values.Context as Context exposing (AccessToken, Context, Versions)
|
||||
import Internal.Values.Settings as Settings
|
||||
|
||||
|
||||
|
@ -74,8 +76,12 @@ type EnvelopeUpdate a
|
|||
= ContentUpdate a
|
||||
| HttpRequest (Request.Request ( Request.Error, List Log ) ( EnvelopeUpdate a, List Log ))
|
||||
| More (List (EnvelopeUpdate a))
|
||||
| SetAccessToken String
|
||||
| Optional (Maybe (EnvelopeUpdate a))
|
||||
| RemoveAccessToken String
|
||||
| SetAccessToken AccessToken
|
||||
| SetBaseUrl String
|
||||
| SetDeviceId String
|
||||
| SetNow Timestamp
|
||||
| SetRefreshToken String
|
||||
| SetVersions Versions
|
||||
|
||||
|
@ -179,10 +185,10 @@ getContent =
|
|||
{-| Create a new enveloped data type. All settings are set to default values
|
||||
from the [Internal.Config.Default](Internal-Config-Default) module.
|
||||
-}
|
||||
init : a -> Envelope a
|
||||
init x =
|
||||
{ content = x
|
||||
, context = Context.init
|
||||
init : { serverName : String, content : a } -> Envelope a
|
||||
init data =
|
||||
{ content = data.content
|
||||
, context = Context.init data.serverName
|
||||
, settings = Settings.init
|
||||
}
|
||||
|
||||
|
@ -296,12 +302,27 @@ update updateContent eu ({ context } as data) =
|
|||
More items ->
|
||||
List.foldl (update updateContent) data items
|
||||
|
||||
Optional (Just u) ->
|
||||
update updateContent u data
|
||||
|
||||
Optional Nothing ->
|
||||
data
|
||||
|
||||
RemoveAccessToken token ->
|
||||
{ data | context = { context | accessTokens = Hashdict.removeKey token context.accessTokens } }
|
||||
|
||||
SetAccessToken a ->
|
||||
{ data | context = { context | accessToken = Just a } }
|
||||
{ data | context = { context | accessTokens = Hashdict.insert a context.accessTokens } }
|
||||
|
||||
SetBaseUrl b ->
|
||||
{ data | context = { context | baseUrl = Just b } }
|
||||
|
||||
SetDeviceId d ->
|
||||
{ data | context = { context | deviceId = Just d } }
|
||||
|
||||
SetNow n ->
|
||||
{ data | context = { context | now = Just n } }
|
||||
|
||||
SetRefreshToken r ->
|
||||
{ data | context = { context | refreshToken = Just r } }
|
||||
|
||||
|
|
|
@ -39,6 +39,7 @@ import Internal.Config.Text as Text
|
|||
import Internal.Tools.Hashdict as Hashdict exposing (Hashdict)
|
||||
import Internal.Tools.Json as Json
|
||||
import Internal.Values.Room as Room exposing (Room)
|
||||
import Internal.Values.User as User exposing (User)
|
||||
|
||||
|
||||
{-| This is the Vault type.
|
||||
|
@ -46,6 +47,7 @@ import Internal.Values.Room as Room exposing (Room)
|
|||
type alias Vault =
|
||||
{ accountData : Dict String Json.Value
|
||||
, rooms : Hashdict Room
|
||||
, user : User
|
||||
}
|
||||
|
||||
|
||||
|
@ -57,11 +59,12 @@ type VaultUpdate
|
|||
| MapRoom String Room.RoomUpdate
|
||||
| More (List VaultUpdate)
|
||||
| SetAccountData String Json.Value
|
||||
| SetUser User
|
||||
|
||||
|
||||
coder : Json.Coder Vault
|
||||
coder =
|
||||
Json.object2
|
||||
Json.object3
|
||||
{ name = Text.docs.vault.name
|
||||
, description = Text.docs.vault.description
|
||||
, init = Vault
|
||||
|
@ -80,6 +83,13 @@ coder =
|
|||
, coder = Hashdict.coder .roomId Room.coder
|
||||
}
|
||||
)
|
||||
(Json.field.required
|
||||
{ fieldName = "user"
|
||||
, toField = .user
|
||||
, description = Debug.todo "Needs description"
|
||||
, coder = User.coder
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
{-| Get a given room by its room id.
|
||||
|
@ -136,3 +146,6 @@ update vu vault =
|
|||
|
||||
SetAccountData key value ->
|
||||
setAccountData key value vault
|
||||
|
||||
SetUser user ->
|
||||
{ vault | user = user }
|
||||
|
|
|
@ -16,6 +16,7 @@ safely access all exposed data types without risking to create circular imports.
|
|||
|
||||
-}
|
||||
|
||||
import Internal.Api.Main as Api
|
||||
import Internal.Values.Envelope as Envelope
|
||||
import Internal.Values.Event as Event
|
||||
import Internal.Values.Room as Room
|
||||
|
@ -50,4 +51,4 @@ type Vault
|
|||
{-| Opaque type for Matrix VaultUpdate
|
||||
-}
|
||||
type VaultUpdate
|
||||
= VaultUpdate (Envelope.EnvelopeUpdate Vault.VaultUpdate)
|
||||
= VaultUpdate Api.Msg
|
||||
|
|
Loading…
Reference in New Issue