Add type aliases to objects

Adding type aliases improves readability, but also avoids duplicate code in the long run.
main
Bram van den Heuvel 3 months ago
parent 1e7e65ce84
commit 6666050ca4
  1. 2
      elm.json
  2. 381
      src/Api.elm
  3. 315
      src/SpecObjects.elm

@ -7,6 +7,7 @@
"dependencies": {
"direct": {
"elm/browser": "1.0.2",
"elm/bytes": "1.0.8",
"elm/core": "1.0.5",
"elm/html": "1.0.0",
"elm/http": "2.0.0",
@ -14,7 +15,6 @@
"elm/time": "1.0.0"
},
"indirect": {
"elm/bytes": "1.0.8",
"elm/file": "1.0.5",
"elm/url": "1.0.0",
"elm/virtual-dom": "1.0.3"

@ -6,6 +6,7 @@ module Api exposing ( changePassword, ClientError(..)
, postLogin, rawAccessToAPI, refreshAccessToken
, ServerError(..), getWhoAmI
)
import Bytes as B
import Dict
import Http
import Json.Decode as D
@ -14,7 +15,7 @@ import Process
import Task exposing (Task)
import Time
import SpecObjects
import SpecObjects as S
{----------------------------}
{----- PUBLIC FUNCTIONS -----}
@ -391,7 +392,7 @@ type ClientError
| ServerDoesntFollowJSONSpec
| ServerDoesntReturnBaseURL
| CouldntGetTimestamp
| InvalidInputAccordingToSpec
| InvalidInputAccordingToSpec String
{-| Potential error codes that the server may return. If the error is not a
default one described in the Matrix Spec, it will be a `CustomServerError`
@ -519,25 +520,23 @@ Gets discovery information about the domain.
https://spec.matrix.org/v1.4/client-server-api/#getwell-knownmatrixclient
-}
getWellKnownMatrixClientTask : String -> Task CommunicationError
{ baseURL : String
, identityServer : Maybe String
}
getWellKnownMatrixClientTask : String -> Task
CommunicationError
WellKnownMatrixClient
getWellKnownMatrixClientTask hostName =
rawAccessToAPITask
{ headers = []
, method = "GET"
, url = withURL hostName "/.well-known/matrix/client"
, body = Nothing
, decoder =
D.map2
(\burl ids -> { baseURL = burl, identityServer = ids })
(D.at ["m.homeserver", "base_url"] D.string)
(D.at ["m.identity_server", "base_url"] D.string
|> D.maybe
)
, decoder = wellKnownMatrixClientDecoder
}
type alias WellKnownMatrixClient = S.DiscoveryInformation
wellKnownMatrixClientDecoder : D.Decoder WellKnownMatrixClient
wellKnownMatrixClientDecoder = S.discoveryInformationDecoder
{-| GET /_matrix/client/versions
Gets the versions of the specification supported by the server.
@ -546,24 +545,33 @@ https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientversions
-}
getSpecVersionSupportTask : String -> Task
CommunicationError
{ versions : List String
, unstableFeatures : Dict.Dict
String
Bool
}
SpecVersionSupport
getSpecVersionSupportTask baseURL =
rawAccessToAPITask
{ headers = []
, method = "GET"
, url = withURL baseURL "/_matrix/client/versions"
, body = Nothing
, decoder =
D.map2
(\ver unF -> { versions = ver, unstableFeatures = unF })
(D.field "versions" (D.list D.string))
(D.field "unstable_features" (D.dict D.bool))
, decoder = specVersionSupportDecoder
}
type alias SpecVersionSupport = { versions : List String
, unstableFeatures : Dict.Dict
String
Bool
}
specVersionSupportDecoder : D.Decoder SpecVersionSupport
specVersionSupportDecoder =
D.map2
(\ver unF ->
{ versions = ver
, unstableFeatures = Maybe.withDefault Dict.empty unF
}
)
( D.field "versions" (D.list D.string) )
(D.maybe <| D.field "unstable_features" (D.dict D.bool) )
{-| GET /_matrix/client/v1/register/m.login.registration_token/validity
Queries the server to determine if a given registration token is still valid
@ -579,16 +587,37 @@ getRegistrationTokenValidityTask : { baseURL : String
CommunicationError
Bool
getRegistrationTokenValidityTask data =
rawAccessToAPITask
{ headers = []
, method = "GET"
, url = "https://" ++ data.baseURL ++
"/_matrix/client/v1/register/m.login.registration_token" ++
"/validity?token=" ++ data.token
, body = Nothing
, decoder = D.field "valid" D.bool
}
|> retryDuringRatelimits data.timeoutAfter
(
if S.validateCharacters "A-Za-z0-9._~-" data.token then
if String.length data.token <= 64 then
Task.succeed ()
else
InvalidInputAccordingToSpec "Invalid token: too long"
|> SDKException
|> Task.fail
else
InvalidInputAccordingToSpec "Invalid token: invalid characters"
|> SDKException
|> Task.fail
)
|> Task.andThen
(\_ ->
rawAccessToAPITask
{ headers = []
, method = "GET"
, url = "https://" ++ data.baseURL ++
"/_matrix/client/v1/register/m.login.registration_token" ++
"/validity?token=" ++ data.token
, body = Nothing
, decoder = registrationTokenValidityDecoder
}
|> retryDuringRatelimits data.timeoutAfter
)
type alias RegistrationTokenValidity = Bool
registrationTokenValidityDecoder : D.Decoder RegistrationTokenValidity
registrationTokenValidityDecoder = D.field "valid" D.bool
{-| GET /_matrix/client/v3/login
@ -599,26 +628,26 @@ https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3login
-}
getLoginTypesTask : { baseURL : String
, timeoutAfter : Int
} -> Task CommunicationError (Maybe (List (Maybe String)))
} -> Task CommunicationError LoginTypes
getLoginTypesTask data =
rawAccessToAPITask
{ headers = []
, method = "GET"
, url = withURL data.baseURL "/_matrix/client/v3/login"
, body = Nothing
, decoder =
-- NOTE: It does not make sense that the `flows` key would not be
-- returned. I might write an MSC for this in the near future.
-- D.field "flows" (D.list (D.field "type" D.string))
D.string
|> D.field "type"
|> D.maybe
|> D.list
|> D.field "flows"
|> D.maybe
, decoder = loginTypesDecoder
}
|> retryDuringRatelimits data.timeoutAfter
type alias LoginTypes = List S.LoginFlow
loginTypesDecoder : D.Decoder LoginTypes
loginTypesDecoder =
D.list S.loginFlowDecoder
|> D.field "flows"
|> D.maybe
|> D.map (Maybe.withDefault [])
{-| POST /_matrix/client/v3/login
Authenticates the user, and issues an access token they can use to authorize
@ -641,24 +670,23 @@ postLoginTask : { baseURL : String
, token : Maybe String
, loginType : String
, timeoutAfter : Int
} -> Task
CommunicationError
{ access_token : String
, device_id : String
, expires_at : Maybe Time.Posix
, refresh_token : Maybe String
, user_id : String
, well_known_baseURL : String
, well_known_identityServer : Maybe String
}
} -> Task CommunicationError Login
postLoginTask data =
( case (data.loginType, data.token, data.password) of
("m.login.password", _, Just _) ->
Time.now
("m.login.token", Just _, _) ->
Time.now
("m.login.password", _, Nothing) ->
InvalidInputAccordingToSpec "Invalid login type"
|> SDKException
|> Task.fail
("m.login.token", Nothing, _) ->
InvalidInputAccordingToSpec "Invalid login type"
|> SDKException
|> Task.fail
_ ->
InvalidInputAccordingToSpec
InvalidInputAccordingToSpec "Invalid login type"
|> SDKException
|> Task.fail
)
@ -683,30 +711,42 @@ postLoginTask data =
, ( "token" , Maybe.map E.string data.token )
, ( "type" , Just <| E.string data.loginType )
]
, decoder = D.map7
(\a d e r u wb wi ->
{ access_token = a
, device_id = d
, expires_at = e
|> Maybe.map ((+) (Time.posixToMillis now))
|> Maybe.map Time.millisToPosix
, refresh_token= r
, user_id = u
, well_known_baseURL = wb
, well_known_identityServer = wi
}
)
( D.field "access_token" D.string )
( D.field "device_id" D.string )
( D.maybe <| D.field "expires_in_ms" D.int )
( D.maybe <| D.field "refresh_token" D.string )
( D.field "user_id" D.string )
( D.at ["well_known", "m.homeserver", "base_url"] D.string )
( D.maybe <| D.at ["well_known", "m.homeserver", "base_url"] D.string )
, decoder = loginDecoder now
}
|> retryDuringRatelimits data.timeoutAfter
)
type alias Login = { access_token : String
, device_id : String
, expires_at : Maybe Time.Posix
, refresh_token : Maybe String
, user_id : String
, well_known_baseURL : String
, well_known_identityServer : Maybe String
}
loginDecoder : Time.Posix -> D.Decoder Login
loginDecoder t =
D.map7
(\a d e r u wb wi ->
{ access_token = a
, device_id = d
, expires_at = e
|> Maybe.map ((+) (Time.posixToMillis now))
|> Maybe.map Time.millisToPosix
, refresh_token= r
, user_id = u
, well_known_baseURL = wb
, well_known_identityServer = wi
}
)
( D.field "access_token" D.string )
( D.field "device_id" D.string )
( D.maybe <| D.field "expires_in_ms" D.int )
( D.maybe <| D.field "refresh_token" D.string )
( D.field "user_id" D.string )
( D.at ["well_known", "m.homeserver", "base_url"] D.string )
( D.maybe <| D.at ["well_known", "m.homeserver", "base_url"] D.string )
{-| POST /_matrix/client/v3/refresh
@ -732,10 +772,7 @@ refreshAccessTokenTask : { baseURL : String
, refreshToken : String
} -> Task
CommunicationError
{ accessToken : String
, refreshToken : String
, expiresAt : Maybe Time.Posix
}
RefreshAccessToken
refreshAccessTokenTask data =
-- NOTE: Fun fact, apparently it's not required to provide an access
-- token in the body of the request.
@ -762,28 +799,37 @@ refreshAccessTokenTask data =
)
]
|> Just
, decoder =
D.map3
(\acs ref exp ->
{ accessToken = acs
, refreshToken = Maybe.withDefault
data.refreshToken
ref
, expiresAt = Maybe.map
(\t ->
now
|> Time.posixToMillis
|> (+) t
|> Time.millisToPosix
)
exp
}
)
( D.field "access_token" D.string )
( D.maybe <| D.field "refresh_token" D.string )
( D.maybe <| D.field "expires_in_ms" D.int )
}
, decoder = refreshAccessTokenDecoder data.refreshToken now
}
)
type alias RefreshAccessToken =
{ accessToken : String
, refreshToken : String
, expiresAt : Maybe Time.Posix
}
refreshAccessTokenDecoder : String -> Time.Posix -> D.Decoder RefreshAccessToken
refreshAccessTokenDecoder token now =
D.map3
(\acs ref exp ->
{ accessToken = acs
, refreshToken = Maybe.withDefault
token
ref
, expiresAt = Maybe.map
(\t ->
now
|> Time.posixToMillis
|> (+) t
|> Time.millisToPosix
)
exp
}
)
( D.field "access_token" D.string )
( D.maybe <| D.field "refresh_token" D.string )
( D.maybe <| D.field "expires_in_ms" D.int )
{-| POST /_matrix/client/v3/logout
@ -802,8 +848,7 @@ logoutAccessTokenTask data =
, method = "POST"
, url = withURL data.baseURL "/_matrix/client/v3/logout"
, body = Nothing
, decoder =
D.succeed ()
, decoder = D.succeed ()
}
{-| POST /_matrix/client/v3/logout/all
@ -824,8 +869,7 @@ logoutAllAccessTokensTask data =
, method = "POST"
, url = withURL data.baseURL "/_matrix/client/v3/logout/all"
, body = Nothing
, decoder =
D.succeed ()
, decoder = D.succeed ()
}
{-| POST /_matrix/client/v3/account/deactivate
@ -844,27 +888,39 @@ https://spec.matrix.org/v1.4/client-server-api/#post_matrixclientv3accountdeacti
-}
deactivateUserTask : { baseURL : String
, accessToken : String
, auth : Maybe S.AuthenticationData
, idServer : Maybe String
, timeoutAfter : Int
} -> Task CommunicationError String
} -> Task CommunicationError DeactivateUser
deactivateUserTask data =
rawAccessToAPITask
{ headers = withAccessToken data.accessToken
, method = "POST"
, url = withURL data.baseURL "/_matrix/client/v3/account/deactivate"
, body = Nothing
, decoder = D.field "id_server_unbind_result" D.string
, body = maybeObject
[ ( "auth"
, Maybe.map S.encodeAuthenticationData data.auth
)
, ( "id_server"
, Maybe.map E.string data.idServer
)
]
, decoder = deactivateUserDecoder
}
|> Task.andThen
|> retryDuringRatelimits data.timeoutAfter
type DeactivateUser = Success | NoSupport
deactivateUserDecoder : D.Decoder DeactivateUser
deactivateUserDecoder =
D.field "id_server_unbind_result" D.string
|> D.andThen
(\result ->
case result of
"success" ->
Task.succeed result
"no-support" ->
Task.succeed result
_ ->
Task.fail (SDKException ServerDoesntFollowJSONSpec)
"success" -> D.succeed Success
"no-support" -> D.succeed NoSupport
_ -> D.fail "Invalid unbind indicator."
)
|> retryDuringRatelimits data.timeoutAfter
{-| POST /_matrix/client/v3/account/password
@ -925,40 +981,39 @@ validateEmailTask : { baseURL : String
, idServer : Maybe String
, nextLink : Maybe String
, sendAttempt : Int
} -> Task
CommunicationError
{ sid : String, submit_url : Maybe String }
} -> Task CommunicationError S.RequestTokenResponse
validateEmailTask data =
( case (data.idAccessToken, data.idServer) of
(Nothing, Just _) ->
InvalidInputAccordingToSpec
InvalidInputAccordingToSpec "ID Access token is required when an ID server is supplied"
|> SDKException
|> Task.fail
_ ->
rawAccessToAPITask
{ headers = []
, method = "POST"
, url = withURL data.baseURL "/_matrix/client/v3/account/password/email/requestToken"
, body = maybeObject
[ ( "client_secret", Just <| E.string data.clientSecret )
, ( "email", Just <| E.string data.email )
, ( "id_access_token", Maybe.map E.string data.idAccessToken )
, ( "id_server", Maybe.map E.string data.idServer )
, ( "next_link", Maybe.map E.string data.nextLink )
, ( "send_attempt", Just <| E.int data.sendAttempt )
]
, decoder = D.map2
(\sid surl -> { sid = sid, submit_url = surl })
( D.field "sid" D.string )
( D.maybe <| D.field "submit_url" D.string)
}
if S.validateCharacters "0-9a-zA-Z.=_-" data.clientSecret then
if String.length data.clientSecret <= 255 then
rawAccessToAPITask
{ headers = []
, method = "POST"
, url = withURL data.baseURL "/_matrix/client/v3/account/password/email/requestToken"
, body = maybeObject
[ ( "client_secret" , Just <| E.string data.clientSecret )
, ( "email" , Just <| E.string data.email )
, ( "id_access_token", Maybe.map E.string data.idAccessToken )
, ( "id_server" , Maybe.map E.string data.idServer )
, ( "next_link" , Maybe.map E.string data.nextLink )
, ( "send_attempt" , Just <| E.int data.sendAttempt )
]
, decoder = S.requestTokenResponseDecoder
}
else
InvalidInputAccordingToSpec "Client secret is longer than allowed"
|> SDKException
|> Task.fail
else
InvalidInputAccordingToSpec "Client secret contains invalid characters"
|> SDKException
|> Task.fail
)
-- -- TODO: Validate function output to see whether the session ID follows
-- -- the spec of consisting entirely of the characters [0-9a-zA-Z.=_-]
-- |> Task.andThen
-- (\result ->
-- )
{- POST /_matrix/client/v3/account/password/msisdn/requestToken
@ -1058,30 +1113,32 @@ https://spec.matrix.org/v1.4/client-server-api/#get_matrixclientv3accountwhoami
getWhoAmITask : { baseURL : String
, accessToken : String
, timeoutAfter : Int
} -> Task
CommunicationError
{ device_id : String
, is_guest : Bool
, user_id : String
}
} -> Task CommunicationError WhoAmI
getWhoAmITask data =
rawAccessToAPITask
{ headers = withAccessToken data.accessToken
, method = "GET"
, url = withURL data.baseURL "/_matrix/client/v3/account/whoami"
, body = Nothing
, decoder = D.map3
(\dv gs uid -> { device_id = dv
, is_guest = Maybe.withDefault False gs
, user_id = uid
}
)
( D.field "device_id" D.string )
( D.maybe <| D.field "is_guest" D.bool )
( D.field "user_id" D.string )
, decoder = whoAmIDecoder
}
|> retryDuringRatelimits data.timeoutAfter
type alias WhoAmI = { device_id : String, is_guest : Bool, user_id : String }
whoAmIDecoder : D.Decoder WhoAmI
whoAmIDecoder =
D.map3
(\dv gs uid -> { device_id = dv
, is_guest = Maybe.withDefault False gs
, user_id = uid
}
)
( D.field "device_id" D.string )
( D.maybe <| D.field "is_guest" D.bool )
( D.field "user_id" D.string )
{- GET /_matrix/client/v3/capabilities
Gets information about the servers supported feature set and other relevant capabilities.
@ -1304,15 +1361,7 @@ decodeTaskAs decoder =
-}
maybeObject : List (String, Maybe E.Value) -> Maybe E.Value
maybeObject =
List.filterMap
(\(name, value) ->
case value of
Just v ->
Just (name, v)
_ ->
Nothing
)
>> E.object
S.maybeObject
>> Just
{-| Raise an error when a specific error code is raised.

@ -1,5 +1,6 @@
module Events exposing (..)
module SpecObjects exposing (..)
import Bytes.Encode as B
import Dict
import Json.Decode as D
import Json.Encode as E
@ -328,7 +329,116 @@ encodeCapabilities info =
-}
-- type alias Categories = Maybe RoomEventsCriteria
{- # CLIENT EVENT WITHOUT ROOM ID
| Name | Type | Description
| -------- | ------- | --------------- |
| events | [Event] | List of events. |
-}
type alias ClientEventWithoutRoomID =
{ content : EventContent
, eventID : String
, originServerTs : Time.Posix
, sender : String
, stateKey : Maybe String
, eventType : String
, unsigned : OptionalUnsignedData
}
type OptionalUnsignedData
= Unsigned UnsignedData
| NoExtraInfo
clientWithoutRoomIDDecoder : D.Decoder ClientEventWithoutRoomID
clientWithoutRoomIDDecoder =
D.map7
(\c e o s sk t u ->
{ content = c, eventID = e, originServerTs = o, sender = s
, stateKey = sk, eventType = t
, unsigned =
( case u of
Just v ->
Unsigned v
Nothing ->
NoExtraInfo
)
}
)
( D.field "type" D.string
|> D.andThen eventContentDecoder
)
( D.field "event_id" D.string )
( D.field "origin_server_ts" D.int
|> D.map Time.millisToPosix
)
( D.field "sender" D.string )
( D.maybe <| D.field "state_key" D.string )
( D.field "type" D.string )
( D.field "unsigned" unsignedDataDecoder )
--
|> D.andThen
(\result ->
if B.getStringWidth result.eventID > 255 then
D.fail "Key `event_id` exceeds 255 bytes"
else
if B.getStringWidth result.sender > 255 then
D.fail "Key `sender` exceeds 255 bytes"
else
if B.getStringWidth result.eventType > 255 then
D.fail "Key `type` exceeds 255 bytes"
else
if result.stateKey
|> Maybe.map B.getStringWidth
|> Maybe.withDefault 0
|> (<) 255
then
D.fail "Key `state_key` exceeds 255 bytes"
else
D.succeed result
)
encodeClientEventWithoutRoomID : ClientEventWithoutRoomID -> E.Value
encodeClientEventWithoutRoomID info =
maybeObject
[ ( "content" , Just <| encodeEventContent info.eventType )
, ( "event_id" , Just <| E.string info.eventID )
, ( "origin_server_ts" , Just <| E.int <| Time.posixToMillis info.originServerTs )
, ( "sender" , Just <| E.string info.sender )
, ( "state_key", Maybe.map E.string info.stateKey )
, ( "type" , Maybe.map E.string info.eventType )
, ( "unsigned"
, ( case info.unsigned of
Unsigned u ->
Just <| encodeUnsignedData u
NoExtraInfo ->
Nothing
)
)
]
{- # DEVICE LISTS
| Name | Type | Description
| ------- | -------- | ----------------------------------------------------------------------------------------------------- |
| changed | [string] | List of users who have updated their device identity or cross-signing keys. |
| left | [string] | List of users with whom we do not share any encrypted rooms anymore since the previous sync response. |
-}
type alias DeviceLists = { changed : List String, left : List String }
deviceListsDecoder : D.Decoder DeviceLists
deviceListsDecoder =
D.map2
(\c l -> { changed = c, left = l })
( D.field "changed" (D.list D.string) |> D.map (Maybe.withDefault []) )
( D.field "left" (D.list D.string) |> D.map (Maybe.withDefault []) )
encodeDeviceLists : DeviceLists -> E.Value
encodeDeviceLists info =
E.object
[ ( "changed" , E.list E.string info.changed )
, ( "left" , E.list E.string info.left )
]
{- # DISCOVERY INFORMATION
@ -365,6 +475,26 @@ encodeDiscoveryInformation info =
)
]
{- # EPHEMERAL
| Name | Type | Description
| -------- | ------- | --------------- |
| events | [Event] | List of events. |
-}
type alias Ephemeral = List Event
ephemeralDecoder : D.Decoder Ephemeral
ephemeralDecoder =
eventDecoder
|> D.list
|> D.field "events"
|> D.maybe
|> D.map (Maybe.withDefault [])
encodeEphemeral : Ephemeral -> E.Value
encodeEphemeral info =
E.object [ ( "events", E.list encodeEvent info ) ]
{- # ERROR
| Name | Type | Description
@ -408,6 +538,24 @@ encodeEvent : Event -> E.Value
encodeEvent info =
E.object [ ( "content", info.content ), ( "type", E.string info.eventType ) ]
{- # EVENT CONTENT
The event content depends highly on the event type.
-}
type EventContent = OtherEventType E.Value
eventContentDecoder : String -> D.Decoder EventContent
eventContentDecoder eventType =
case eventType of
_ ->
D.map OtherEventType D.value
encodeEventContent : EventContent -> E.Value
encodeEventContent info =
case info of
OtherEventType v ->
v
{- # FLOW INFORMATION
| Name | Type | Description
@ -460,6 +608,45 @@ encodeIdentityServerInformation : IdentityServerInformation -> E.Value
encodeIdentityServerInformation info =
E.object [ ( "base_url", E.string info ) ]
{- # INVITE STATE
| Name | Type | Description
| -------- | -------------------- | --------------- |
| events | [StrippedStateEvent] | List of events. |
-}
type alias InviteState = List StrippedStateEvent
inviteStateDecoder : D.Decoder InviteState
inviteStateDecoder =
strippedStateEventDecoder
|> D.list
|> D.field "events"
|> D.maybe
|> D.map (Maybe.withDefault [])
encodeInviteState : InviteState -> E.Value
encodeInviteState info =
E.object [ ( "events", E.list encodeStrippedStateEvent info ) ]
{- # INVITED ROOM
| Name | Type | Description
| ------------ | ----------- | --------------------------------------------------------------- |
| invite_state | InviteState | The stripped state of a room that the user has been invited to. |
-}
type alias InvitedRoom = InviteState
invitedRoomDecoder : D.Decoder InvitedRoom
invitedRoomDecoder =
inviteStateDecoder
|> D.field "invite_state"
|> D.maybe
|> D.map (Maybe.withDefault [])
encodeInvitedRoom : InvitedRoom -> E.Value
encodeInvitedRoom info =
E.object [ ( "invite_state", encodeInviteState info ) ]
{- # LOGINFLOW
| Name | Type | Description
@ -476,6 +663,40 @@ encodeLoginFlow : LoginFlow -> E.Value
encodeLoginFlow info =
maybeObject [ ( "type", Maybe.map E.string info ) ]
{- # ONE-TIME KEYS COUNT
| Name | Type | Description
| --------------- | ------------- | --------------- |
| Encryption keys | string -> int | List of events. |
-}
type alias OneTimeKeysCount = Dict.Dict String Int
oneTimeKeysCountDecoder : D.Decoder OneTimeKeysCount
oneTimeKeysCountDecoder = D.dict D.int
encodeOneTimeKeysCount : OneTimeKeysCount -> E.Value
encodeOneTimeKeysCount info = E.dict identity E.int info
{- # PRESENCE
| Name | Type | Description
| -------- | ------- | --------------- |
| events | [Event] | List of events. |
-}
type alias Presence = List Event
presenceDecoder : D.Decoder Presence
presenceDecoder =
eventDecoder
|> D.list
|> D.field "events"
|> D.maybe
|> D.map (Maybe.withDefault [])
encodePresence : Presence -> E.Value
encodePresence info =
E.object [ ( "events", E.list encodeEvent info ) ]
{- # RATE LIMIT ERROR
| Name | Type | Description
@ -583,6 +804,89 @@ encodeRoomVersionStability info =
)
]
{- # STRIPPED STATE EVENT
| Name | Type | Description
| --------- | ------------ | -------------------------------------- |
| content | EventContent | REQUIRED: The content for the event. |
| sender | string | REQUIRED: The sender for the event. |
| state_key | string | REQUIRED: The state_key for the event. |
| type | string | REQUIRED: The type for the event. |
-}
type alias StrippedStateEvent =
{ content : EventContent
, sender : String
, stateKey : String
, eventType : String
}
strippedStateEventDecoder : D.Decoder StrippedStateEvent
strippedStateEventDecoder =
D.map4
(\c s sk t -> { content = c, sender = s, stateKey = sk, eventType = t })
( D.field "type" D.string |> D.andThen eventContentDecoder)
( D.field "sender" D.string )
( D.field "state_key" D.string )
( D.field "type" D.string )
-- Verify that the keys do not exceed 255 bytes
|> D.andThen
(\result ->
if B.getStringWidth result.sender > 255 then
D.fail "Key `sender` exceeds 255 bytes"
else
if B.getStringWidth result.stateKey > 255 then
D.fail "Key `state_key` exceeds 255 bytes"
else
if B.getStringWidth result.eventType > 255 then
D.fail "Key `type` exceeds 255 bytes"
else
D.succeed result
)
encodeStrippedStateEvent : StrippedStateEvent -> E.Value
encodeStrippedStateEvent info =
E.object
[ ( "content" , encodeEventContent info.content )
, ( "sender" , E.string info.sender )
, ( "state_key", E.string info.stateKey )
, ( "type" , E.string info.eventType )
]
{- # UNSIGNED DATA
| Name | Type | Description
| ---------------- | ------------------------ | ------------------------------------------------------------------- |
| age | int | The time in milliseconds that has elapsed since the event was sent. |
| prev_content | EventContent | The previous content for this event. |
| redacted_because | ClientEventWithoutRoomID | The event that redacted this event, if any. |
| transaction_id | string | The client-supplied transaction ID. |
-}
type alias UnsignedData =
{ age : Maybe Int
, prevContent : Maybe EventContent
, redactedBecause : Maybe ClientEventWithoutRoomID
, transactionID : String
}
unsignedDataDecoder : D.Decoder UnsignedData
unsignedDataDecoder =
D.map4
(\a p r t -> {age=a, prevContent=p, redactedBecause=r, transactionID=t})
( D.maybe <| D.field "age" D.int )
( D.maybe <| D.field "prev_content" eventContentDecoder )
( D.maybe <| D.field "redacted_because" clientEventWithoutRoomIDDecoder )
( D.maybe <| D.field "transaction_id" D.string )
encodeUnsignedData : UnsignedData -> E.Value
encodeUnsignedData info =
maybeObject
[ ( "age" , Maybe.map E.int info.age )
, ( "prev_content" , Maybe.map encodeEventContent info.prevContent )
, ( "redacted_because" , Maybe.map encodeClientEventWithoutRoomID info.redactedBecause )
, ( "transaction_id" , Maybe.map E.string info.transactionID )
]
{- # USER IDENTIFIER
| Name | Type | Description
@ -657,11 +961,6 @@ type alias ClientEvent =
type RoomEventType = MessageEvent | StateEvent String
type alias StrippedStateEvent =
{ content : EventBody, sender : String, stateKey : String, eventType : String
}
-- TODO: Verify that all keys do not exceed 255 bytes
type JoinRule = KnockRestricted | Public | InviteBased | Private
| OtherJoinRule String | Knock | Restricted
@ -715,7 +1014,7 @@ type EventBody
, usersDefault : Int
}
| OtherEventType E.Value
| OtherEventTypeTemp E.Value
decodeClientEvent : D.Decoder ClientEvent
decodeClientEvent =
@ -875,5 +1174,5 @@ decodeEventBody eventType =
)
_ ->
D.map OtherEventType D.value
D.map OtherEventTypeTemp D.value

Loading…
Cancel
Save